godot/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs

using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

#nullable enable

namespace Godot
{
    /// <summary>
    /// 2×3 matrix (2 rows, 3 columns) used for 2D linear transformations.
    /// It can represent transformations such as translation, rotation, or scaling.
    /// It consists of a three <see cref="Vector2"/> values: x, y, and the origin.
    ///
    /// For more information, read this documentation article:
    /// https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html
    /// </summary>
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct Transform2D : IEquatable<Transform2D>
    {
        /// <summary>
        /// The basis matrix's X vector (column 0). Equivalent to array index <c>[0]</c>.
        /// </summary>
        public Vector2 X;

        /// <summary>
        /// The basis matrix's Y vector (column 1). Equivalent to array index <c>[1]</c>.
        /// </summary>
        public Vector2 Y;

        /// <summary>
        /// The origin vector (column 2, the third column). Equivalent to array index <c>[2]</c>.
        /// The origin vector represents translation.
        /// </summary>
        public Vector2 Origin;

        /// <summary>
        /// Returns the transform's rotation (in radians).
        /// </summary>
        public readonly real_t Rotation => Mathf.Atan2(X.Y, X.X);

        /// <summary>
        /// Returns the scale.
        /// </summary>
        public readonly Vector2 Scale
        {
            get
            {
                real_t detSign = Mathf.Sign(Determinant());
                return new Vector2(X.Length(), detSign * Y.Length());
            }
        }

        /// <summary>
        /// Returns the transform's skew (in radians).
        /// </summary>
        public readonly real_t Skew
        {
            get
            {
                real_t detSign = Mathf.Sign(Determinant());
                return Mathf.Acos(X.Normalized().Dot(detSign * Y.Normalized())) - Mathf.Pi * 0.5f;
            }
        }

        /// <summary>
        /// Access whole columns in the form of <see cref="Vector2"/>.
        /// The third column is the <see cref="Origin"/> vector.
        /// </summary>
        /// <param name="column">Which column vector.</param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="column"/> is not 0, 1 or 2.
        /// </exception>
        public Vector2 this[int column]
        {
            readonly get
            {
                switch (column)
                {
                    case 0:
                        return X;
                    case 1:
                        return Y;
                    case 2:
                        return Origin;
                    default:
                        throw new ArgumentOutOfRangeException(nameof(column));
                }
            }
            set
            {
                switch (column)
                {
                    case 0:
                        X = value;
                        return;
                    case 1:
                        Y = value;
                        return;
                    case 2:
                        Origin = value;
                        return;
                    default:
                        throw new ArgumentOutOfRangeException(nameof(column));
                }
            }
        }

        /// <summary>
        /// Access matrix elements in column-major order.
        /// The third column is the <see cref="Origin"/> vector.
        /// </summary>
        /// <param name="column">Which column, the matrix horizontal position.</param>
        /// <param name="row">Which row, the matrix vertical position.</param>
        public real_t this[int column, int row]
        {
            readonly get
            {
                return this[column][row];
            }
            set
            {
                Vector2 columnVector = this[column];
                columnVector[row] = value;
                this[column] = columnVector;
            }
        }

        /// <summary>
        /// Returns the inverse of the transform, under the assumption that
        /// the basis is invertible (must have non-zero determinant).
        /// </summary>
        /// <seealso cref="Inverse"/>
        /// <returns>The inverse transformation matrix.</returns>
        public readonly Transform2D AffineInverse()
        {
            real_t det = Determinant();

            if (det == 0)
                throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted.");

            Transform2D inv = this;

            inv[0, 0] = this[1, 1];
            inv[1, 1] = this[0, 0];

            real_t detInv = 1.0f / det;

            inv[0] *= new Vector2(detInv, -detInv);
            inv[1] *= new Vector2(-detInv, detInv);

            inv[2] = inv.BasisXform(-inv[2]);

            return inv;
        }

        /// <summary>
        /// Returns the determinant of the basis matrix. If the basis is
        /// uniformly scaled, then its determinant equals the square of the
        /// scale factor.
        ///
        /// A negative determinant means the basis was flipped, so one part of
        /// the scale is negative. A zero determinant means the basis isn't
        /// invertible, and is usually considered invalid.
        /// </summary>
        /// <returns>The determinant of the basis matrix.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public readonly real_t Determinant()
        {
            return (X.X * Y.Y) - (X.Y * Y.X);
        }

        /// <summary>
        /// Returns a vector transformed (multiplied) by the basis matrix.
        /// This method does not account for translation (the <see cref="Origin"/> vector).
        /// </summary>
        /// <seealso cref="BasisXformInv(Vector2)"/>
        /// <param name="v">A vector to transform.</param>
        /// <returns>The transformed vector.</returns>
        public readonly Vector2 BasisXform(Vector2 v)
        {
            return new Vector2(Tdotx(v), Tdoty(v));
        }

        /// <summary>
        /// Returns a vector transformed (multiplied) by the inverse basis matrix,
        /// under the assumption that the basis is orthonormal (i.e. rotation/reflection
        /// is fine, scaling/skew is not).
        /// This method does not account for translation (the <see cref="Origin"/> vector).
        /// <c>transform.BasisXformInv(vector)</c> is equivalent to <c>transform.Inverse().BasisXform(vector)</c>. See <see cref="Inverse"/>.
        /// For non-orthonormal transforms (e.g. with scaling) <c>transform.AffineInverse().BasisXform(vector)</c> can be used instead. See <see cref="AffineInverse"/>.
        /// </summary>
        /// <seealso cref="BasisXform(Vector2)"/>
        /// <param name="v">A vector to inversely transform.</param>
        /// <returns>The inversely transformed vector.</returns>
        public readonly Vector2 BasisXformInv(Vector2 v)
        {
            return new Vector2(X.Dot(v), Y.Dot(v));
        }

        /// <summary>
        /// Interpolates this transform to the other <paramref name="transform"/> by <paramref name="weight"/>.
        /// </summary>
        /// <param name="transform">The other transform.</param>
        /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
        /// <returns>The interpolated transform.</returns>
        public readonly Transform2D InterpolateWith(Transform2D transform, real_t weight)
        {
            return new Transform2D
            (
                Mathf.LerpAngle(Rotation, transform.Rotation, weight),
                Scale.Lerp(transform.Scale, weight),
                Mathf.LerpAngle(Skew, transform.Skew, weight),
                Origin.Lerp(transform.Origin, weight)
            );
        }

        /// <summary>
        /// Returns the inverse of the transform, under the assumption that
        /// the transformation basis is orthonormal (i.e. rotation/reflection
        /// is fine, scaling/skew is not). Use <see cref="AffineInverse"/> for
        /// non-orthonormal transforms (e.g. with scaling).
        /// </summary>
        /// <returns>The inverse matrix.</returns>
        public readonly Transform2D Inverse()
        {
            Transform2D inv = this;

            // Swap
            inv.X.Y = Y.X;
            inv.Y.X = X.Y;

            inv.Origin = inv.BasisXform(-inv.Origin);

            return inv;
        }

        /// <summary>
        /// Returns <see langword="true"/> if this transform is finite, by calling
        /// <see cref="Mathf.IsFinite(real_t)"/> on each component.
        /// </summary>
        /// <returns>Whether this vector is finite or not.</returns>
        public readonly bool IsFinite()
        {
            return X.IsFinite() && Y.IsFinite() && Origin.IsFinite();
        }

        /// <summary>
        /// Returns the transform with the basis orthogonal (90 degrees),
        /// and normalized axis vectors (scale of 1 or -1).
        /// </summary>
        /// <returns>The orthonormalized transform.</returns>
        public readonly Transform2D Orthonormalized()
        {
            Transform2D ortho = this;

            Vector2 orthoX = ortho.X;
            Vector2 orthoY = ortho.Y;

            orthoX.Normalize();
            orthoY = orthoY - orthoX * orthoX.Dot(orthoY);
            orthoY.Normalize();

            ortho.X = orthoX;
            ortho.Y = orthoY;

            return ortho;
        }

        /// <summary>
        /// Rotates the transform by <paramref name="angle"/> (in radians).
        /// The operation is done in the parent/global frame, equivalent to
        /// multiplying the matrix from the left.
        /// </summary>
        /// <param name="angle">The angle to rotate, in radians.</param>
        /// <returns>The rotated transformation matrix.</returns>
        public readonly Transform2D Rotated(real_t angle)
        {
            return new Transform2D(angle, new Vector2()) * this;
        }

        /// <summary>
        /// Rotates the transform by <paramref name="angle"/> (in radians).
        /// The operation is done in the local frame, equivalent to
        /// multiplying the matrix from the right.
        /// </summary>
        /// <param name="angle">The angle to rotate, in radians.</param>
        /// <returns>The rotated transformation matrix.</returns>
        public readonly Transform2D RotatedLocal(real_t angle)
        {
            return this * new Transform2D(angle, new Vector2());
        }

        /// <summary>
        /// Scales the transform by the given scaling factor.
        /// The operation is done in the parent/global frame, equivalent to
        /// multiplying the matrix from the left.
        /// </summary>
        /// <param name="scale">The scale to introduce.</param>
        /// <returns>The scaled transformation matrix.</returns>
        public readonly Transform2D Scaled(Vector2 scale)
        {
            Transform2D copy = this;
            copy.X *= scale;
            copy.Y *= scale;
            copy.Origin *= scale;
            return copy;
        }

        /// <summary>
        /// Scales the transform by the given scaling factor.
        /// The operation is done in the local frame, equivalent to
        /// multiplying the matrix from the right.
        /// </summary>
        /// <param name="scale">The scale to introduce.</param>
        /// <returns>The scaled transformation matrix.</returns>
        public readonly Transform2D ScaledLocal(Vector2 scale)
        {
            Transform2D copy = this;
            copy.X *= scale;
            copy.Y *= scale;
            return copy;
        }

        private readonly real_t Tdotx(Vector2 with)
        {
            return (this[0, 0] * with[0]) + (this[1, 0] * with[1]);
        }

        private readonly real_t Tdoty(Vector2 with)
        {
            return (this[0, 1] * with[0]) + (this[1, 1] * with[1]);
        }

        /// <summary>
        /// Translates the transform by the given <paramref name="offset"/>.
        /// The operation is done in the parent/global frame, equivalent to
        /// multiplying the matrix from the left.
        /// </summary>
        /// <param name="offset">The offset to translate by.</param>
        /// <returns>The translated matrix.</returns>
        public readonly Transform2D Translated(Vector2 offset)
        {
            Transform2D copy = this;
            copy.Origin += offset;
            return copy;
        }

        /// <summary>
        /// Translates the transform by the given <paramref name="offset"/>.
        /// The operation is done in the local frame, equivalent to
        /// multiplying the matrix from the right.
        /// </summary>
        /// <param name="offset">The offset to translate by.</param>
        /// <returns>The translated matrix.</returns>
        public readonly Transform2D TranslatedLocal(Vector2 offset)
        {
            Transform2D copy = this;
            copy.Origin += copy.BasisXform(offset);
            return copy;
        }

        // Constants
        private static readonly Transform2D _identity = new Transform2D(1, 0, 0, 1, 0, 0);
        private static readonly Transform2D _flipX = new Transform2D(-1, 0, 0, 1, 0, 0);
        private static readonly Transform2D _flipY = new Transform2D(1, 0, 0, -1, 0, 0);

        /// <summary>
        /// The identity transform, with no translation, rotation, or scaling applied.
        /// This is used as a replacement for <c>Transform2D()</c> in GDScript.
        /// Do not use <c>new Transform2D()</c> with no arguments in C#, because it sets all values to zero.
        /// </summary>
        /// <value>Equivalent to <c>new Transform2D(Vector2.Right, Vector2.Down, Vector2.Zero)</c>.</value>
        public static Transform2D Identity { get { return _identity; } }
        /// <summary>
        /// The transform that will flip something along the X axis.
        /// </summary>
        /// <value>Equivalent to <c>new Transform2D(Vector2.Left, Vector2.Down, Vector2.Zero)</c>.</value>
        public static Transform2D FlipX { get { return _flipX; } }
        /// <summary>
        /// The transform that will flip something along the Y axis.
        /// </summary>
        /// <value>Equivalent to <c>new Transform2D(Vector2.Right, Vector2.Up, Vector2.Zero)</c>.</value>
        public static Transform2D FlipY { get { return _flipY; } }

        /// <summary>
        /// Constructs a transformation matrix from 3 vectors (matrix columns).
        /// </summary>
        /// <param name="xAxis">The X vector, or column index 0.</param>
        /// <param name="yAxis">The Y vector, or column index 1.</param>
        /// <param name="originPos">The origin vector, or column index 2.</param>
        public Transform2D(Vector2 xAxis, Vector2 yAxis, Vector2 originPos)
        {
            X = xAxis;
            Y = yAxis;
            Origin = originPos;
        }

        /// <summary>
        /// Constructs a transformation matrix from the given components.
        /// Arguments are named such that xy is equal to calling <c>X.Y</c>.
        /// </summary>
        /// <param name="xx">The X component of the X column vector, accessed via <c>t.X.X</c> or <c>[0][0]</c>.</param>
        /// <param name="xy">The Y component of the X column vector, accessed via <c>t.X.Y</c> or <c>[0][1]</c>.</param>
        /// <param name="yx">The X component of the Y column vector, accessed via <c>t.Y.X</c> or <c>[1][0]</c>.</param>
        /// <param name="yy">The Y component of the Y column vector, accessed via <c>t.Y.Y</c> or <c>[1][1]</c>.</param>
        /// <param name="ox">The X component of the origin vector, accessed via <c>t.Origin.X</c> or <c>[2][0]</c>.</param>
        /// <param name="oy">The Y component of the origin vector, accessed via <c>t.Origin.Y</c> or <c>[2][1]</c>.</param>
        public Transform2D(real_t xx, real_t xy, real_t yx, real_t yy, real_t ox, real_t oy)
        {
            X = new Vector2(xx, xy);
            Y = new Vector2(yx, yy);
            Origin = new Vector2(ox, oy);
        }

        /// <summary>
        /// Constructs a transformation matrix from a <paramref name="rotation"/> value and
        /// <paramref name="origin"/> vector.
        /// </summary>
        /// <param name="rotation">The rotation of the new transform, in radians.</param>
        /// <param name="origin">The origin vector, or column index 2.</param>
        public Transform2D(real_t rotation, Vector2 origin)
        {
            (real_t sin, real_t cos) = Mathf.SinCos(rotation);
            X.X = Y.Y = cos;
            X.Y = Y.X = sin;
            Y.X *= -1;
            Origin = origin;
        }

        /// <summary>
        /// Constructs a transformation matrix from a <paramref name="rotation"/> value,
        /// <paramref name="scale"/> vector, <paramref name="skew"/> value, and
        /// <paramref name="origin"/> vector.
        /// </summary>
        /// <param name="rotation">The rotation of the new transform, in radians.</param>
        /// <param name="scale">The scale of the new transform.</param>
        /// <param name="skew">The skew of the new transform, in radians.</param>
        /// <param name="origin">The origin vector, or column index 2.</param>
        public Transform2D(real_t rotation, Vector2 scale, real_t skew, Vector2 origin)
        {
            (real_t rotationSin, real_t rotationCos) = Mathf.SinCos(rotation);
            (real_t rotationSkewSin, real_t rotationSkewCos) = Mathf.SinCos(rotation + skew);
            X.X = rotationCos * scale.X;
            Y.Y = rotationSkewCos * scale.Y;
            Y.X = -rotationSkewSin * scale.Y;
            X.Y = rotationSin * scale.X;
            Origin = origin;
        }

        /// <summary>
        /// Composes these two transformation matrices by multiplying them
        /// together. This has the effect of transforming the second transform
        /// (the child) by the first transform (the parent).
        /// </summary>
        /// <param name="left">The parent transform.</param>
        /// <param name="right">The child transform.</param>
        /// <returns>The composed transform.</returns>
        public static Transform2D operator *(Transform2D left, Transform2D right)
        {
            left.Origin = left * right.Origin;

            real_t x0 = left.Tdotx(right.X);
            real_t x1 = left.Tdoty(right.X);
            real_t y0 = left.Tdotx(right.Y);
            real_t y1 = left.Tdoty(right.Y);

            left.X.X = x0;
            left.X.Y = x1;
            left.Y.X = y0;
            left.Y.Y = y1;

            return left;
        }

        /// <summary>
        /// Returns a Vector2 transformed (multiplied) by the transformation matrix.
        /// </summary>
        /// <param name="transform">The transformation to apply.</param>
        /// <param name="vector">A Vector2 to transform.</param>
        /// <returns>The transformed Vector2.</returns>
        public static Vector2 operator *(Transform2D transform, Vector2 vector)
        {
            return new Vector2(transform.Tdotx(vector), transform.Tdoty(vector)) + transform.Origin;
        }

        /// <summary>
        /// Returns a Vector2 transformed (multiplied) by the inverse transformation matrix,
        /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection
        /// is fine, scaling/skew is not).
        /// <c>vector * transform</c> is equivalent to <c>transform.Inverse() * vector</c>. See <see cref="Inverse"/>.
        /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * vector</c> can be used instead. See <see cref="AffineInverse"/>.
        /// </summary>
        /// <param name="vector">A Vector2 to inversely transform.</param>
        /// <param name="transform">The transformation to apply.</param>
        /// <returns>The inversely transformed Vector2.</returns>
        public static Vector2 operator *(Vector2 vector, Transform2D transform)
        {
            Vector2 vInv = vector - transform.Origin;
            return new Vector2(transform.X.Dot(vInv), transform.Y.Dot(vInv));
        }

        /// <summary>
        /// Returns a Rect2 transformed (multiplied) by the transformation matrix.
        /// </summary>
        /// <param name="transform">The transformation to apply.</param>
        /// <param name="rect">A Rect2 to transform.</param>
        /// <returns>The transformed Rect2.</returns>
        public static Rect2 operator *(Transform2D transform, Rect2 rect)
        {
            Vector2 pos = transform * rect.Position;
            Vector2 toX = transform.X * rect.Size.X;
            Vector2 toY = transform.Y * rect.Size.Y;

            return new Rect2(pos, new Vector2()).Expand(pos + toX).Expand(pos + toY).Expand(pos + toX + toY);
        }

        /// <summary>
        /// Returns a Rect2 transformed (multiplied) by the inverse transformation matrix,
        /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection
        /// is fine, scaling/skew is not).
        /// <c>rect * transform</c> is equivalent to <c>transform.Inverse() * rect</c>. See <see cref="Inverse"/>.
        /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * rect</c> can be used instead. See <see cref="AffineInverse"/>.
        /// </summary>
        /// <param name="rect">A Rect2 to inversely transform.</param>
        /// <param name="transform">The transformation to apply.</param>
        /// <returns>The inversely transformed Rect2.</returns>
        public static Rect2 operator *(Rect2 rect, Transform2D transform)
        {
            Vector2 pos = rect.Position * transform;
            Vector2 to1 = new Vector2(rect.Position.X, rect.Position.Y + rect.Size.Y) * transform;
            Vector2 to2 = new Vector2(rect.Position.X + rect.Size.X, rect.Position.Y + rect.Size.Y) * transform;
            Vector2 to3 = new Vector2(rect.Position.X + rect.Size.X, rect.Position.Y) * transform;

            return new Rect2(pos, new Vector2()).Expand(to1).Expand(to2).Expand(to3);
        }

        /// <summary>
        /// Returns a copy of the given Vector2[] transformed (multiplied) by the transformation matrix.
        /// </summary>
        /// <param name="transform">The transformation to apply.</param>
        /// <param name="array">A Vector2[] to transform.</param>
        /// <returns>The transformed copy of the Vector2[].</returns>
        public static Vector2[] operator *(Transform2D transform, Vector2[] array)
        {
            Vector2[] newArray = new Vector2[array.Length];

            for (int i = 0; i < array.Length; i++)
            {
                newArray[i] = transform * array[i];
            }

            return newArray;
        }

        /// <summary>
        /// Returns a copy of the given Vector2[] transformed (multiplied) by the inverse transformation matrix,
        /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection
        /// is fine, scaling/skew is not).
        /// <c>array * transform</c> is equivalent to <c>transform.Inverse() * array</c>. See <see cref="Inverse"/>.
        /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * array</c> can be used instead. See <see cref="AffineInverse"/>.
        /// </summary>
        /// <param name="array">A Vector2[] to inversely transform.</param>
        /// <param name="transform">The transformation to apply.</param>
        /// <returns>The inversely transformed copy of the Vector2[].</returns>
        public static Vector2[] operator *(Vector2[] array, Transform2D transform)
        {
            Vector2[] newArray = new Vector2[array.Length];

            for (int i = 0; i < array.Length; i++)
            {
                newArray[i] = array[i] * transform;
            }

            return newArray;
        }

        /// <summary>
        /// Returns <see langword="true"/> if the transforms are exactly equal.
        /// Note: Due to floating-point precision errors, consider using
        /// <see cref="IsEqualApprox"/> instead, which is more reliable.
        /// </summary>
        /// <param name="left">The left transform.</param>
        /// <param name="right">The right transform.</param>
        /// <returns>Whether or not the transforms are exactly equal.</returns>
        public static bool operator ==(Transform2D left, Transform2D right)
        {
            return left.Equals(right);
        }

        /// <summary>
        /// Returns <see langword="true"/> if the transforms are not equal.
        /// Note: Due to floating-point precision errors, consider using
        /// <see cref="IsEqualApprox"/> instead, which is more reliable.
        /// </summary>
        /// <param name="left">The left transform.</param>
        /// <param name="right">The right transform.</param>
        /// <returns>Whether or not the transforms are not equal.</returns>
        public static bool operator !=(Transform2D left, Transform2D right)
        {
            return !left.Equals(right);
        }

        /// <summary>
        /// Returns <see langword="true"/> if the transform is exactly equal
        /// to the given object (<paramref name="obj"/>).
        /// Note: Due to floating-point precision errors, consider using
        /// <see cref="IsEqualApprox"/> instead, which is more reliable.
        /// </summary>
        /// <param name="obj">The object to compare with.</param>
        /// <returns>Whether or not the transform and the object are exactly equal.</returns>
        public override readonly bool Equals([NotNullWhen(true)] object? obj)
        {
            return obj is Transform2D other && Equals(other);
        }

        /// <summary>
        /// Returns <see langword="true"/> if the transforms are exactly equal.
        /// Note: Due to floating-point precision errors, consider using
        /// <see cref="IsEqualApprox"/> instead, which is more reliable.
        /// </summary>
        /// <param name="other">The other transform to compare.</param>
        /// <returns>Whether or not the matrices are exactly equal.</returns>
        public readonly bool Equals(Transform2D other)
        {
            return X.Equals(other.X) && Y.Equals(other.Y) && Origin.Equals(other.Origin);
        }

        /// <summary>
        /// Returns <see langword="true"/> if this transform and <paramref name="other"/> are approximately equal,
        /// by running <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component.
        /// </summary>
        /// <param name="other">The other transform to compare.</param>
        /// <returns>Whether or not the matrices are approximately equal.</returns>
        public readonly bool IsEqualApprox(Transform2D other)
        {
            return X.IsEqualApprox(other.X) && Y.IsEqualApprox(other.Y) && Origin.IsEqualApprox(other.Origin);
        }

        /// <summary>
        /// Serves as the hash function for <see cref="Transform2D"/>.
        /// </summary>
        /// <returns>A hash code for this transform.</returns>
        public override readonly int GetHashCode()
        {
            return HashCode.Combine(X, Y, Origin);
        }

        /// <summary>
        /// Converts this <see cref="Transform2D"/> to a string.
        /// </summary>
        /// <returns>A string representation of this transform.</returns>
        public override readonly string ToString() => ToString(null);

        /// <summary>
        /// Converts this <see cref="Transform2D"/> to a string with the given <paramref name="format"/>.
        /// </summary>
        /// <returns>A string representation of this transform.</returns>
        public readonly string ToString(string? format)
        {
            return $"[X: {X.ToString(format)}, Y: {Y.ToString(format)}, O: {Origin.ToString(format)}]";
        }
    }
}