#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 { /// /// 3×4 matrix (3 rows, 4 columns) used for 3D linear transformations. /// It can represent transformations such as translation, rotation, or scaling. /// It consists of a (first 3 columns) and a /// for the origin (last column). /// /// 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 Transform3D : IEquatable { /// /// The of this transform. Contains the X, Y, and Z basis /// vectors (columns 0 to 2) and is responsible for rotation and scale. /// public Basis basis; /// /// The origin vector (column 3, the fourth column). Equivalent to array index `[3]`. /// public Vector3 origin; /// /// Access whole columns in the form of Vector3. The fourth column is the origin vector. /// /// Which column vector. public Vector3 this[int column] { get { switch (column) { case 0: return basis.Column0; case 1: return basis.Column1; case 2: return basis.Column2; case 3: return origin; default: throw new IndexOutOfRangeException(); } } set { switch (column) { case 0: basis.Column0 = value; return; case 1: basis.Column1 = value; return; case 2: basis.Column2 = value; return; case 3: origin = value; return; default: throw new IndexOutOfRangeException(); } } } /// /// Access matrix elements in column-major order. The fourth 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 { if (column == 3) { return origin[row]; } return basis[column, row]; } set { if (column == 3) { origin[row] = value; return; } basis[column, row] = value; } } /// /// Returns the inverse of the transform, under the assumption that /// the transformation is composed of rotation, scaling, and translation. /// /// The inverse transformation matrix. public Transform3D AffineInverse() { Basis basisInv = basis.Inverse(); return new Transform3D(basisInv, basisInv.Xform(-origin)); } /// /// 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 Transform3D InterpolateWith(Transform3D transform, real_t weight) { /* not sure if very "efficient" but good enough? */ Vector3 sourceScale = basis.Scale; Quaternion sourceRotation = basis.RotationQuaternion(); Vector3 sourceLocation = origin; Vector3 destinationScale = transform.basis.Scale; Quaternion destinationRotation = transform.basis.RotationQuaternion(); Vector3 destinationLocation = transform.origin; var interpolated = new Transform3D(); interpolated.basis.SetQuaternionScale(sourceRotation.Slerp(destinationRotation, weight).Normalized(), sourceScale.Lerp(destinationScale, weight)); interpolated.origin = sourceLocation.Lerp(destinationLocation, weight); return interpolated; } /// /// 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 Transform3D Inverse() { Basis basisTr = basis.Transposed(); return new Transform3D(basisTr, basisTr.Xform(-origin)); } /// /// Returns a copy of the transform rotated such that its /// -Z axis (forward) points towards the target position. /// /// The transform will first be rotated around the given up vector, /// and then fully aligned to the target by a further rotation around /// an axis perpendicular to both the target and up vectors. /// /// Operations take place in global space. /// /// The object to look at. /// The relative up direction /// The resulting transform. public Transform3D LookingAt(Vector3 target, Vector3 up) { var t = this; t.SetLookAt(origin, target, up); return t; } /// /// Returns the transform with the basis orthogonal (90 degrees), /// and normalized axis vectors (scale of 1 or -1). /// /// The orthonormalized transform. public Transform3D Orthonormalized() { return new Transform3D(basis.Orthonormalized(), origin); } /// /// Rotates the transform around the given `axis` by `phi` (in radians), /// using matrix multiplication. The axis must be a normalized vector. /// /// The axis to rotate around. Must be normalized. /// The angle to rotate, in radians. /// The rotated transformation matrix. public Transform3D Rotated(Vector3 axis, real_t phi) { return new Transform3D(new Basis(axis, phi), new Vector3()) * this; } /// /// Scales the transform by the given 3D scaling factor, using matrix multiplication. /// /// The scale to introduce. /// The scaled transformation matrix. public Transform3D Scaled(Vector3 scale) { return new Transform3D(basis.Scaled(scale), origin * scale); } private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up) { // Make rotation matrix // Z vector Vector3 column2 = eye - target; column2.Normalize(); Vector3 column1 = up; Vector3 column0 = column1.Cross(column2); // Recompute Y = Z cross X column1 = column2.Cross(column0); column0.Normalize(); column1.Normalize(); basis = new Basis(column0, column1, column2); origin = eye; } /// /// 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 Transform3D Translated(Vector3 offset) { return new Transform3D(basis, new Vector3 ( origin[0] += basis.Row0.Dot(offset), origin[1] += basis.Row1.Dot(offset), origin[2] += basis.Row2.Dot(offset) )); } /// /// Returns a vector transformed (multiplied) by this transformation matrix. /// /// A vector to transform. /// The transformed vector. public Vector3 Xform(Vector3 v) { return new Vector3 ( basis.Row0.Dot(v) + origin.x, basis.Row1.Dot(v) + origin.y, basis.Row2.Dot(v) + origin.z ); } /// /// Returns a vector transformed (multiplied) by the transposed transformation matrix. /// /// Note: This results in a multiplication by the inverse of the /// transformation matrix only if it represents a rotation-reflection. /// /// A vector to inversely transform. /// The inversely transformed vector. public Vector3 XformInv(Vector3 v) { Vector3 vInv = v - origin; return new Vector3 ( basis.Row0[0] * vInv.x + basis.Row1[0] * vInv.y + basis.Row2[0] * vInv.z, basis.Row0[1] * vInv.x + basis.Row1[1] * vInv.y + basis.Row2[1] * vInv.z, basis.Row0[2] * vInv.x + basis.Row1[2] * vInv.y + basis.Row2[2] * vInv.z ); } // Constants private static readonly Transform3D _identity = new Transform3D(Basis.Identity, Vector3.Zero); private static readonly Transform3D _flipX = new Transform3D(new Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1), Vector3.Zero); private static readonly Transform3D _flipY = new Transform3D(new Basis(1, 0, 0, 0, -1, 0, 0, 0, 1), Vector3.Zero); private static readonly Transform3D _flipZ = new Transform3D(new Basis(1, 0, 0, 0, 1, 0, 0, 0, -1), Vector3.Zero); /// /// The identity transform, with no translation, rotation, or scaling applied. /// This is used as a replacement for `Transform()` in GDScript. /// Do not use `new Transform()` with no arguments in C#, because it sets all values to zero. /// /// Equivalent to `new Transform(Vector3.Right, Vector3.Up, Vector3.Back, Vector3.Zero)`. public static Transform3D Identity { get { return _identity; } } /// /// The transform that will flip something along the X axis. /// /// Equivalent to `new Transform(Vector3.Left, Vector3.Up, Vector3.Back, Vector3.Zero)`. public static Transform3D FlipX { get { return _flipX; } } /// /// The transform that will flip something along the Y axis. /// /// Equivalent to `new Transform(Vector3.Right, Vector3.Down, Vector3.Back, Vector3.Zero)`. public static Transform3D FlipY { get { return _flipY; } } /// /// The transform that will flip something along the Z axis. /// /// Equivalent to `new Transform(Vector3.Right, Vector3.Up, Vector3.Forward, Vector3.Zero)`. public static Transform3D FlipZ { get { return _flipZ; } } /// /// Constructs a transformation matrix from 4 vectors (matrix columns). /// /// The X vector, or column index 0. /// The Y vector, or column index 1. /// The Z vector, or column index 2. /// The origin vector, or column index 3. public Transform3D(Vector3 column0, Vector3 column1, Vector3 column2, Vector3 origin) { basis = new Basis(column0, column1, column2); this.origin = origin; } /// /// Constructs a transformation matrix from the given quaternion and origin vector. /// /// The to create the basis from. /// The origin vector, or column index 3. public Transform3D(Quaternion quaternion, Vector3 origin) { basis = new Basis(quaternion); this.origin = origin; } /// /// Constructs a transformation matrix from the given basis and origin vector. /// /// The to create the basis from. /// The origin vector, or column index 3. public Transform3D(Basis basis, Vector3 origin) { this.basis = basis; this.origin = origin; } public static Transform3D operator *(Transform3D left, Transform3D right) { left.origin = left.Xform(right.origin); left.basis *= right.basis; return left; } public static bool operator ==(Transform3D left, Transform3D right) { return left.Equals(right); } public static bool operator !=(Transform3D left, Transform3D right) { return !left.Equals(right); } public override bool Equals(object obj) { if (obj is Transform3D) { return Equals((Transform3D)obj); } return false; } public bool Equals(Transform3D other) { return basis.Equals(other.basis) && 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(Transform3D other) { return basis.IsEqualApprox(other.basis) && origin.IsEqualApprox(other.origin); } public override int GetHashCode() { return basis.GetHashCode() ^ origin.GetHashCode(); } public override string ToString() { return "[X: " + basis.x.ToString() + ", Y: " + basis.y.ToString() + ", Z: " + basis.z.ToString() + ", O: " + origin.ToString() + "]"; } public string ToString(string format) { return "[X: " + basis.x.ToString(format) + ", Y: " + basis.y.ToString(format) + ", Z: " + basis.z.ToString(format) + ", O: " + origin.ToString(format) + "]"; } } }