#if REAL_T_IS_DOUBLE using real_t = System.Double; #else using real_t = System.Single; #endif using System; using System.Runtime.InteropServices; namespace Godot { /// /// 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 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 /// [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Transform2D : IEquatable { /// /// The basis matrix's X vector (column 0). Equivalent to array index `[0]`. /// /// public Vector2 x; /// /// The basis matrix's Y vector (column 1). Equivalent to array index `[1]`. /// public Vector2 y; /// /// The origin vector (column 2, the third column). Equivalent to array index `[2]`. /// The origin vector represents translation. /// public Vector2 origin; /// /// The rotation of this transformation matrix. /// /// Getting is equivalent to calling with the values of . public real_t Rotation { get { return Mathf.Atan2(x.y, x.x); } set { Vector2 scale = Scale; x.x = y.y = Mathf.Cos(value); x.y = y.x = Mathf.Sin(value); y.x *= -1; Scale = scale; } } /// /// The scale of this transformation matrix. /// /// Equivalent to the lengths of each column vector, but Y is negative if the determinant is negative. public Vector2 Scale { get { real_t detSign = Mathf.Sign(BasisDeterminant()); return new Vector2(x.Length(), detSign * y.Length()); } set { value /= Scale; // Value becomes what's called "delta_scale" in core. x *= value.x; y *= value.y; } } /// /// Access whole columns in the form of Vector2. The third column is the origin vector. /// /// Which column vector. public Vector2 this[int column] { get { switch (column) { case 0: return x; case 1: return y; case 2: return origin; default: throw new IndexOutOfRangeException(); } } set { switch (column) { case 0: x = value; return; case 1: y = value; return; case 2: origin = value; return; default: throw new IndexOutOfRangeException(); } } } /// /// Access matrix elements in column-major order. The third column is the origin vector. /// /// Which column, the matrix horizontal position. /// Which row, the matrix vertical position. public real_t this[int column, int row] { get { return this[column][row]; } set { Vector2 columnVector = this[column]; columnVector[row] = value; this[column] = columnVector; } } /// /// Returns the inverse of the transform, under the assumption that /// the transformation is composed of rotation, scaling, and translation. /// /// The inverse transformation matrix. public Transform2D AffineInverse() { real_t det = BasisDeterminant(); if (det == 0) throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted."); var inv = this; real_t temp = inv[0, 0]; inv[0, 0] = inv[1, 1]; inv[1, 1] = temp; 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; } /// /// Returns the determinant of the basis matrix. If the basis is /// uniformly scaled, its determinant is the square of the scale. /// /// A negative determinant means the Y scale is negative. /// A zero determinant means the basis isn't invertible, /// and is usually considered invalid. /// /// The determinant of the basis matrix. private real_t BasisDeterminant() { return x.x * y.y - x.y * y.x; } /// /// Returns a vector transformed (multiplied) by the basis matrix. /// This method does not account for translation (the origin vector). /// /// A vector to transform. /// The transformed vector. public Vector2 BasisXform(Vector2 v) { return new Vector2(Tdotx(v), Tdoty(v)); } /// /// Returns a vector transformed (multiplied) by the inverse basis matrix. /// This method does not account for translation (the origin vector). /// /// Note: This results in a multiplication by the inverse of the /// basis matrix only if it represents a rotation-reflection. /// /// A vector to inversely transform. /// The inversely transformed vector. public Vector2 BasisXformInv(Vector2 v) { return new Vector2(x.Dot(v), y.Dot(v)); } /// /// Interpolates this transform to the other `transform` by `weight`. /// /// The other transform. /// A value on the range of 0.0 to 1.0, representing the amount of interpolation. /// The interpolated transform. public Transform2D InterpolateWith(Transform2D transform, real_t weight) { real_t r1 = Rotation; real_t r2 = transform.Rotation; Vector2 s1 = Scale; Vector2 s2 = transform.Scale; // Slerp rotation var v1 = new Vector2(Mathf.Cos(r1), Mathf.Sin(r1)); var v2 = new Vector2(Mathf.Cos(r2), Mathf.Sin(r2)); real_t dot = v1.Dot(v2); dot = Mathf.Clamp(dot, -1.0f, 1.0f); Vector2 v; if (dot > 0.9995f) { // Linearly interpolate to avoid numerical precision issues v = v1.Lerp(v2, weight).Normalized(); } else { real_t angle = weight * Mathf.Acos(dot); Vector2 v3 = (v2 - v1 * dot).Normalized(); v = v1 * Mathf.Cos(angle) + v3 * Mathf.Sin(angle); } // Extract parameters Vector2 p1 = origin; Vector2 p2 = transform.origin; // Construct matrix var res = new Transform2D(Mathf.Atan2(v.y, v.x), p1.Lerp(p2, weight)); Vector2 scale = s1.Lerp(s2, weight); res.x *= scale; res.y *= scale; return res; } /// /// Returns the inverse of the transform, under the assumption that /// the transformation is composed of rotation and translation /// (no scaling, use for transforms with scaling). /// /// The inverse matrix. public Transform2D Inverse() { var inv = this; // Swap real_t temp = inv.x.y; inv.x.y = inv.y.x; inv.y.x = temp; inv.origin = inv.BasisXform(-inv.origin); return inv; } /// /// Returns the transform with the basis orthogonal (90 degrees), /// and normalized axis vectors (scale of 1 or -1). /// /// The orthonormalized transform. public Transform2D Orthonormalized() { var on = this; Vector2 onX = on.x; Vector2 onY = on.y; onX.Normalize(); onY = onY - onX * onX.Dot(onY); onY.Normalize(); on.x = onX; on.y = onY; return on; } /// /// Rotates the transform by `phi` (in radians), using matrix multiplication. /// /// The angle to rotate, in radians. /// The rotated transformation matrix. public Transform2D Rotated(real_t phi) { return this * new Transform2D(phi, new Vector2()); } /// /// Scales the transform by the given scaling factor, using matrix multiplication. /// /// The scale to introduce. /// The scaled transformation matrix. public Transform2D Scaled(Vector2 scale) { var copy = this; copy.x *= scale; copy.y *= scale; copy.origin *= scale; return copy; } private void ScaleBasis(Vector2 scale) { x.x *= scale.x; x.y *= scale.y; y.x *= scale.x; y.y *= scale.y; } private real_t Tdotx(Vector2 with) { return this[0, 0] * with[0] + this[1, 0] * with[1]; } private real_t Tdoty(Vector2 with) { return this[0, 1] * with[0] + this[1, 1] * with[1]; } /// /// Translates the transform by the given `offset`, /// relative to the transform's basis vectors. /// /// Unlike and , /// this does not use matrix multiplication. /// /// The offset to translate by. /// The translated matrix. public Transform2D Translated(Vector2 offset) { var copy = this; copy.origin += copy.BasisXform(offset); return copy; } /// /// Returns a vector transformed (multiplied) by this transformation matrix. /// /// A vector to transform. /// The transformed vector. public Vector2 Xform(Vector2 v) { return new Vector2(Tdotx(v), Tdoty(v)) + origin; } /// /// Returns a vector transformed (multiplied) by the inverse transformation matrix. /// /// A vector to inversely transform. /// The inversely transformed vector. public Vector2 XformInv(Vector2 v) { Vector2 vInv = v - origin; return new Vector2(x.Dot(vInv), y.Dot(vInv)); } // 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); /// /// The identity transform, with no translation, rotation, or scaling applied. /// This is used as a replacement for `Transform2D()` in GDScript. /// Do not use `new Transform2D()` with no arguments in C#, because it sets all values to zero. /// /// Equivalent to `new Transform2D(Vector2.Right, Vector2.Down, Vector2.Zero)`. public static Transform2D Identity { get { return _identity; } } /// /// The transform that will flip something along the X axis. /// /// Equivalent to `new Transform2D(Vector2.Left, Vector2.Down, Vector2.Zero)`. public static Transform2D FlipX { get { return _flipX; } } /// /// The transform that will flip something along the Y axis. /// /// Equivalent to `new Transform2D(Vector2.Right, Vector2.Up, Vector2.Zero)`. public static Transform2D FlipY { get { return _flipY; } } /// /// Constructs a transformation matrix from 3 vectors (matrix columns). /// /// The X vector, or column index 0. /// The Y vector, or column index 1. /// The origin vector, or column index 2. public Transform2D(Vector2 xAxis, Vector2 yAxis, Vector2 originPos) { x = xAxis; y = yAxis; origin = originPos; } /// /// Constructs a transformation matrix from the given components. /// Arguments are named such that xy is equal to calling x.y /// /// The X component of the X column vector, accessed via `t.x.x` or `[0][0]` /// The Y component of the X column vector, accessed via `t.x.y` or `[0][1]` /// The X component of the Y column vector, accessed via `t.y.x` or `[1][0]` /// The Y component of the Y column vector, accessed via `t.y.y` or `[1][1]` /// The X component of the origin vector, accessed via `t.origin.x` or `[2][0]` /// The Y component of the origin vector, accessed via `t.origin.y` or `[2][1]` 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); } /// /// Constructs a transformation matrix from a rotation value and origin vector. /// /// The rotation of the new transform, in radians. /// The origin vector, or column index 2. public Transform2D(real_t rot, Vector2 pos) { x.x = y.y = Mathf.Cos(rot); x.y = y.x = Mathf.Sin(rot); y.x *= -1; origin = pos; } public static Transform2D operator *(Transform2D left, Transform2D right) { left.origin = left.Xform(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; } public static bool operator ==(Transform2D left, Transform2D right) { return left.Equals(right); } public static bool operator !=(Transform2D left, Transform2D right) { return !left.Equals(right); } public override bool Equals(object obj) { return obj is Transform2D transform2D && Equals(transform2D); } public bool Equals(Transform2D other) { return x.Equals(other.x) && y.Equals(other.y) && origin.Equals(other.origin); } /// /// Returns true if this transform and `other` are approximately equal, by running /// on each component. /// /// The other transform to compare. /// Whether or not the matrices are approximately equal. public bool IsEqualApprox(Transform2D other) { return x.IsEqualApprox(other.x) && y.IsEqualApprox(other.y) && origin.IsEqualApprox(other.origin); } public override int GetHashCode() { return x.GetHashCode() ^ y.GetHashCode() ^ origin.GetHashCode(); } public override string ToString() { return "[X: " + x.ToString() + ", Y: " + y.ToString() + ", O: " + origin.ToString() + "]"; } public string ToString(string format) { return "[X: " + x.ToString(format) + ", Y: " + y.ToString(format) + ", O: " + origin.ToString(format) + "]"; } } }