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

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

#nullable enable

namespace Godot
{
    /// <summary>
    /// 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 <see cref="Godot.Basis"/> (first 3 columns) and a
    /// <see cref="Vector3"/> for the origin (last column).
    ///
    /// 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 Transform3D : IEquatable<Transform3D>
    {
        /// <summary>
        /// The <see cref="Godot.Basis"/> of this transform. Contains the X, Y, and Z basis
        /// vectors (columns 0 to 2) and is responsible for rotation and scale.
        /// </summary>
        public Basis Basis;

        /// <summary>
        /// The origin vector (column 3, the fourth column). Equivalent to array index <c>[3]</c>.
        /// </summary>
        public Vector3 Origin;

        /// <summary>
        /// Access whole columns in the form of <see cref="Vector3"/>.
        /// The fourth 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, 2 or 3.
        /// </exception>
        public Vector3 this[int column]
        {
            readonly 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 ArgumentOutOfRangeException(nameof(column));
                }
            }
            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 ArgumentOutOfRangeException(nameof(column));
                }
            }
        }

        /// <summary>
        /// Access matrix elements in column-major order.
        /// The fourth 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
            {
                if (column == 3)
                {
                    return Origin[row];
                }
                return Basis[column, row];
            }
            set
            {
                if (column == 3)
                {
                    Origin[row] = value;
                    return;
                }
                Basis[column, row] = value;
            }
        }

        /// <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 Transform3D AffineInverse()
        {
            Basis basisInv = Basis.Inverse();
            return new Transform3D(basisInv, basisInv * -Origin);
        }

        /// <summary>
        /// Returns a transform interpolated between this transform and another
        /// <paramref name="transform"/> by a given <paramref name="weight"/>
        /// (on the range of 0.0 to 1.0).
        /// </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 Transform3D InterpolateWith(Transform3D transform, real_t weight)
        {
            Vector3 sourceScale = Basis.Scale;
            Quaternion sourceRotation = Basis.GetRotationQuaternion();
            Vector3 sourceLocation = Origin;

            Vector3 destinationScale = transform.Basis.Scale;
            Quaternion destinationRotation = transform.Basis.GetRotationQuaternion();
            Vector3 destinationLocation = transform.Origin;

            var interpolated = new Transform3D();
            Quaternion quaternion = sourceRotation.Slerp(destinationRotation, weight).Normalized();
            Vector3 scale = sourceScale.Lerp(destinationScale, weight);
            interpolated.Basis.SetQuaternionScale(quaternion, scale);
            interpolated.Origin = sourceLocation.Lerp(destinationLocation, weight);

            return interpolated;
        }

        /// <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 Transform3D Inverse()
        {
            Basis basisTr = Basis.Transposed();
            return new Transform3D(basisTr, basisTr * -Origin);
        }

        /// <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 Basis.IsFinite() && Origin.IsFinite();
        }

        /// <summary>
        /// Returns a copy of the transform rotated such that the forward axis (-Z)
        /// points towards the <paramref name="target"/> position.
        /// The up axis (+Y) points as close to the <paramref name="up"/> vector
        /// as possible while staying perpendicular to the forward axis.
        /// The resulting transform is orthonormalized.
        /// The existing rotation, scale, and skew information from the original transform is discarded.
        /// The <paramref name="target"/> and <paramref name="up"/> vectors cannot be zero,
        /// cannot be parallel to each other, and are defined in global/parent space.
        /// </summary>
        /// <param name="target">The object to look at.</param>
        /// <param name="up">The relative up direction.</param>
        /// <param name="useModelFront">
        /// If true, then the model is oriented in reverse,
        /// towards the model front axis (+Z, Vector3.ModelFront),
        /// which is more useful for orienting 3D models.
        /// </param>
        /// <returns>The resulting transform.</returns>
        public readonly Transform3D LookingAt(Vector3 target, Vector3? up = null, bool useModelFront = false)
        {
            Transform3D t = this;
            t.SetLookAt(Origin, target, up ?? Vector3.Up, useModelFront);
            return t;
        }

        /// <inheritdoc cref="LookingAt(Vector3, Nullable{Vector3}, bool)"/>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public readonly Transform3D LookingAt(Vector3 target, Vector3 up)
        {
            return LookingAt(target, up, false);
        }

        /// <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 Transform3D Orthonormalized()
        {
            return new Transform3D(Basis.Orthonormalized(), Origin);
        }

        /// <summary>
        /// Rotates the transform around the given <paramref name="axis"/> by <paramref name="angle"/> (in radians).
        /// The axis must be a normalized vector.
        /// The operation is done in the parent/global frame, equivalent to
        /// multiplying the matrix from the left.
        /// </summary>
        /// <param name="axis">The axis to rotate around. Must be normalized.</param>
        /// <param name="angle">The angle to rotate, in radians.</param>
        /// <returns>The rotated transformation matrix.</returns>
        public readonly Transform3D Rotated(Vector3 axis, real_t angle)
        {
            return new Transform3D(new Basis(axis, angle), new Vector3()) * this;
        }

        /// <summary>
        /// Rotates the transform around the given <paramref name="axis"/> by <paramref name="angle"/> (in radians).
        /// The axis must be a normalized vector.
        /// The operation is done in the local frame, equivalent to
        /// multiplying the matrix from the right.
        /// </summary>
        /// <param name="axis">The axis to rotate around. Must be normalized.</param>
        /// <param name="angle">The angle to rotate, in radians.</param>
        /// <returns>The rotated transformation matrix.</returns>
        public readonly Transform3D RotatedLocal(Vector3 axis, real_t angle)
        {
            Basis tmpBasis = new Basis(axis, angle);
            return new Transform3D(Basis * tmpBasis, Origin);
        }

        /// <summary>
        /// Scales the transform by the given 3D <paramref name="scale"/> 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 Transform3D Scaled(Vector3 scale)
        {
            return new Transform3D(Basis.Scaled(scale), Origin * scale);
        }

        /// <summary>
        /// Scales the transform by the given 3D <paramref name="scale"/> 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 Transform3D ScaledLocal(Vector3 scale)
        {
            Basis tmpBasis = Basis.FromScale(scale);
            return new Transform3D(Basis * tmpBasis, Origin);
        }

        private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up, bool useModelFront = false)
        {
            Basis = Basis.LookingAt(target - eye, up, useModelFront);
            Origin = eye;
        }

        /// <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 Transform3D Translated(Vector3 offset)
        {
            return new Transform3D(Basis, Origin + offset);
        }

        /// <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 Transform3D TranslatedLocal(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)
            ));
        }

        // 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);

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

        /// <summary>
        /// Constructs a transformation matrix from 4 vectors (matrix columns).
        /// </summary>
        /// <param name="column0">The X vector, or column index 0.</param>
        /// <param name="column1">The Y vector, or column index 1.</param>
        /// <param name="column2">The Z vector, or column index 2.</param>
        /// <param name="origin">The origin vector, or column index 3.</param>
        public Transform3D(Vector3 column0, Vector3 column1, Vector3 column2, Vector3 origin)
        {
            Basis = new Basis(column0, column1, column2);
            Origin = origin;
        }

        /// <summary>
        /// Constructs a transformation matrix from the given components.
        /// Arguments are named such that xy is equal to calling <c>Basis.X.Y</c>.
        /// </summary>
        /// <param name="xx">The X component of the X column vector, accessed via <c>t.Basis.X.X</c> or <c>[0][0]</c>.</param>
        /// <param name="yx">The X component of the Y column vector, accessed via <c>t.Basis.Y.X</c> or <c>[1][0]</c>.</param>
        /// <param name="zx">The X component of the Z column vector, accessed via <c>t.Basis.Z.X</c> or <c>[2][0]</c>.</param>
        /// <param name="xy">The Y component of the X column vector, accessed via <c>t.Basis.X.Y</c> or <c>[0][1]</c>.</param>
        /// <param name="yy">The Y component of the Y column vector, accessed via <c>t.Basis.Y.Y</c> or <c>[1][1]</c>.</param>
        /// <param name="zy">The Y component of the Z column vector, accessed via <c>t.Basis.Y.Y</c> or <c>[2][1]</c>.</param>
        /// <param name="xz">The Z component of the X column vector, accessed via <c>t.Basis.X.Y</c> or <c>[0][2]</c>.</param>
        /// <param name="yz">The Z component of the Y column vector, accessed via <c>t.Basis.Y.Y</c> or <c>[1][2]</c>.</param>
        /// <param name="zz">The Z component of the Z column vector, accessed via <c>t.Basis.Y.Y</c> or <c>[2][2]</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>
        /// <param name="oz">The Z component of the origin vector, accessed via <c>t.Origin.Z</c> or <c>[2][2]</c>.</param>
        public Transform3D(real_t xx, real_t yx, real_t zx, real_t xy, real_t yy, real_t zy, real_t xz, real_t yz, real_t zz, real_t ox, real_t oy, real_t oz)
        {
            Basis = new Basis(xx, yx, zx, xy, yy, zy, xz, yz, zz);
            Origin = new Vector3(ox, oy, oz);
        }

        /// <summary>
        /// Constructs a transformation matrix from the given <paramref name="basis"/> and
        /// <paramref name="origin"/> vector.
        /// </summary>
        /// <param name="basis">The <see cref="Godot.Basis"/> to create the basis from.</param>
        /// <param name="origin">The origin vector, or column index 3.</param>
        public Transform3D(Basis basis, Vector3 origin)
        {
            Basis = basis;
            Origin = origin;
        }

        /// <summary>
        /// Constructs a transformation matrix from the given <paramref name="projection"/>
        /// by trimming the last row of the projection matrix (<c>projection.X.W</c>,
        /// <c>projection.Y.W</c>, <c>projection.Z.W</c>, and <c>projection.W.W</c>
        /// are not copied over).
        /// </summary>
        /// <param name="projection">The <see cref="Projection"/> to create the transform from.</param>
        public Transform3D(Projection projection)
        {
            Basis = new Basis
            (
                projection.X.X, projection.Y.X, projection.Z.X,
                projection.X.Y, projection.Y.Y, projection.Z.Y,
                projection.X.Z, projection.Y.Z, projection.Z.Z
            );
            Origin = new Vector3
            (
                projection.W.X,
                projection.W.Y,
                projection.W.Z
            );
        }

        /// <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 Transform3D operator *(Transform3D left, Transform3D right)
        {
            left.Origin = left * right.Origin;
            left.Basis *= right.Basis;
            return left;
        }

        /// <summary>
        /// Returns a Vector3 transformed (multiplied) by the transformation matrix.
        /// </summary>
        /// <param name="transform">The transformation to apply.</param>
        /// <param name="vector">A Vector3 to transform.</param>
        /// <returns>The transformed Vector3.</returns>
        public static Vector3 operator *(Transform3D transform, Vector3 vector)
        {
            return new Vector3
            (
                transform.Basis.Row0.Dot(vector) + transform.Origin.X,
                transform.Basis.Row1.Dot(vector) + transform.Origin.Y,
                transform.Basis.Row2.Dot(vector) + transform.Origin.Z
            );
        }

        /// <summary>
        /// Returns a Vector3 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 Vector3 to inversely transform.</param>
        /// <param name="transform">The transformation to apply.</param>
        /// <returns>The inversely transformed Vector3.</returns>
        public static Vector3 operator *(Vector3 vector, Transform3D transform)
        {
            Vector3 vInv = vector - transform.Origin;

            return new Vector3
            (
                (transform.Basis.Row0[0] * vInv.X) + (transform.Basis.Row1[0] * vInv.Y) + (transform.Basis.Row2[0] * vInv.Z),
                (transform.Basis.Row0[1] * vInv.X) + (transform.Basis.Row1[1] * vInv.Y) + (transform.Basis.Row2[1] * vInv.Z),
                (transform.Basis.Row0[2] * vInv.X) + (transform.Basis.Row1[2] * vInv.Y) + (transform.Basis.Row2[2] * vInv.Z)
            );
        }

        /// <summary>
        /// Returns an AABB transformed (multiplied) by the transformation matrix.
        /// </summary>
        /// <param name="transform">The transformation to apply.</param>
        /// <param name="aabb">An AABB to transform.</param>
        /// <returns>The transformed AABB.</returns>
        public static Aabb operator *(Transform3D transform, Aabb aabb)
        {
            Vector3 min = aabb.Position;
            Vector3 max = aabb.Position + aabb.Size;

            Vector3 tmin = transform.Origin;
            Vector3 tmax = transform.Origin;
            for (int i = 0; i < 3; i++)
            {
                for (int j = 0; j < 3; j++)
                {
                    real_t e = transform.Basis[j][i] * min[j];
                    real_t f = transform.Basis[j][i] * max[j];
                    if (e < f)
                    {
                        tmin[i] += e;
                        tmax[i] += f;
                    }
                    else
                    {
                        tmin[i] += f;
                        tmax[i] += e;
                    }
                }
            }

            return new Aabb(tmin, tmax - tmin);
        }

        /// <summary>
        /// Returns an AABB 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>aabb * transform</c> is equivalent to <c>transform.Inverse() * aabb</c>. See <see cref="Inverse"/>.
        /// For transforming by inverse of an affine transformation (e.g. with scaling) <c>transform.AffineInverse() * aabb</c> can be used instead. See <see cref="AffineInverse"/>.
        /// </summary>
        /// <param name="aabb">An AABB to inversely transform.</param>
        /// <param name="transform">The transformation to apply.</param>
        /// <returns>The inversely transformed AABB.</returns>
        public static Aabb operator *(Aabb aabb, Transform3D transform)
        {
            Vector3 pos = new Vector3(aabb.Position.X + aabb.Size.X, aabb.Position.Y + aabb.Size.Y, aabb.Position.Z + aabb.Size.Z) * transform;
            Vector3 to1 = new Vector3(aabb.Position.X + aabb.Size.X, aabb.Position.Y + aabb.Size.Y, aabb.Position.Z) * transform;
            Vector3 to2 = new Vector3(aabb.Position.X + aabb.Size.X, aabb.Position.Y, aabb.Position.Z + aabb.Size.Z) * transform;
            Vector3 to3 = new Vector3(aabb.Position.X + aabb.Size.X, aabb.Position.Y, aabb.Position.Z) * transform;
            Vector3 to4 = new Vector3(aabb.Position.X, aabb.Position.Y + aabb.Size.Y, aabb.Position.Z + aabb.Size.Z) * transform;
            Vector3 to5 = new Vector3(aabb.Position.X, aabb.Position.Y + aabb.Size.Y, aabb.Position.Z) * transform;
            Vector3 to6 = new Vector3(aabb.Position.X, aabb.Position.Y, aabb.Position.Z + aabb.Size.Z) * transform;
            Vector3 to7 = new Vector3(aabb.Position.X, aabb.Position.Y, aabb.Position.Z) * transform;

            return new Aabb(pos, new Vector3()).Expand(to1).Expand(to2).Expand(to3).Expand(to4).Expand(to5).Expand(to6).Expand(to7);
        }

        /// <summary>
        /// Returns a Plane transformed (multiplied) by the transformation matrix.
        /// </summary>
        /// <param name="transform">The transformation to apply.</param>
        /// <param name="plane">A Plane to transform.</param>
        /// <returns>The transformed Plane.</returns>
        public static Plane operator *(Transform3D transform, Plane plane)
        {
            Basis bInvTrans = transform.Basis.Inverse().Transposed();

            // Transform a single point on the plane.
            Vector3 point = transform * (plane.Normal * plane.D);

            // Use inverse transpose for correct normals with non-uniform scaling.
            Vector3 normal = (bInvTrans * plane.Normal).Normalized();

            real_t d = normal.Dot(point);
            return new Plane(normal, d);
        }

        /// <summary>
        /// Returns a Plane transformed (multiplied) by the inverse transformation matrix.
        /// <c>plane * transform</c> is equivalent to <c>transform.AffineInverse() * plane</c>. See <see cref="AffineInverse"/>.
        /// </summary>
        /// <param name="plane">A Plane to inversely transform.</param>
        /// <param name="transform">The transformation to apply.</param>
        /// <returns>The inversely transformed Plane.</returns>
        public static Plane operator *(Plane plane, Transform3D transform)
        {
            Transform3D tInv = transform.AffineInverse();
            Basis bTrans = transform.Basis.Transposed();

            // Transform a single point on the plane.
            Vector3 point = tInv * (plane.Normal * plane.D);

            // Note that instead of precalculating the transpose, an alternative
            // would be to use the transpose for the basis transform.
            // However that would be less SIMD friendly (requiring a swizzle).
            // So the cost is one extra precalced value in the calling code.
            // This is probably worth it, as this could be used in bottleneck areas. And
            // where it is not a bottleneck, the non-fast method is fine.

            // Use transpose for correct normals with non-uniform scaling.
            Vector3 normal = (bTrans * plane.Normal).Normalized();

            real_t d = normal.Dot(point);
            return new Plane(normal, d);
        }

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

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

            return newArray;
        }

        /// <summary>
        /// Returns a copy of the given Vector3[] 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 Vector3[] to inversely transform.</param>
        /// <param name="transform">The transformation to apply.</param>
        /// <returns>The inversely transformed copy of the Vector3[].</returns>
        public static Vector3[] operator *(Vector3[] array, Transform3D transform)
        {
            Vector3[] newArray = new Vector3[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 ==(Transform3D left, Transform3D 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 !=(Transform3D left, Transform3D 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 Transform3D 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(Transform3D other)
        {
            return Basis.Equals(other.Basis) && Origin.Equals(other.Origin);
        }

        /// <summary>
        /// Returns <see langword="true"/> if this transform and <paramref name="other"/> are approximately equal,
        /// by running <see cref="Vector3.IsEqualApprox(Vector3)"/> 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(Transform3D other)
        {
            return Basis.IsEqualApprox(other.Basis) && Origin.IsEqualApprox(other.Origin);
        }

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

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

        /// <summary>
        /// Converts this <see cref="Transform3D"/> 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: {Basis.X.ToString(format)}, Y: {Basis.Y.ToString(format)}, Z: {Basis.Z.ToString(format)}, O: {Origin.ToString(format)}]";
        }
    }
}