< Summary

Information
Class: FixedMathSharp.Fixed4x4
Assembly: FixedMathSharp
File(s): /home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Numerics/Fixed4x4.cs
Line coverage
100%
Covered lines: 507
Uncovered lines: 0
Coverable lines: 507
Total lines: 1071
Line coverage: 100%
Branch coverage
100%
Covered branches: 108
Total branches: 108
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_IsAffine()100%66100%
get_Translation()100%11100%
get_Up()100%11100%
get_Scale()100%11100%
get_Rotation()100%11100%
get_Item(...)100%1717100%
set_Item(...)100%1717100%
GetDeterminant()100%22100%
ResetScaleToIdentity()100%11100%
SetTransform(...)100%11100%
CreateTranslation(...)100%11100%
CreateRotation(...)100%11100%
CreateScale(...)100%11100%
CreateTransform(...)100%11100%
ScaleRotateTranslate(...)100%11100%
TranslateRotateScale(...)100%11100%
ExtractTranslation(...)100%11100%
ExtractUp(...)100%11100%
ExtractScale(...)100%11100%
ExtractLossyScale(...)100%11100%
ExtractRotation(...)100%66100%
Decompose(...)100%88100%
SetTranslation(...)100%11100%
SetScale(...)100%11100%
ApplyScaleToRotation(...)100%11100%
ResetScaleToIdentity(...)100%11100%
SetGlobalScale(...)100%11100%
SetRotation(...)100%11100%
NormalizeRotationMatrix(...)100%11100%
Invert(...)100%44100%
FullInvert(...)100%22100%
TransformPoint(...)100%22100%
FullTransformPoint(...)100%22100%
InverseTransformPoint(...)100%44100%
FullInverseTransformPoint(...)100%22100%
op_UnaryNegation(...)100%11100%
op_Addition(...)100%11100%
op_Subtraction(...)100%11100%
op_Multiply(...)100%44100%
op_Equality(...)100%11100%
op_Inequality(...)100%11100%
Equals(...)100%22100%
Equals(...)100%3030100%
GetHashCode()100%11100%

File(s)

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Numerics/Fixed4x4.cs

#LineLine coverage
 1using MemoryPack;
 2using System;
 3using System.Runtime.CompilerServices;
 4using System.Text.Json.Serialization;
 5
 6namespace FixedMathSharp;
 7
 8/// <summary>
 9/// Represents a 4x4 matrix used for transformations in 3D space, including translation, rotation, scaling, and perspect
 10/// </summary>
 11/// <remarks>
 12/// A 4x4 matrix is the standard structure for 3D transformations because it can handle both linear transformations (rot
 13/// and affine transformations (translation, shearing, and perspective projections).
 14/// It is commonly used in graphics pipelines, game engines, and 3D rendering systems.
 15///
 16/// Use Cases:
 17/// - Transforming objects in 3D space (position, orientation, and size).
 18/// - Combining multiple transformations (e.g., model-view-projection matrices).
 19/// - Applying translations, which require an extra dimension for homogeneous coordinates.
 20/// - Useful in animation, physics engines, and 3D rendering for full transformation control.
 21/// </remarks>
 22[Serializable]
 23[MemoryPackable]
 24public partial struct Fixed4x4 : IEquatable<Fixed4x4>
 25{
 26    #region Static Readonly Fields
 27
 28    /// <summary>
 29    /// Returns the identity matrix (diagonal elements set to 1).
 30    /// </summary>
 31    public static readonly Fixed4x4 Identity = new Fixed4x4(
 32        Fixed64.One, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 33        Fixed64.Zero, Fixed64.One, Fixed64.Zero, Fixed64.Zero,
 34        Fixed64.Zero, Fixed64.Zero, Fixed64.One, Fixed64.Zero,
 35        Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One);
 36
 37    /// <summary>
 38    /// Returns a matrix with all elements set to zero.
 39    /// </summary>
 40    public static readonly Fixed4x4 Zero = new Fixed4x4(
 41        Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 42        Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 43        Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 44        Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero);
 45
 46    #endregion
 47
 48    #region Fields and Constants
 49
 50    [JsonInclude]
 51    [MemoryPackOrder(0)]
 52    public Fixed64 m00;
 53    [JsonInclude]
 54    [MemoryPackOrder(1)]
 55    public Fixed64 m01;
 56    [JsonInclude]
 57    [MemoryPackOrder(2)]
 58    public Fixed64 m02;
 59    [JsonInclude]
 60    [MemoryPackOrder(3)]
 61    public Fixed64 m03;
 62
 63    [JsonInclude]
 64    [MemoryPackOrder(4)]
 65    public Fixed64 m10;
 66    [JsonInclude]
 67    [MemoryPackOrder(5)]
 68    public Fixed64 m11;
 69    [JsonInclude]
 70    [MemoryPackOrder(6)]
 71    public Fixed64 m12;
 72    [JsonInclude]
 73    [MemoryPackOrder(7)]
 74    public Fixed64 m13;
 75
 76    [JsonInclude]
 77    [MemoryPackOrder(8)]
 78    public Fixed64 m20;
 79    [JsonInclude]
 80    [MemoryPackOrder(9)]
 81    public Fixed64 m21;
 82    [JsonInclude]
 83    [MemoryPackOrder(10)]
 84    public Fixed64 m22;
 85    [JsonInclude]
 86    [MemoryPackOrder(11)]
 87    public Fixed64 m23;
 88
 89    [JsonInclude]
 90    [MemoryPackOrder(12)]
 91    public Fixed64 m30;
 92    [JsonInclude]
 93    [MemoryPackOrder(13)]
 94    public Fixed64 m31;
 95    [JsonInclude]
 96    [MemoryPackOrder(14)]
 97    public Fixed64 m32;
 98    [JsonInclude]
 99    [MemoryPackOrder(15)]
 100    public Fixed64 m33;
 101
 102    #endregion
 103
 104    #region Constructors
 105
 106    /// <summary>
 107    /// Initializes a new FixedMatrix4x4 with individual elements.
 108    /// </summary>
 109    public Fixed4x4(
 110        Fixed64 m00, Fixed64 m01, Fixed64 m02, Fixed64 m03,
 111        Fixed64 m10, Fixed64 m11, Fixed64 m12, Fixed64 m13,
 112        Fixed64 m20, Fixed64 m21, Fixed64 m22, Fixed64 m23,
 113        Fixed64 m30, Fixed64 m31, Fixed64 m32, Fixed64 m33
 114    )
 120115    {
 480116        this.m00 = m00; this.m01 = m01; this.m02 = m02; this.m03 = m03;
 480117        this.m10 = m10; this.m11 = m11; this.m12 = m12; this.m13 = m13;
 480118        this.m20 = m20; this.m21 = m21; this.m22 = m22; this.m23 = m23;
 480119        this.m30 = m30; this.m31 = m31; this.m32 = m32; this.m33 = m33;
 120120    }
 121
 122    #endregion
 123
 124    #region Properties
 125
 126    [JsonIgnore]
 127    [MemoryPackIgnore]
 104128    public readonly bool IsAffine => (m33 == Fixed64.One) && (m03 == Fixed64.Zero && m13 == Fixed64.Zero && m23 == Fixed
 129
 130    /// <inheritdoc cref="ExtractTranslation(Fixed4x4)" />
 131    [JsonIgnore]
 132    [MemoryPackIgnore]
 18133    public readonly Vector3d Translation => ExtractTranslation(this);
 134
 135    [JsonIgnore]
 136    [MemoryPackIgnore]
 10137    public readonly Vector3d Up => ExtractUp(this);
 138
 139    /// <inheritdoc cref="ExtractScale(Fixed4x4)" />
 140    [JsonIgnore]
 141    [MemoryPackIgnore]
 17142    public readonly Vector3d Scale => ExtractScale(this);
 143
 144    /// <inheritdoc cref="ExtractRotation(Fixed4x4)" />
 145    [JsonIgnore]
 146    [MemoryPackIgnore]
 16147    public readonly FixedQuaternion Rotation => ExtractRotation(this);
 148
 149    [JsonIgnore]
 150    [MemoryPackIgnore]
 151    public Fixed64 this[int index]
 152    {
 153        get
 18154        {
 18155            return index switch
 18156            {
 1157                0 => m00,
 1158                1 => m10,
 1159                2 => m20,
 1160                3 => m30,
 1161                4 => m01,
 1162                5 => m11,
 1163                6 => m21,
 1164                7 => m31,
 1165                8 => m02,
 1166                9 => m12,
 1167                10 => m22,
 1168                11 => m32,
 1169                12 => m03,
 1170                13 => m13,
 1171                14 => m23,
 1172                15 => m33,
 2173                _ => throw new IndexOutOfRangeException("Invalid matrix index!"),
 18174            };
 16175        }
 176        set
 18177        {
 18178            switch (index)
 179            {
 180                case 0:
 1181                    m00 = value;
 1182                    break;
 183                case 1:
 1184                    m10 = value;
 1185                    break;
 186                case 2:
 1187                    m20 = value;
 1188                    break;
 189                case 3:
 1190                    m30 = value;
 1191                    break;
 192                case 4:
 1193                    m01 = value;
 1194                    break;
 195                case 5:
 1196                    m11 = value;
 1197                    break;
 198                case 6:
 1199                    m21 = value;
 1200                    break;
 201                case 7:
 1202                    m31 = value;
 1203                    break;
 204                case 8:
 1205                    m02 = value;
 1206                    break;
 207                case 9:
 1208                    m12 = value;
 1209                    break;
 210                case 10:
 1211                    m22 = value;
 1212                    break;
 213                case 11:
 1214                    m32 = value;
 1215                    break;
 216                case 12:
 1217                    m03 = value;
 1218                    break;
 219                case 13:
 1220                    m13 = value;
 1221                    break;
 222                case 14:
 1223                    m23 = value;
 1224                    break;
 225                case 15:
 1226                    m33 = value;
 1227                    break;
 228                default:
 2229                    throw new IndexOutOfRangeException("Invalid matrix index!");
 230            }
 16231        }
 232    }
 233
 234    #endregion
 235
 236    #region Methods (Instance)
 237
 238    /// <summary>
 239    /// Calculates the determinant of a 4x4 matrix.
 240    /// </summary>
 241    public Fixed64 GetDeterminant()
 18242    {
 18243        if (IsAffine)
 11244        {
 11245            return m00 * (m11 * m22 - m12 * m21)
 11246                 - m01 * (m10 * m22 - m12 * m20)
 11247                 + m02 * (m10 * m21 - m11 * m20);
 248        }
 249
 250        // Process as full 4x4 matrix
 7251        Fixed64 minor0 = m22 * m33 - m23 * m32;
 7252        Fixed64 minor1 = m21 * m33 - m23 * m31;
 7253        Fixed64 minor2 = m21 * m32 - m22 * m31;
 7254        Fixed64 cofactor0 = m20 * m33 - m23 * m30;
 7255        Fixed64 cofactor1 = m20 * m32 - m22 * m30;
 7256        Fixed64 cofactor2 = m20 * m31 - m21 * m30;
 7257        return m00 * (m11 * minor0 - m12 * minor1 + m13 * minor2)
 7258            - m01 * (m10 * minor0 - m12 * cofactor0 + m13 * cofactor1)
 7259            + m02 * (m10 * minor1 - m11 * cofactor0 + m13 * cofactor2)
 7260            - m03 * (m10 * minor2 - m11 * cofactor1 + m12 * cofactor2);
 18261    }
 262
 263    /// <inheritdoc cref="Fixed4x4.ResetScaleToIdentity(Fixed4x4)" />
 264    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 265    public Fixed4x4 ResetScaleToIdentity()
 2266    {
 2267        return this = ResetScaleToIdentity(this);
 2268    }
 269
 270    /// <summary>
 271    /// Sets the translation, scale, and rotation components onto the matrix.
 272    /// </summary>
 273    /// <param name="translation">The translation vector.</param>
 274    /// <param name="scale">The scale vector.</param>
 275    /// <param name="rotation">The rotation quaternion.</param>
 276    public void SetTransform(Vector3d translation, FixedQuaternion rotation, Vector3d scale)
 5277    {
 5278        this = CreateTransform(translation, rotation, scale);
 5279    }
 280
 281    #endregion
 282
 283    #region Static Matrix Generators and Transformations
 284
 285    /// <summary>
 286    /// Creates a translation matrix from the specified 3-dimensional vector.
 287    /// </summary>
 288    /// <param name="position"></param>
 289    /// <returns>The translation matrix.</returns>
 290    public static Fixed4x4 CreateTranslation(Vector3d position)
 12291    {
 12292        Fixed4x4 result = default;
 12293        result.m00 = Fixed64.One;
 12294        result.m01 = Fixed64.Zero;
 12295        result.m02 = Fixed64.Zero;
 12296        result.m03 = Fixed64.Zero;
 12297        result.m10 = Fixed64.Zero;
 12298        result.m11 = Fixed64.One;
 12299        result.m12 = Fixed64.Zero;
 12300        result.m13 = Fixed64.Zero;
 12301        result.m20 = Fixed64.Zero;
 12302        result.m21 = Fixed64.Zero;
 12303        result.m22 = Fixed64.One;
 12304        result.m23 = Fixed64.Zero;
 12305        result.m30 = position.x;
 12306        result.m31 = position.y;
 12307        result.m32 = position.z;
 12308        result.m33 = Fixed64.One;
 12309        return result;
 12310    }
 311
 312    /// <summary>
 313    /// Creates a rotation matrix from a quaternion.
 314    /// </summary>
 315    /// <param name="rotation">The quaternion representing the rotation.</param>
 316    /// <returns>A 4x4 matrix representing the rotation.</returns>
 317    public static Fixed4x4 CreateRotation(FixedQuaternion rotation)
 21318    {
 21319        Fixed3x3 rotationMatrix = rotation.ToMatrix3x3();
 320
 21321        return new Fixed4x4(
 21322            rotationMatrix.m00, rotationMatrix.m01, rotationMatrix.m02, Fixed64.Zero,
 21323            rotationMatrix.m10, rotationMatrix.m11, rotationMatrix.m12, Fixed64.Zero,
 21324            rotationMatrix.m20, rotationMatrix.m21, rotationMatrix.m22, Fixed64.Zero,
 21325            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One
 21326        );
 21327    }
 328
 329    /// <summary>
 330    /// Creates a scale matrix from a 3-dimensional vector.
 331    /// </summary>
 332    /// <param name="scale">The vector representing the scale along each axis.</param>
 333    /// <returns>A 4x4 matrix representing the scale transformation.</returns>
 334    public static Fixed4x4 CreateScale(Vector3d scale)
 15335    {
 15336        return new Fixed4x4(
 15337            scale.x, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 15338            Fixed64.Zero, scale.y, Fixed64.Zero, Fixed64.Zero,
 15339            Fixed64.Zero, Fixed64.Zero, scale.z, Fixed64.Zero,
 15340            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One
 15341        );
 15342    }
 343
 344    /// <summary>
 345    /// Constructs a transformation matrix from translation, scale, and rotation.
 346    /// This method ensures that the rotation is properly normalized, applies the scale to the
 347    /// rotational basis, and sets the translation component separately.
 348    /// </summary>
 349    /// <remarks>
 350    /// - Uses a normalized rotation matrix to maintain numerical stability.
 351    /// - Applies non-uniform scaling to the rotation before setting translation.
 352    /// - Preferred when ensuring transformations remain mathematically correct.
 353    /// - If the rotation is already normalized and combined transformations are needed, consider using <see cref="Scale
 354    /// </remarks>
 355    /// <param name="translation">The translation vector.</param>
 356    /// <param name="scale">The scale vector.</param>
 357    /// <param name="rotation">The rotation quaternion.</param>
 358    /// <returns>A transformation matrix incorporating translation, rotation, and scale.</returns>
 359    public static Fixed4x4 CreateTransform(Vector3d translation, FixedQuaternion rotation, Vector3d scale)
 9360    {
 361        // Create the rotation matrix and normalize it
 9362        Fixed4x4 rotationMatrix = CreateRotation(rotation);
 9363        rotationMatrix = NormalizeRotationMatrix(rotationMatrix);
 364
 365        // Apply scale directly to the rotation matrix
 9366        rotationMatrix = ApplyScaleToRotation(rotationMatrix, scale);
 367
 368        // Apply the translation to the combined matrix
 9369        rotationMatrix = SetTranslation(rotationMatrix, translation);
 370
 9371        return rotationMatrix;
 9372    }
 373
 374    /// <summary>
 375    /// Constructs a transformation matrix from translation, rotation, and scale by multiplying
 376    /// separate matrices in the order: Scale * Rotation * Translation.
 377    /// </summary>
 378    /// <remarks>
 379    /// - This method directly multiplies the scale, rotation, and translation matrices.
 380    /// - Ensures that scale is applied first to preserve correct axis scaling.
 381    /// - Then rotation is applied so that rotation is not affected by non-uniform scaling.
 382    /// - Finally, translation moves the object to its correct world position.
 383    /// </remarks>
 384    public static Fixed4x4 ScaleRotateTranslate(Vector3d translation, FixedQuaternion rotation, Vector3d scale)
 8385    {
 386        // Create translation matrix
 8387        Fixed4x4 translationMatrix = CreateTranslation(translation);
 388
 389        // Create rotation matrix using the quaternion
 8390        Fixed4x4 rotationMatrix = CreateRotation(rotation);
 391
 392        // Create scaling matrix
 8393        Fixed4x4 scalingMatrix = CreateScale(scale);
 394
 395        // Combine all transformations
 8396        return (scalingMatrix * rotationMatrix) * translationMatrix;
 8397    }
 398
 399    /// <summary>
 400    /// Constructs a transformation matrix from translation, rotation, and scale by multiplying
 401    /// matrices in the order: Translation * Rotation * Scale (T * R * S).
 402    /// </summary>
 403    /// <remarks>
 404    /// - Use this method when transformations need to be applied **relative to an object's local origin**.
 405    /// - Example use cases include **animation systems**, **hierarchical transformations**, and **UI transformations**.
 406    /// - If you need to apply world-space transformations, use <see cref="CreateTransform"/> instead.
 407    /// </remarks>
 408    public static Fixed4x4 TranslateRotateScale(Vector3d translation, FixedQuaternion rotation, Vector3d scale)
 1409    {
 410        // Create translation matrix
 1411        Fixed4x4 translationMatrix = CreateTranslation(translation);
 412
 413        // Create rotation matrix using the quaternion
 1414        Fixed4x4 rotationMatrix = CreateRotation(rotation);
 415
 416        // Create scaling matrix
 1417        Fixed4x4 scalingMatrix = CreateScale(scale);
 418
 419        // Combine all transformations
 1420        return (translationMatrix * rotationMatrix) * scalingMatrix;
 1421    }
 422
 423    #endregion
 424
 425    #region Decomposition, Extraction, and Setters
 426
 427    /// <summary>
 428    /// Extracts the translation component from the 4x4 matrix.
 429    /// </summary>
 430    /// <param name="matrix">The matrix from which to extract the translation.</param>
 431    /// <returns>A Vector3d representing the translation component.</returns>
 432    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 433    public static Vector3d ExtractTranslation(Fixed4x4 matrix)
 18434    {
 18435        return new Vector3d(matrix.m30, matrix.m31, matrix.m32);
 18436    }
 437
 438    /// <summary>
 439    /// Extracts the up direction from the 4x4 matrix.
 440    /// </summary>
 441    /// <remarks>
 442    /// This is the surface normal if the matrix represents ground orientation.
 443    /// </remarks>
 444    /// <param name="matrix"></param>
 445    /// <returns>A <see cref="Vector3d"/> representing the up direction.</returns>
 446    public static Vector3d ExtractUp(Fixed4x4 matrix)
 10447    {
 10448        return new Vector3d(matrix.m10, matrix.m11, matrix.m12).Normalize();
 10449    }
 450
 451    /// <summary>
 452    /// Extracts the scaling factors from the matrix by calculating the magnitudes of the basis vectors (non-lossy).
 453    /// </summary>
 454    /// <returns>A Vector3d representing the precise scale along the X, Y, and Z axes.</returns>
 455    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 456    public static Vector3d ExtractScale(Fixed4x4 matrix)
 40457    {
 40458        return new Vector3d(
 40459            new Vector3d(matrix.m00, matrix.m01, matrix.m02).Magnitude,  // X scale
 40460            new Vector3d(matrix.m10, matrix.m11, matrix.m12).Magnitude,  // Y scale
 40461            new Vector3d(matrix.m20, matrix.m21, matrix.m22).Magnitude   // Z scale
 40462        );
 40463    }
 464
 465    /// <summary>
 466    /// Extracts the scaling factors from the matrix by returning the diagonal elements (lossy).
 467    /// </summary>
 468    /// <returns>A Vector3d representing the scale along X, Y, and Z axes (lossy).</returns>
 469    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 470    public static Vector3d ExtractLossyScale(Fixed4x4 matrix)
 3471    {
 3472        return new Vector3d(matrix.m00, matrix.m11, matrix.m22);
 3473    }
 474
 475    /// <summary>
 476    /// Extracts the rotation component from the 4x4 matrix by normalizing the rotation matrix.
 477    /// </summary>
 478    /// <param name="matrix">The matrix from which to extract the rotation.</param>
 479    /// <returns>A FixedQuaternion representing the rotation component.</returns>
 480    public static FixedQuaternion ExtractRotation(Fixed4x4 matrix)
 17481    {
 17482        Vector3d scale = ExtractScale(matrix);
 483
 484        // prevent divide by zero exception
 17485        Fixed64 scaleX = scale.x == Fixed64.Zero ? Fixed64.One : scale.x;
 17486        Fixed64 scaleY = scale.y == Fixed64.Zero ? Fixed64.One : scale.y;
 17487        Fixed64 scaleZ = scale.z == Fixed64.Zero ? Fixed64.One : scale.z;
 488
 17489        Fixed4x4 normalizedMatrix = new Fixed4x4(
 17490            matrix.m00 / scaleX, matrix.m01 / scaleY, matrix.m02 / scaleZ, Fixed64.Zero,
 17491            matrix.m10 / scaleX, matrix.m11 / scaleY, matrix.m12 / scaleZ, Fixed64.Zero,
 17492            matrix.m20 / scaleX, matrix.m21 / scaleY, matrix.m22 / scaleZ, Fixed64.Zero,
 17493            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One
 17494        );
 495
 17496        return FixedQuaternion.FromMatrix(normalizedMatrix);
 17497    }
 498
 499    /// <summary>
 500    /// Decomposes a 4x4 matrix into its translation, scale, and rotation components.
 501    /// </summary>
 502    /// <param name="matrix">The 4x4 matrix to decompose.</param>
 503    /// <param name="scale">The extracted scale component.</param>
 504    /// <param name="rotation">The extracted rotation component as a quaternion.</param>
 505    /// <param name="translation">The extracted translation component.</param>
 506    /// <returns>True if decomposition was successful, otherwise false.</returns>
 507    public static bool Decompose(
 508        Fixed4x4 matrix,
 509        out Vector3d scale,
 510        out FixedQuaternion rotation,
 511        out Vector3d translation)
 4512    {
 513        // Extract scale by calculating the magnitudes of the basis vectors
 4514        scale = ExtractScale(matrix);
 515
 516        // prevent divide by zero exception
 4517        scale = new Vector3d(
 4518             scale.x == Fixed64.Zero ? Fixed64.One : scale.x,
 4519             scale.y == Fixed64.Zero ? Fixed64.One : scale.y,
 4520             scale.z == Fixed64.Zero ? Fixed64.One : scale.z);
 521
 522        // normalize rotation and scaling
 4523        Fixed4x4 normalizedMatrix = ApplyScaleToRotation(matrix, Vector3d.One / scale);
 524
 525        // Extract translation
 4526        translation = new Vector3d(normalizedMatrix.m30, normalizedMatrix.m31, normalizedMatrix.m32);
 527
 528        // Check the determinant to ensure correct handedness
 4529        Fixed64 determinant = normalizedMatrix.GetDeterminant();
 4530        if (determinant < Fixed64.Zero)
 1531        {
 532            // Adjust for left-handed coordinate system by flipping one of the axes
 1533            scale.x = -scale.x;
 1534            normalizedMatrix.m00 = -normalizedMatrix.m00;
 1535            normalizedMatrix.m01 = -normalizedMatrix.m01;
 1536            normalizedMatrix.m02 = -normalizedMatrix.m02;
 1537        }
 538
 539        // Extract the rotation component from the orthogonalized matrix
 4540        rotation = FixedQuaternion.FromMatrix(normalizedMatrix);
 541
 4542        return true;
 4543    }
 544
 545    /// <summary>
 546    /// Sets the translation component of the 4x4 matrix.
 547    /// </summary>
 548    /// <param name="matrix">The matrix to modify.</param>
 549    /// <param name="translation">The new translation vector.</param>
 550    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 551    public static Fixed4x4 SetTranslation(Fixed4x4 matrix, Vector3d translation)
 10552    {
 10553        matrix.m30 = translation.x;
 10554        matrix.m31 = translation.y;
 10555        matrix.m32 = translation.z;
 10556        return matrix;
 10557    }
 558
 559    /// <summary>
 560    /// Sets the scale component of the 4x4 matrix by assigning the provided scale vector to the matrix's diagonal eleme
 561    /// </summary>
 562    /// <param name="matrix">The matrix to modify. Typically an identity or transformation matrix.</param>
 563    /// <param name="scale">The new scale vector to apply along the X, Y, and Z axes.</param>
 564    /// <remarks>
 565    /// Best used for applying scale to an identity matrix or resetting the scale on an existing matrix.
 566    /// For non-uniform scaling in combination with rotation, use <see cref="ApplyScaleToRotation"/>.
 567    /// </remarks>
 568    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 569    public static Fixed4x4 SetScale(Fixed4x4 matrix, Vector3d scale)
 1570    {
 1571        matrix.m00 = scale.x;
 1572        matrix.m11 = scale.y;
 1573        matrix.m22 = scale.z;
 1574        return matrix;
 1575    }
 576
 577    /// <summary>
 578    /// Applies non-uniform scaling to the 4x4 matrix by multiplying the scale vector with the rotation matrix's basis v
 579    /// </summary>
 580    /// <param name="matrix">The matrix to modify. Should already contain a valid rotation component.</param>
 581    /// <param name="scale">The scale vector to apply along the X, Y, and Z axes.</param>
 582    /// <remarks>
 583    /// Use this method when scaling is required in combination with an existing rotation, ensuring proper axis alignmen
 584    /// </remarks>
 585    public static Fixed4x4 ApplyScaleToRotation(Fixed4x4 matrix, Vector3d scale)
 15586    {
 587        // Scale each row of the rotation matrix
 15588        matrix.m00 *= scale.x;
 15589        matrix.m01 *= scale.x;
 15590        matrix.m02 *= scale.x;
 591
 15592        matrix.m10 *= scale.y;
 15593        matrix.m11 *= scale.y;
 15594        matrix.m12 *= scale.y;
 595
 15596        matrix.m20 *= scale.z;
 15597        matrix.m21 *= scale.z;
 15598        matrix.m22 *= scale.z;
 599
 15600        return matrix;
 15601    }
 602
 603    /// <summary>
 604    /// Resets the scaling part of the matrix to identity (1,1,1).
 605    /// </summary>
 606    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 607    public static Fixed4x4 ResetScaleToIdentity(Fixed4x4 matrix)
 2608    {
 2609        matrix.m00 = Fixed64.One;  // X scale
 2610        matrix.m11 = Fixed64.One;  // Y scale
 2611        matrix.m22 = Fixed64.One;  // Z scale
 612
 2613        return matrix;
 2614    }
 615
 616    /// <summary>
 617    /// Sets the global scale of an object using a 4x4 transformation matrix.
 618    /// </summary>
 619    /// <param name="matrix">The transformation matrix representing the object's global state.</param>
 620    /// <param name="globalScale">The desired global scale as a vector.</param>
 621    /// <remarks>
 622    /// The method extracts the current global scale from the matrix and computes the new local scale
 623    /// by dividing the desired global scale by the current global scale.
 624    /// The new local scale is then applied to the matrix.
 625    /// </remarks>
 626    public static Fixed4x4 SetGlobalScale(Fixed4x4 matrix, Vector3d globalScale)
 2627    {
 628        // normalize the matrix to avoid drift in the rotation component
 2629        matrix = NormalizeRotationMatrix(matrix);
 630
 631        // Reset the local scaling portion of the matrix
 2632        matrix.ResetScaleToIdentity();
 633
 634        // Compute the new local scale by dividing the desired global scale by the current scale (which was reset to (1,
 2635        Vector3d newLocalScale = new Vector3d(
 2636           globalScale.x / Fixed64.One,
 2637           globalScale.y / Fixed64.One,
 2638           globalScale.z / Fixed64.One
 2639        );
 640
 641        // Apply the new local scale directly to the matrix
 2642        return ApplyScaleToRotation(matrix, newLocalScale);
 2643    }
 644
 645    /// <summary>
 646    /// Replaces the rotation component of the 4x4 matrix using the provided quaternion, without affecting the translati
 647    /// </summary>
 648    /// <param name="matrix">The matrix to modify. The rotation will replace the upper-left 3x3 portion of the matrix.</
 649    /// <param name="rotation">The quaternion representing the new rotation to apply.</param>
 650    /// <remarks>
 651    /// This method preserves the matrix's translation component. For complete transformation updates, use <see cref="Se
 652    /// </remarks>
 653    public static Fixed4x4 SetRotation(Fixed4x4 matrix, FixedQuaternion rotation)
 2654    {
 2655        Fixed3x3 rotationMatrix = rotation.ToMatrix3x3();
 656
 2657        Vector3d scale = ExtractScale(matrix);
 658
 659        // Apply rotation to the upper-left 3x3 matrix
 660
 2661        matrix.m00 = rotationMatrix.m00 * scale.x;
 2662        matrix.m01 = rotationMatrix.m01 * scale.x;
 2663        matrix.m02 = rotationMatrix.m02 * scale.x;
 664
 2665        matrix.m10 = rotationMatrix.m10 * scale.y;
 2666        matrix.m11 = rotationMatrix.m11 * scale.y;
 2667        matrix.m12 = rotationMatrix.m12 * scale.y;
 668
 2669        matrix.m20 = rotationMatrix.m20 * scale.z;
 2670        matrix.m21 = rotationMatrix.m21 * scale.z;
 2671        matrix.m22 = rotationMatrix.m22 * scale.z;
 672
 2673        return matrix;
 2674    }
 675
 676    /// <summary>
 677    /// Normalizes the rotation component of a 4x4 matrix by ensuring the basis vectors are orthogonal and unit length.
 678    /// </summary>
 679    /// <remarks>
 680    /// This method recalculates the X, Y, and Z basis vectors from the upper-left 3x3 portion of the matrix, ensuring t
 681    /// The remaining components of the matrix are reset to maintain a valid transformation structure.
 682    ///
 683    /// Use Cases:
 684    /// - Ensuring the rotation component remains stable and accurate after multiple transformations.
 685    /// - Used in 3D transformations to prevent numerical drift from affecting the orientation over time.
 686    /// - Essential for cases where precise orientation is required, such as animations or physics simulations.
 687    /// </remarks>
 688    public static Fixed4x4 NormalizeRotationMatrix(Fixed4x4 matrix)
 12689    {
 12690        Vector3d basisX = new Vector3d(matrix.m00, matrix.m01, matrix.m02).Normalize();
 12691        Vector3d basisY = new Vector3d(matrix.m10, matrix.m11, matrix.m12).Normalize();
 12692        Vector3d basisZ = new Vector3d(matrix.m20, matrix.m21, matrix.m22).Normalize();
 693
 12694        return new Fixed4x4(
 12695            basisX.x, basisX.y, basisX.z, Fixed64.Zero,
 12696            basisY.x, basisY.y, basisY.z, Fixed64.Zero,
 12697            basisZ.x, basisZ.y, basisZ.z, Fixed64.Zero,
 12698            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One
 12699        );
 12700    }
 701
 702    #endregion
 703
 704    #region Static Matrix Operators
 705
 706    /// <summary>
 707    /// Inverts the matrix if it is invertible (i.e., if the determinant is not zero).
 708    /// </summary>
 709    /// <remarks>
 710    /// To Invert a FixedMatrix4x4, we need to calculate the inverse for each element.
 711    /// This involves computing the cofactor for each element,
 712    /// which is the determinant of the submatrix when the row and column of that element are removed,
 713    /// multiplied by a sign based on the element's position.
 714    /// After computing all cofactors, the result is transposed to get the inverse matrix.
 715    /// </remarks>
 716    public static bool Invert(Fixed4x4 matrix, out Fixed4x4 result)
 11717    {
 11718        if (!matrix.IsAffine)
 6719            return FullInvert(matrix, out result);
 720
 5721        Fixed64 det = matrix.GetDeterminant();
 722
 5723        if (det == Fixed64.Zero)
 1724        {
 1725            result = Identity;
 1726            return false;
 727        }
 728
 4729        Fixed64 invDet = Fixed64.One / det;
 730
 731        // Invert the 3×3 upper-left rotation/scale matrix
 4732        result = new Fixed4x4(
 4733            (matrix.m11 * matrix.m22 - matrix.m12 * matrix.m21) * invDet,
 4734            (matrix.m02 * matrix.m21 - matrix.m01 * matrix.m22) * invDet,
 4735            (matrix.m01 * matrix.m12 - matrix.m02 * matrix.m11) * invDet, Fixed64.Zero,
 4736
 4737            (matrix.m12 * matrix.m20 - matrix.m10 * matrix.m22) * invDet,
 4738            (matrix.m00 * matrix.m22 - matrix.m02 * matrix.m20) * invDet,
 4739            (matrix.m02 * matrix.m10 - matrix.m00 * matrix.m12) * invDet, Fixed64.Zero,
 4740
 4741            (matrix.m10 * matrix.m21 - matrix.m11 * matrix.m20) * invDet,
 4742            (matrix.m01 * matrix.m20 - matrix.m00 * matrix.m21) * invDet,
 4743            (matrix.m00 * matrix.m11 - matrix.m01 * matrix.m10) * invDet, Fixed64.Zero,
 4744
 4745            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One  // Ensure homogeneous coordinate stays valid
 4746        );
 747
 4748        Fixed3x3 rotationScaleInverse = new Fixed3x3(
 4749            result.m00, result.m01, result.m02,
 4750            result.m10, result.m11, result.m12,
 4751            result.m20, result.m21, result.m22
 4752        );
 753
 754        // Correct translation component
 4755        Vector3d transformedTranslation = new Vector3d(matrix.m30, matrix.m31, matrix.m32);
 4756        transformedTranslation = -Fixed3x3.TransformDirection(rotationScaleInverse, transformedTranslation);
 757
 4758        result.m30 = transformedTranslation.x;
 4759        result.m31 = transformedTranslation.y;
 4760        result.m32 = transformedTranslation.z;
 4761        result.m33 = Fixed64.One;
 762
 4763        return true;
 11764    }
 765
 766    private static bool FullInvert(Fixed4x4 matrix, out Fixed4x4 result)
 6767    {
 6768        Fixed64 det = matrix.GetDeterminant();
 769
 6770        if (det == Fixed64.Zero)
 2771        {
 2772            result = Fixed4x4.Identity;
 2773            return false;
 774        }
 775
 4776        Fixed64 invDet = Fixed64.One / det;
 777
 778        // Inversion using cofactors and determinants of 3x3 submatrices
 4779        result = new Fixed4x4
 4780        {
 4781            // First row
 4782            m00 = invDet * ((matrix.m11 * matrix.m22 * matrix.m33 + matrix.m12 * matrix.m23 * matrix.m31 + matrix.m13 * 
 4783                          - (matrix.m13 * matrix.m22 * matrix.m31 + matrix.m11 * matrix.m23 * matrix.m32 + matrix.m12 * 
 4784            m01 = invDet * ((matrix.m01 * matrix.m23 * matrix.m32 + matrix.m02 * matrix.m21 * matrix.m33 + matrix.m03 * 
 4785                          - (matrix.m03 * matrix.m21 * matrix.m32 + matrix.m01 * matrix.m22 * matrix.m33 + matrix.m02 * 
 4786            m02 = invDet * ((matrix.m01 * matrix.m12 * matrix.m33 + matrix.m02 * matrix.m13 * matrix.m31 + matrix.m03 * 
 4787                          - (matrix.m03 * matrix.m12 * matrix.m31 + matrix.m01 * matrix.m13 * matrix.m32 + matrix.m02 * 
 4788            m03 = invDet * ((matrix.m01 * matrix.m13 * matrix.m22 + matrix.m02 * matrix.m11 * matrix.m23 + matrix.m03 * 
 4789                          - (matrix.m03 * matrix.m11 * matrix.m22 + matrix.m01 * matrix.m12 * matrix.m23 + matrix.m02 * 
 4790
 4791            // Second row
 4792            m10 = invDet * ((matrix.m10 * matrix.m23 * matrix.m32 + matrix.m12 * matrix.m20 * matrix.m33 + matrix.m13 * 
 4793                          - (matrix.m13 * matrix.m20 * matrix.m32 + matrix.m10 * matrix.m22 * matrix.m33 + matrix.m12 * 
 4794            m11 = invDet * ((matrix.m00 * matrix.m22 * matrix.m33 + matrix.m02 * matrix.m23 * matrix.m30 + matrix.m03 * 
 4795                          - (matrix.m03 * matrix.m20 * matrix.m32 + matrix.m00 * matrix.m23 * matrix.m32 + matrix.m02 * 
 4796            m12 = invDet * ((matrix.m00 * matrix.m13 * matrix.m32 + matrix.m02 * matrix.m10 * matrix.m33 + matrix.m03 * 
 4797                          - (matrix.m03 * matrix.m10 * matrix.m32 + matrix.m00 * matrix.m12 * matrix.m33 + matrix.m02 * 
 4798            m13 = invDet * ((matrix.m00 * matrix.m12 * matrix.m23 + matrix.m02 * matrix.m13 * matrix.m20 + matrix.m03 * 
 4799                          - (matrix.m03 * matrix.m10 * matrix.m22 + matrix.m00 * matrix.m13 * matrix.m22 + matrix.m02 * 
 4800
 4801            // Third row
 4802            m20 = invDet * ((matrix.m10 * matrix.m21 * matrix.m33 + matrix.m11 * matrix.m23 * matrix.m30 + matrix.m13 * 
 4803                          - (matrix.m13 * matrix.m20 * matrix.m31 + matrix.m10 * matrix.m23 * matrix.m31 + matrix.m11 * 
 4804            m21 = invDet * ((matrix.m00 * matrix.m23 * matrix.m31 + matrix.m01 * matrix.m20 * matrix.m33 + matrix.m03 * 
 4805                          - (matrix.m03 * matrix.m20 * matrix.m31 + matrix.m00 * matrix.m21 * matrix.m33 + matrix.m01 * 
 4806            m22 = invDet * ((matrix.m00 * matrix.m11 * matrix.m33 + matrix.m01 * matrix.m13 * matrix.m30 + matrix.m03 * 
 4807                          - (matrix.m03 * matrix.m10 * matrix.m31 + matrix.m00 * matrix.m13 * matrix.m31 + matrix.m01 * 
 4808            m23 = invDet * ((matrix.m00 * matrix.m13 * matrix.m21 + matrix.m01 * matrix.m10 * matrix.m23 + matrix.m03 * 
 4809                          - (matrix.m03 * matrix.m10 * matrix.m21 + matrix.m00 * matrix.m11 * matrix.m23 + matrix.m01 * 
 4810
 4811            // Fourth row
 4812            m30 = invDet * ((matrix.m10 * matrix.m22 * matrix.m31 + matrix.m11 * matrix.m20 * matrix.m32 + matrix.m12 * 
 4813                          - (matrix.m12 * matrix.m20 * matrix.m31 + matrix.m10 * matrix.m21 * matrix.m32 + matrix.m11 * 
 4814            m31 = invDet * ((matrix.m00 * matrix.m21 * matrix.m32 + matrix.m01 * matrix.m22 * matrix.m30 + matrix.m02 * 
 4815                          - (matrix.m02 * matrix.m20 * matrix.m31 + matrix.m00 * matrix.m22 * matrix.m31 + matrix.m01 * 
 4816            m32 = invDet * ((matrix.m00 * matrix.m12 * matrix.m31 + matrix.m01 * matrix.m10 * matrix.m32 + matrix.m02 * 
 4817                          - (matrix.m02 * matrix.m10 * matrix.m31 + matrix.m00 * matrix.m11 * matrix.m32 + matrix.m01 * 
 4818            m33 = invDet * ((matrix.m00 * matrix.m11 * matrix.m22 + matrix.m01 * matrix.m12 * matrix.m20 + matrix.m02 * 
 4819                          - (matrix.m02 * matrix.m10 * matrix.m21 + matrix.m00 * matrix.m12 * matrix.m21 + matrix.m01 * 
 4820        };
 821
 4822        return true;
 6823    }
 824
 825    /// <summary>
 826    /// Transforms a point from local space to world space using this transformation matrix.
 827    /// </summary>
 828    /// <remarks>
 829    /// This is the same as doing `<see cref="Fixed4x4"/> a * <see cref="Vector3d"/> b`
 830    /// </remarks>
 831    /// <param name="matrix">The transformation matrix.</param>
 832    /// <param name="point">The local-space point.</param>
 833    /// <returns>The transformed point in world space.</returns>
 834    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 835    public static Vector3d TransformPoint(Fixed4x4 matrix, Vector3d point)
 8836    {
 8837        if (matrix.IsAffine)
 3838        {
 3839            return new Vector3d(
 3840                matrix.m00 * point.x + matrix.m01 * point.y + matrix.m02 * point.z + matrix.m30,
 3841                matrix.m10 * point.x + matrix.m11 * point.y + matrix.m12 * point.z + matrix.m31,
 3842                matrix.m20 * point.x + matrix.m21 * point.y + matrix.m22 * point.z + matrix.m32
 3843            );
 844        }
 845
 5846        return FullTransformPoint(matrix, point);
 8847    }
 848
 849    private static Vector3d FullTransformPoint(Fixed4x4 matrix, Vector3d point)
 5850    {
 851        // Full 4×4 transformation (needed for perspective projections)
 5852        Fixed64 w = matrix.m03 * point.x + matrix.m13 * point.y + matrix.m23 * point.z + matrix.m33;
 6853        if (w == Fixed64.Zero) w = Fixed64.One;  // Prevent divide-by-zero
 854
 5855        return new Vector3d(
 5856            (matrix.m00 * point.x + matrix.m01 * point.y + matrix.m02 * point.z + matrix.m30) / w,
 5857            (matrix.m10 * point.x + matrix.m11 * point.y + matrix.m12 * point.z + matrix.m31) / w,
 5858            (matrix.m20 * point.x + matrix.m21 * point.y + matrix.m22 * point.z + matrix.m32) / w
 5859        );
 5860    }
 861
 862    /// <summary>
 863    /// Transforms a point from world space into the local space of the matrix.
 864    /// </summary>
 865    /// <param name="matrix">The transformation matrix.</param>
 866    /// <param name="point">The world-space point.</param>
 867    /// <returns>The local-space point relative to the transformation matrix.</returns>
 868    public static Vector3d InverseTransformPoint(Fixed4x4 matrix, Vector3d point)
 7869    {
 870        // Invert the transformation matrix
 7871        if (!Invert(matrix, out Fixed4x4 inverseMatrix))
 1872            throw new InvalidOperationException("Matrix is not invertible.");
 873
 6874        if (inverseMatrix.IsAffine)
 3875        {
 3876            return new Vector3d(
 3877                inverseMatrix.m00 * point.x + inverseMatrix.m01 * point.y + inverseMatrix.m02 * point.z + inverseMatrix.
 3878                inverseMatrix.m10 * point.x + inverseMatrix.m11 * point.y + inverseMatrix.m12 * point.z + inverseMatrix.
 3879                inverseMatrix.m20 * point.x + inverseMatrix.m21 * point.y + inverseMatrix.m22 * point.z + inverseMatrix.
 3880            );
 881        }
 882
 3883        return FullInverseTransformPoint(inverseMatrix, point);
 6884    }
 885
 886    private static Vector3d FullInverseTransformPoint(Fixed4x4 matrix, Vector3d point)
 3887    {
 888        // Full 4×4 transformation (needed for perspective projections)
 3889        Fixed64 w = matrix.m03 * point.x + matrix.m13 * point.y + matrix.m23 * point.z + matrix.m33;
 4890        if (w == Fixed64.Zero) w = Fixed64.One;  // Prevent divide-by-zero
 891
 3892        return new Vector3d(
 3893            (matrix.m00 * point.x + matrix.m01 * point.y + matrix.m02 * point.z + matrix.m30) / w,
 3894            (matrix.m10 * point.x + matrix.m11 * point.y + matrix.m12 * point.z + matrix.m31) / w,
 3895            (matrix.m20 * point.x + matrix.m21 * point.y + matrix.m22 * point.z + matrix.m32) / w
 3896        );
 3897    }
 898
 899    #endregion
 900
 901    #region Operators
 902
 903    /// <summary>
 904    /// Negates the specified matrix by multiplying all its values by -1.
 905    /// </summary>
 906    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 907    public static Fixed4x4 operator -(Fixed4x4 value)
 1908    {
 1909        Fixed4x4 result = default;
 1910        result.m00 = -value.m00;
 1911        result.m01 = -value.m01;
 1912        result.m02 = -value.m02;
 1913        result.m03 = -value.m03;
 1914        result.m10 = -value.m10;
 1915        result.m11 = -value.m11;
 1916        result.m12 = -value.m12;
 1917        result.m13 = -value.m13;
 1918        result.m20 = -value.m20;
 1919        result.m21 = -value.m21;
 1920        result.m22 = -value.m22;
 1921        result.m23 = -value.m23;
 1922        result.m30 = -value.m30;
 1923        result.m31 = -value.m31;
 1924        result.m32 = -value.m32;
 1925        result.m33 = -value.m33;
 1926        return result;
 1927    }
 928
 929    /// <summary>
 930    /// Adds two matrices element-wise.
 931    /// </summary>
 932    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 933    public static Fixed4x4 operator +(Fixed4x4 lhs, Fixed4x4 rhs)
 1934    {
 1935        return new Fixed4x4(
 1936            lhs.m00 + rhs.m00, lhs.m01 + rhs.m01, lhs.m02 + rhs.m02, lhs.m03 + rhs.m03,
 1937            lhs.m10 + rhs.m10, lhs.m11 + rhs.m11, lhs.m12 + rhs.m12, lhs.m13 + rhs.m13,
 1938            lhs.m20 + rhs.m20, lhs.m21 + rhs.m21, lhs.m22 + rhs.m22, lhs.m23 + rhs.m23,
 1939            lhs.m30 + rhs.m30, lhs.m31 + rhs.m31, lhs.m32 + rhs.m32, lhs.m33 + rhs.m33);
 1940    }
 941
 942    /// <summary>
 943    /// Subtracts two matrices element-wise.
 944    /// </summary>
 945    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 946    public static Fixed4x4 operator -(Fixed4x4 lhs, Fixed4x4 rhs)
 1947    {
 1948        return new Fixed4x4(
 1949            lhs.m00 - rhs.m00, lhs.m01 - rhs.m01, lhs.m02 - rhs.m02, lhs.m03 - rhs.m03,
 1950            lhs.m10 - rhs.m10, lhs.m11 - rhs.m11, lhs.m12 - rhs.m12, lhs.m13 - rhs.m13,
 1951            lhs.m20 - rhs.m20, lhs.m21 - rhs.m21, lhs.m22 - rhs.m22, lhs.m23 - rhs.m23,
 1952            lhs.m30 - rhs.m30, lhs.m31 - rhs.m31, lhs.m32 - rhs.m32, lhs.m33 - rhs.m33);
 1953    }
 954
 955    /// <summary>
 956    /// Multiplies two 4x4 matrices using standard matrix multiplication.
 957    /// </summary>
 958    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 959    public static Fixed4x4 operator *(Fixed4x4 lhs, Fixed4x4 rhs)
 22960    {
 22961        if (lhs.IsAffine && rhs.IsAffine)
 20962        {
 963            // Optimized affine multiplication (skips full 4×4 multiplication)
 20964            return new Fixed4x4(
 20965                lhs.m00 * rhs.m00 + lhs.m01 * rhs.m10 + lhs.m02 * rhs.m20,
 20966                lhs.m00 * rhs.m01 + lhs.m01 * rhs.m11 + lhs.m02 * rhs.m21,
 20967                lhs.m00 * rhs.m02 + lhs.m01 * rhs.m12 + lhs.m02 * rhs.m22,
 20968                Fixed64.Zero,
 20969
 20970                lhs.m10 * rhs.m00 + lhs.m11 * rhs.m10 + lhs.m12 * rhs.m20,
 20971                lhs.m10 * rhs.m01 + lhs.m11 * rhs.m11 + lhs.m12 * rhs.m21,
 20972                lhs.m10 * rhs.m02 + lhs.m11 * rhs.m12 + lhs.m12 * rhs.m22,
 20973                Fixed64.Zero,
 20974
 20975                lhs.m20 * rhs.m00 + lhs.m21 * rhs.m10 + lhs.m22 * rhs.m20,
 20976                lhs.m20 * rhs.m01 + lhs.m21 * rhs.m11 + lhs.m22 * rhs.m21,
 20977                lhs.m20 * rhs.m02 + lhs.m21 * rhs.m12 + lhs.m22 * rhs.m22,
 20978                Fixed64.Zero,
 20979
 20980                lhs.m30 * rhs.m00 + lhs.m31 * rhs.m10 + lhs.m32 * rhs.m20 + rhs.m30,
 20981                lhs.m30 * rhs.m01 + lhs.m31 * rhs.m11 + lhs.m32 * rhs.m21 + rhs.m31,
 20982                lhs.m30 * rhs.m02 + lhs.m31 * rhs.m12 + lhs.m32 * rhs.m22 + rhs.m32,
 20983                Fixed64.One
 20984            );
 985        }
 986
 987        // Full 4×4 multiplication (fallback for perspective matrices)
 2988        return new Fixed4x4(
 2989            // Upper-left 3×3 matrix multiplication (rotation & scale)
 2990            lhs.m00 * rhs.m00 + lhs.m01 * rhs.m10 + lhs.m02 * rhs.m20 + lhs.m03 * rhs.m30,
 2991            lhs.m00 * rhs.m01 + lhs.m01 * rhs.m11 + lhs.m02 * rhs.m21 + lhs.m03 * rhs.m31,
 2992            lhs.m00 * rhs.m02 + lhs.m01 * rhs.m12 + lhs.m02 * rhs.m22 + lhs.m03 * rhs.m32,
 2993            lhs.m00 * rhs.m03 + lhs.m01 * rhs.m13 + lhs.m02 * rhs.m23 + lhs.m03 * rhs.m33,
 2994
 2995            lhs.m10 * rhs.m00 + lhs.m11 * rhs.m10 + lhs.m12 * rhs.m20 + lhs.m13 * rhs.m30,
 2996            lhs.m10 * rhs.m01 + lhs.m11 * rhs.m11 + lhs.m12 * rhs.m21 + lhs.m13 * rhs.m31,
 2997            lhs.m10 * rhs.m02 + lhs.m11 * rhs.m12 + lhs.m12 * rhs.m22 + lhs.m13 * rhs.m32,
 2998            lhs.m10 * rhs.m03 + lhs.m11 * rhs.m13 + lhs.m12 * rhs.m23 + lhs.m13 * rhs.m33,
 2999
 21000            lhs.m20 * rhs.m00 + lhs.m21 * rhs.m10 + lhs.m22 * rhs.m20 + lhs.m23 * rhs.m30,
 21001            lhs.m20 * rhs.m01 + lhs.m21 * rhs.m11 + lhs.m22 * rhs.m21 + lhs.m23 * rhs.m31,
 21002            lhs.m20 * rhs.m02 + lhs.m21 * rhs.m12 + lhs.m22 * rhs.m22 + lhs.m23 * rhs.m32,
 21003            lhs.m20 * rhs.m03 + lhs.m21 * rhs.m13 + lhs.m22 * rhs.m23 + lhs.m23 * rhs.m33,
 21004
 21005            // Compute new translation
 21006            lhs.m30 * rhs.m00 + lhs.m31 * rhs.m10 + lhs.m32 * rhs.m20 + lhs.m33 * rhs.m30,
 21007            lhs.m30 * rhs.m01 + lhs.m31 * rhs.m11 + lhs.m32 * rhs.m21 + lhs.m33 * rhs.m31,
 21008            lhs.m30 * rhs.m02 + lhs.m31 * rhs.m12 + lhs.m32 * rhs.m22 + lhs.m33 * rhs.m32,
 21009            lhs.m30 * rhs.m03 + lhs.m31 * rhs.m13 + lhs.m32 * rhs.m23 + lhs.m33 * rhs.m33
 21010        );
 221011    }
 1012
 1013    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1014    public static bool operator ==(Fixed4x4 left, Fixed4x4 right)
 21015    {
 21016        return left.Equals(right);
 21017    }
 1018
 1019    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1020    public static bool operator !=(Fixed4x4 left, Fixed4x4 right)
 11021    {
 11022        return !(left == right);
 11023    }
 1024
 1025    #endregion
 1026
 1027    #region Equality and HashCode Overrides
 1028
 1029    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1030    public override bool Equals(object? obj)
 51031    {
 51032        return obj is Fixed4x4 x && Equals(x);
 51033    }
 1034
 1035    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1036    public bool Equals(Fixed4x4 other)
 201037    {
 201038        return m00 == other.m00 && m01 == other.m01 && m02 == other.m02 && m03 == other.m03 &&
 201039               m10 == other.m10 && m11 == other.m11 && m12 == other.m12 && m13 == other.m13 &&
 201040               m20 == other.m20 && m21 == other.m21 && m22 == other.m22 && m23 == other.m23 &&
 201041               m30 == other.m30 && m31 == other.m31 && m32 == other.m32 && m33 == other.m33;
 201042    }
 1043
 1044    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1045    public override int GetHashCode()
 41046    {
 1047        unchecked
 41048        {
 41049            int hash = 17;
 41050            hash = hash * 23 + m00.GetHashCode();
 41051            hash = hash * 23 + m01.GetHashCode();
 41052            hash = hash * 23 + m02.GetHashCode();
 41053            hash = hash * 23 + m03.GetHashCode();
 41054            hash = hash * 23 + m10.GetHashCode();
 41055            hash = hash * 23 + m11.GetHashCode();
 41056            hash = hash * 23 + m12.GetHashCode();
 41057            hash = hash * 23 + m13.GetHashCode();
 41058            hash = hash * 23 + m20.GetHashCode();
 41059            hash = hash * 23 + m21.GetHashCode();
 41060            hash = hash * 23 + m22.GetHashCode();
 41061            hash = hash * 23 + m23.GetHashCode();
 41062            hash = hash * 23 + m30.GetHashCode();
 41063            hash = hash * 23 + m31.GetHashCode();
 41064            hash = hash * 23 + m32.GetHashCode();
 41065            hash = hash * 23 + m33.GetHashCode();
 41066            return hash;
 1067        }
 41068    }
 1069
 1070    #endregion
 1071}

Methods/Properties

.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
get_IsAffine()
get_Translation()
get_Up()
get_Scale()
get_Rotation()
get_Item(System.Int32)
set_Item(System.Int32,FixedMathSharp.Fixed64)
GetDeterminant()
ResetScaleToIdentity()
SetTransform(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d)
CreateTranslation(FixedMathSharp.Vector3d)
CreateRotation(FixedMathSharp.FixedQuaternion)
CreateScale(FixedMathSharp.Vector3d)
CreateTransform(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d)
ScaleRotateTranslate(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d)
TranslateRotateScale(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d)
ExtractTranslation(FixedMathSharp.Fixed4x4)
ExtractUp(FixedMathSharp.Fixed4x4)
ExtractScale(FixedMathSharp.Fixed4x4)
ExtractLossyScale(FixedMathSharp.Fixed4x4)
ExtractRotation(FixedMathSharp.Fixed4x4)
Decompose(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d&,FixedMathSharp.FixedQuaternion&,FixedMathSharp.Vector3d&)
SetTranslation(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)
SetScale(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)
ApplyScaleToRotation(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)
ResetScaleToIdentity(FixedMathSharp.Fixed4x4)
SetGlobalScale(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)
SetRotation(FixedMathSharp.Fixed4x4,FixedMathSharp.FixedQuaternion)
NormalizeRotationMatrix(FixedMathSharp.Fixed4x4)
Invert(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4&)
FullInvert(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4&)
TransformPoint(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)
FullTransformPoint(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)
InverseTransformPoint(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)
FullInverseTransformPoint(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)
op_UnaryNegation(FixedMathSharp.Fixed4x4)
op_Addition(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4)
op_Subtraction(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4)
op_Multiply(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4)
op_Equality(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4)
op_Inequality(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4)
Equals(System.Object)
Equals(FixedMathSharp.Fixed4x4)
GetHashCode()