< Summary

Line coverage
100%
Covered lines: 781
Uncovered lines: 0
Coverable lines: 781
Total lines: 1972
Line coverage: 100%
Branch coverage
100%
Covered branches: 242
Total branches: 242
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: .cctor()100%11100%
File 1: .ctor(...)100%11100%
File 1: FromRows(...)100%11100%
File 1: FromColumns(...)100%11100%
File 1: get_IsAffine()100%66100%
File 1: get_Translation()100%11100%
File 1: get_Right()100%11100%
File 1: get_Left()100%11100%
File 1: get_Up()100%11100%
File 1: get_Down()100%11100%
File 1: get_Forward()100%11100%
File 1: get_Backward()100%11100%
File 1: get_LossyScale()100%11100%
File 1: get_Rotation()100%11100%
File 1: get_Item(...)100%1717100%
File 1: set_Item(...)100%1717100%
File 1: GetDeterminant()100%22100%
File 1: SetTransform(...)100%11100%
File 2: ExtractTranslation(...)100%11100%
File 2: ExtractRight(...)100%11100%
File 2: ExtractUp(...)100%11100%
File 2: ExtractForward(...)100%11100%
File 2: ExtractScaleMagnitudes(...)100%11100%
File 2: ExtractLossyScale(...)100%22100%
File 2: TryExtractLossyScale(...)100%44100%
File 2: ExtractRotation(...)100%66100%
File 2: Decompose(...)100%2222100%
File 2: IsNormalizedOrthogonalBasis(...)100%1616100%
File 2: SetTranslation(...)100%11100%
File 2: ApplyScaleToRotation(...)100%11100%
File 2: SetRotation(...)100%11100%
File 2: NormalizeRotationMatrix(...)100%11100%
File 3: Equals(...)100%22100%
File 3: Equals(...)100%3030100%
File 3: GetHashCode()100%11100%
File 3: ToString()100%11100%
File 3: ToString(...)100%11100%
File 3: TryFormat(...)100%1818100%
File 3: AppendRow(...)100%1212100%
File 4: CreateTranslation(...)100%11100%
File 4: CreateTranslation(...)100%11100%
File 4: CreateRotation(...)100%11100%
File 4: CreateRotationX(...)100%11100%
File 4: CreateRotationY(...)100%11100%
File 4: CreateRotationZ(...)100%11100%
File 4: CreateFromAxisAngle(...)100%11100%
File 4: CreateFromEulerAngles(...)100%11100%
File 4: CreateScale(...)100%11100%
File 4: CreateScale(...)100%11100%
File 4: CreateScale(...)100%11100%
File 4: CreateLookAt(...)100%44100%
File 4: CreateOrthographic(...)100%44100%
File 4: CreateOrthographicOffCenter(...)100%44100%
File 4: CreatePerspective(...)100%44100%
File 4: CreatePerspectiveFieldOfView(...)100%66100%
File 4: CreatePerspectiveOffCenter(...)100%44100%
File 4: CreateWorld(...)100%44100%
File 4: CreateTransform(...)100%11100%
File 4: CreateTransform(...)100%11100%
File 4: TryCreateTransform(...)100%22100%
File 4: ScaleRotateTranslate(...)100%11100%
File 4: TranslateRotateScale(...)100%11100%
File 5: FromRotationMatrix(...)100%11100%
File 5: ValidateDepthRange(...)100%44100%
File 5: ValidatePerspectiveDepthRange(...)100%44100%
File 6: op_UnaryNegation(...)100%11100%
File 6: op_Addition(...)100%11100%
File 6: op_Subtraction(...)100%11100%
File 6: TryMultiply(...)100%22100%
File 6: op_Multiply(...)100%44100%
File 6: TryGetExactProductSum(...)100%2020100%
File 6: op_Multiply(...)100%11100%
File 6: op_Multiply(...)100%11100%
File 6: op_Division(...)100%11100%
File 6: op_Equality(...)100%11100%
File 6: op_Inequality(...)100%11100%
File 7: Lerp(...)100%11100%
File 7: Transpose(...)100%11100%
File 7: ComponentDivide(...)100%11100%
File 7: InverseDivide(...)100%22100%
File 7: Invert(...)100%44100%
File 7: FullInvert(...)100%22100%
File 7: Transform(...)100%11100%
File 7: TransformPoint(...)100%22100%
File 7: TryTransformAffinePoint(...)100%44100%
File 7: FullTransformPoint(...)100%22100%
File 7: InverseTransformPoint(...)100%44100%
File 7: FullInverseTransformPoint(...)100%22100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// Fixed4x4.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Runtime.CompilerServices;
 10using System.Text.Json.Serialization;
 11using MemoryPack;
 12
 13namespace FixedMathSharp;
 14
 15/// <summary>
 16/// Represents a 4x4 matrix used for transformations in 3D space, including translation, rotation, scaling, and perspect
 17/// </summary>
 18/// <remarks>
 19/// A 4x4 matrix is the standard structure for 3D transformations because it can handle both linear transformations (rot
 20/// and affine transformations (translation, shearing, and perspective projections).
 21/// It is commonly used in graphics pipelines, game engines, and 3D rendering systems.
 22///
 23/// FixedMathSharp applies transform matrices with a row-vector convention: <c>point * matrix</c>.
 24/// Translation is stored in <c>M41</c>, <c>M42</c>, and <c>M43</c>. Direction properties use the
 25/// canonical 3D basis: <c>+X</c> right, <c>+Y</c> up, and <c>+Z</c> forward.
 26///
 27/// Use Cases:
 28/// - Transforming objects in 3D space (position, orientation, and size).
 29/// - Combining multiple transformations (e.g., model-view-projection matrices).
 30/// - Applying translations, which require an extra dimension for homogeneous coordinates.
 31/// - Useful in animation, physics engines, and 3D rendering for full transformation control.
 32/// </remarks>
 33[Serializable]
 34[MemoryPackable]
 35public partial struct Fixed4x4 : IEquatable<Fixed4x4>, IFormattable
 36#if NET10_0_OR_GREATER
 37    , ISpanFormattable
 38#endif
 39{
 40    #region Static Readonly Fields
 41
 42    /// <summary>
 43    /// Returns the identity matrix (diagonal elements set to 1).
 44    /// </summary>
 245    public static readonly Fixed4x4 Identity = new(
 246        Fixed64.One, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 247        Fixed64.Zero, Fixed64.One, Fixed64.Zero, Fixed64.Zero,
 248        Fixed64.Zero, Fixed64.Zero, Fixed64.One, Fixed64.Zero,
 249        Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One);
 50
 51    /// <summary>
 52    /// Returns a matrix with all elements set to zero.
 53    /// </summary>
 254    public static readonly Fixed4x4 Zero = new(
 255        Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 256        Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 257        Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 258        Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero);
 59
 60    #endregion
 61    #region Fields and Constants
 62
 63    // First row
 64
 65    /// <summary>
 66    /// Represents the element in the first row and first column of the matrix.
 67    /// </summary>
 68    [JsonInclude]
 69    [MemoryPackOrder(0)]
 70    public Fixed64 M11;
 71    /// <summary>
 72    /// Represents the element in the first row and second column of the matrix.
 73    /// </summary>
 74    [JsonInclude]
 75    [MemoryPackOrder(1)]
 76    public Fixed64 M12;
 77    /// <summary>
 78    /// Represents the element in the first row and third column of the matrix.
 79    /// </summary>
 80    [JsonInclude]
 81    [MemoryPackOrder(2)]
 82    public Fixed64 M13;
 83    /// <summary>
 84    /// Represents the element in the first row and fourth column of the matrix.
 85    /// </summary>
 86    [JsonInclude]
 87    [MemoryPackOrder(3)]
 88    public Fixed64 M14;
 89
 90    // Second row
 91
 92    /// <summary>
 93    /// Represents the element in the second row and first column of the matrix.
 94    /// </summary>
 95    [JsonInclude]
 96    [MemoryPackOrder(4)]
 97    public Fixed64 M21;
 98    /// <summary>
 99    /// Represents the element in the second row and second column of the matrix.
 100    /// </summary>
 101    [JsonInclude]
 102    [MemoryPackOrder(5)]
 103    public Fixed64 M22;
 104    /// <summary>
 105    /// Represents the element in the second row and third column of the matrix.
 106    /// </summary>
 107    [JsonInclude]
 108    [MemoryPackOrder(6)]
 109    public Fixed64 M23;
 110    /// <summary>
 111    /// Represents the element in the second row and fourth column of the matrix.
 112    /// </summary>
 113    [JsonInclude]
 114    [MemoryPackOrder(7)]
 115    public Fixed64 M24;
 116
 117    // Third row
 118
 119    /// <summary>
 120    /// Represents the element in the third row and first column of the matrix.
 121    /// </summary>
 122    [JsonInclude]
 123    [MemoryPackOrder(8)]
 124    public Fixed64 M31;
 125    /// <summary>
 126    /// Represents the element in the third row and second column of the matrix.
 127    /// </summary>
 128    [JsonInclude]
 129    [MemoryPackOrder(9)]
 130    public Fixed64 M32;
 131    /// <summary>
 132    /// Represents the element in the third row and third column of the matrix.
 133    /// </summary>
 134    [JsonInclude]
 135    [MemoryPackOrder(10)]
 136    public Fixed64 M33;
 137    /// <summary>
 138    /// Represents the element in the third row and fourth column of the matrix.
 139    /// </summary>
 140    [JsonInclude]
 141    [MemoryPackOrder(11)]
 142    public Fixed64 M34;
 143
 144    // Fourth row
 145
 146    /// <summary>
 147    /// Represents the element in the fourth row and first column of the matrix.
 148    /// </summary>
 149    [JsonInclude]
 150    [MemoryPackOrder(12)]
 151    public Fixed64 M41;
 152    /// <summary>
 153    /// Represents the element in the fourth row and second column of the matrix.
 154    /// </summary>
 155    [JsonInclude]
 156    [MemoryPackOrder(13)]
 157    public Fixed64 M42;
 158    /// <summary>
 159    /// Represents the element in the fourth row and third column of the matrix.
 160    /// </summary>
 161    [JsonInclude]
 162    [MemoryPackOrder(14)]
 163    public Fixed64 M43;
 164    /// <summary>
 165    /// Represents the element in the fourth row and fourth column of the matrix.
 166    /// </summary>
 167    [JsonInclude]
 168    [MemoryPackOrder(15)]
 169    public Fixed64 M44;
 170
 171    #endregion
 172    #region Constructors
 173
 174    /// <summary>
 175    /// Initializes a new FixedMatrix4x4 with individual elements.
 176    /// </summary>
 177    public Fixed4x4(
 178        Fixed64 m11, Fixed64 m12, Fixed64 m13, Fixed64 m14,
 179        Fixed64 m21, Fixed64 m22, Fixed64 m23, Fixed64 m24,
 180        Fixed64 m31, Fixed64 m32, Fixed64 m33, Fixed64 m34,
 181        Fixed64 m41, Fixed64 m42, Fixed64 m43, Fixed64 m44
 182    )
 183    {
 275072184        M11 = m11; M12 = m12; M13 = m13; M14 = m14;
 275072185        M21 = m21; M22 = m22; M23 = m23; M24 = m24;
 275072186        M31 = m31; M32 = m32; M33 = m33; M34 = m34;
 275072187        M41 = m41; M42 = m42; M43 = m43; M44 = m44;
 68768188    }
 189
 190    /// <summary>
 191    /// Creates a matrix from four row vectors.
 192    /// </summary>
 193    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 194    public static Fixed4x4 FromRows(Vector4d row1, Vector4d row2, Vector4d row3, Vector4d row4)
 195    {
 3196        return new Fixed4x4(
 3197            row1.X, row1.Y, row1.Z, row1.W,
 3198            row2.X, row2.Y, row2.Z, row2.W,
 3199            row3.X, row3.Y, row3.Z, row3.W,
 3200            row4.X, row4.Y, row4.Z, row4.W);
 201    }
 202
 203    /// <summary>
 204    /// Creates a matrix from four column vectors.
 205    /// </summary>
 206    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 207    public static Fixed4x4 FromColumns(Vector4d column1, Vector4d column2, Vector4d column3, Vector4d column4)
 208    {
 1209        return new Fixed4x4(
 1210            column1.X, column2.X, column3.X, column4.X,
 1211            column1.Y, column2.Y, column3.Y, column4.Y,
 1212            column1.Z, column2.Z, column3.Z, column4.Z,
 1213            column1.W, column2.W, column3.W, column4.W);
 214    }
 215
 216    #endregion
 217    #region Properties
 218
 219    /// <summary>
 220    /// Gets a value indicating whether the matrix represents an affine transformation.
 221    /// </summary>
 222    /// <remarks>
 223    /// An affine transformation is one where the bottom row is (0, 0, 0, 1),
 224    /// allowing for efficient operations such as translation, scaling, rotation, and shearing without perspective disto
 225    /// </remarks>
 226    [JsonIgnore]
 227    [MemoryPackIgnore]
 62092228    public readonly bool IsAffine => M44 == Fixed64.One
 62092229        && M14 == Fixed64.Zero
 62092230        && M24 == Fixed64.Zero
 62092231        && M34 == Fixed64.Zero;
 232
 233    /// <inheritdoc cref="ExtractTranslation(Fixed4x4)" />
 234    [JsonIgnore]
 235    [MemoryPackIgnore]
 43236    public readonly Vector3d Translation => ExtractTranslation(this);
 237
 238    /// <summary>
 239    /// Gets the right direction vector for this instance.
 240    /// </summary>
 241    [JsonIgnore]
 242    [MemoryPackIgnore]
 3243    public readonly Vector3d Right => ExtractRight(this);
 244
 245    /// <summary>
 246    /// Gets the left direction vector for this instance.
 247    /// </summary>
 248    [JsonIgnore]
 249    [MemoryPackIgnore]
 3250    public readonly Vector3d Left => -ExtractRight(this);
 251
 252    /// <summary>
 253    /// Gets the upward direction vector for this instance.
 254    /// </summary>
 255    [JsonIgnore]
 256    [MemoryPackIgnore]
 3257    public readonly Vector3d Up => ExtractUp(this);
 258
 259    /// <summary>
 260    /// Gets the downward direction vector for this instance.
 261    /// </summary>
 262    [JsonIgnore]
 263    [MemoryPackIgnore]
 3264    public readonly Vector3d Down => -ExtractUp(this);
 265
 266    /// <summary>
 267    /// Gets the canonical forward direction vector for this instance.
 268    /// </summary>
 269    /// <remarks>
 270    /// FixedMathSharp defines semantic forward as <c>+Z</c>. Convert external matrix or transform
 271    /// conventions at adapter boundaries before relying on this basis extraction.
 272    /// </remarks>
 273    [JsonIgnore]
 274    [MemoryPackIgnore]
 3275    public readonly Vector3d Forward => ExtractForward(this);
 276
 277    /// <summary>
 278    /// Gets the backward direction vector for this instance.
 279    /// </summary>
 280    [JsonIgnore]
 281    [MemoryPackIgnore]
 3282    public readonly Vector3d Backward => -ExtractForward(this);
 283
 284    /// <inheritdoc cref="ExtractLossyScale(Fixed4x4)" />
 285    [JsonIgnore]
 286    [MemoryPackIgnore]
 23287    public readonly Vector3d LossyScale => ExtractLossyScale(this);
 288
 289    /// <inheritdoc cref="ExtractRotation(Fixed4x4)" />
 290    [JsonIgnore]
 291    [MemoryPackIgnore]
 6292    public readonly FixedQuaternion Rotation => ExtractRotation(this);
 293
 294    /// <summary>
 295    /// Gets or sets the matrix element at the specified linear index.
 296    /// </summary>
 297    /// <remarks>Matrix elements are indexed in row-major order from 0 to 15.</remarks>
 298    /// <param name="index">The zero-based linear index of the matrix element to get or set. Must be in the range 0 to 1
 299    /// <returns>The matrix element at the specified index.</returns>
 300    /// <exception cref="IndexOutOfRangeException">Thrown when the specified index is less than 0 or greater than 15.</e
 301    [JsonIgnore]
 302    [MemoryPackIgnore]
 303    public Fixed64 this[int index]
 304    {
 305        get
 306        {
 252307            return index switch
 252308            {
 27309                0 => M11,
 27310                1 => M21,
 27311                2 => M31,
 1312                3 => M41,
 27313                4 => M12,
 27314                5 => M22,
 27315                6 => M32,
 1316                7 => M42,
 27317                8 => M13,
 27318                9 => M23,
 27319                10 => M33,
 1320                11 => M43,
 1321                12 => M14,
 1322                13 => M24,
 1323                14 => M34,
 1324                15 => M44,
 2325                _ => throw new IndexOutOfRangeException("Invalid matrix index!"),
 252326            };
 327        }
 328        set
 329        {
 330            switch (index)
 331            {
 332                case 0:
 6333                    M11 = value;
 6334                    break;
 335                case 1:
 8336                    M21 = value;
 8337                    break;
 338                case 2:
 8339                    M31 = value;
 8340                    break;
 341                case 3:
 1342                    M41 = value;
 1343                    break;
 344                case 4:
 9345                    M12 = value;
 9346                    break;
 347                case 5:
 6348                    M22 = value;
 6349                    break;
 350                case 6:
 7351                    M32 = value;
 7352                    break;
 353                case 7:
 1354                    M42 = value;
 1355                    break;
 356                case 8:
 6357                    M13 = value;
 6358                    break;
 359                case 9:
 7360                    M23 = value;
 7361                    break;
 362                case 10:
 3363                    M33 = value;
 3364                    break;
 365                case 11:
 1366                    M43 = value;
 1367                    break;
 368                case 12:
 4369                    M14 = value;
 4370                    break;
 371                case 13:
 4372                    M24 = value;
 4373                    break;
 374                case 14:
 4375                    M34 = value;
 4376                    break;
 377                case 15:
 1378                    M44 = value;
 1379                    break;
 380                default:
 2381                    throw new IndexOutOfRangeException("Invalid matrix index!");
 382            }
 383        }
 384    }
 385
 386    #endregion
 387    #region Methods (Instance)
 388
 389    /// <summary>
 390    /// Calculates the determinant of a 4x4 matrix.
 391    /// </summary>
 392    public Fixed64 GetDeterminant()
 393    {
 2218394        if (IsAffine)
 395        {
 2211396            return M11 * (M22 * M33 - M23 * M32)
 2211397                 - M12 * (M21 * M33 - M23 * M31)
 2211398                 + M13 * (M21 * M32 - M22 * M31);
 399        }
 400
 401        // Process as full 4x4 matrix
 7402        Fixed64 minor0 = M33 * M44 - M34 * M43;
 7403        Fixed64 minor1 = M32 * M44 - M34 * M42;
 7404        Fixed64 minor2 = M32 * M43 - M33 * M42;
 7405        Fixed64 cofactor0 = M31 * M44 - M34 * M41;
 7406        Fixed64 cofactor1 = M31 * M43 - M33 * M41;
 7407        Fixed64 cofactor2 = M31 * M42 - M32 * M41;
 7408        return M11 * (M22 * minor0 - M23 * minor1 + M24 * minor2)
 7409            - M12 * (M21 * minor0 - M23 * cofactor0 + M24 * cofactor1)
 7410            + M13 * (M21 * minor1 - M22 * cofactor0 + M24 * cofactor2)
 7411            - M14 * (M21 * minor2 - M22 * cofactor1 + M23 * cofactor2);
 412    }
 413
 414    /// <summary>
 415    /// Sets the translation, scale, and rotation components onto the matrix.
 416    /// </summary>
 417    /// <param name="translation">The translation vector.</param>
 418    /// <param name="scale">The scale vector.</param>
 419    /// <param name="rotation">The rotation quaternion.</param>
 420    public void SetTransform(Vector3d translation, FixedQuaternion rotation, Vector3d scale) =>
 3421        this = CreateTransform(translation, rotation, scale);
 422
 423    #endregion
 424}

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

#LineLine coverage
 1//=======================================================================
 2// Fixed4x4.Decomposition.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System.Runtime.CompilerServices;
 9using FixedMathSharp.Geometry;
 10
 11namespace FixedMathSharp;
 12
 13/// <content>
 14/// Provides methods for decomposing and extracting components (translation, rotation axes, and scale)
 15/// from a <see cref="Fixed4x4"/> matrix.
 16/// </content>
 17public partial struct Fixed4x4
 18{
 19    #region Decomposition, Extraction, and Setters
 20
 21    /// <summary>
 22    /// Extracts the translation component from the 4x4 matrix.
 23    /// </summary>
 24    /// <param name="matrix">The matrix from which to extract the translation.</param>
 25    /// <returns>A Vector3d representing the translation component.</returns>
 26    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 4627    public static Vector3d ExtractTranslation(Fixed4x4 matrix) => new(matrix.M41, matrix.M42, matrix.M43);
 28
 29    /// <summary>
 30    /// Extracts the right direction from the 4x4 matrix.
 31    /// </summary>
 632    public static Vector3d ExtractRight(Fixed4x4 matrix) => new Vector3d(matrix.M11, matrix.M12, matrix.M13).NormalizeIn
 33
 34    /// <summary>
 35    /// Extracts the up direction from the 4x4 matrix.
 36    /// </summary>
 37    /// <remarks>
 38    /// This is the surface normal if the matrix represents ground orientation.
 39    /// </remarks>
 40    /// <param name="matrix"></param>
 41    /// <returns>A <see cref="Vector3d"/> representing the up direction.</returns>
 642    public static Vector3d ExtractUp(Fixed4x4 matrix) => new Vector3d(matrix.M21, matrix.M22, matrix.M23).NormalizeInPla
 43
 44    /// <summary>
 45    /// Extracts the forward direction from the 4x4 matrix.
 46    /// </summary>
 647    public static Vector3d ExtractForward(Fixed4x4 matrix) => new Vector3d(matrix.M31, matrix.M32, matrix.M33).Normalize
 48
 49    /// <summary>
 50    /// Extracts the unsigned magnitudes of the matrix basis rows.
 51    /// </summary>
 52    /// <returns>The nonnegative basis magnitudes along X, Y, and Z.</returns>
 53    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 54    public static Vector3d ExtractScaleMagnitudes(Fixed4x4 matrix) =>
 8255        new(
 8256            new Vector3d(matrix.M11, matrix.M12, matrix.M13).Magnitude,
 8257            new Vector3d(matrix.M21, matrix.M22, matrix.M23).Magnitude,
 8258            new Vector3d(matrix.M31, matrix.M32, matrix.M33).Magnitude);
 59
 60    /// <summary>
 61    /// Extracts canonical signed lossy scale from the matrix basis rows.
 62    /// </summary>
 63    /// <remarks>A reflected basis assigns its one recoverable negative sign to X.</remarks>
 64    public static Vector3d ExtractLossyScale(Fixed4x4 matrix)
 65    {
 4466        Vector3d scale = ExtractScaleMagnitudes(matrix);
 4467        if (WideGeometry.GetTripleProductSign(
 4468            matrix.M11, matrix.M12, matrix.M13,
 4469            matrix.M21, matrix.M22, matrix.M23,
 4470            matrix.M31, matrix.M32, matrix.M33) < 0)
 71        {
 1472            scale.X = -scale.X;
 73        }
 74
 4475        return scale;
 76    }
 77
 78    /// <summary>
 79    /// Attempts to extract canonical signed lossy scale from the matrix basis rows.
 80    /// </summary>
 81    /// <remarks>
 82    /// This operation accepts affine, non-affine, singular, and sheared matrices.
 83    /// It fails only when at least one basis-row magnitude is outside the
 84    /// representable <see cref="Fixed64"/> range. A reflected basis assigns its
 85    /// one recoverable negative sign to X. Every failure writes zero.
 86    /// </remarks>
 87    public static bool TryExtractLossyScale(Fixed4x4 matrix, out Vector3d scale)
 88    {
 1189        bool representable =
 1190            Vector3d.TryGetMagnitude(
 1191                new Vector3d(matrix.M11, matrix.M12, matrix.M13),
 1192                out Fixed64 scaleX)
 1193            & Vector3d.TryGetMagnitude(
 1194                new Vector3d(matrix.M21, matrix.M22, matrix.M23),
 1195                out Fixed64 scaleY)
 1196            & Vector3d.TryGetMagnitude(
 1197                new Vector3d(matrix.M31, matrix.M32, matrix.M33),
 1198                out Fixed64 scaleZ);
 1199        if (!representable)
 100        {
 4101            scale = Vector3d.Zero;
 4102            return false;
 103        }
 104
 7105        scale = new Vector3d(scaleX, scaleY, scaleZ);
 7106        if (WideGeometry.GetTripleProductSign(
 7107            matrix.M11, matrix.M12, matrix.M13,
 7108            matrix.M21, matrix.M22, matrix.M23,
 7109            matrix.M31, matrix.M32, matrix.M33) < 0)
 110        {
 3111            scale.X = -scale.X;
 112        }
 113
 7114        return true;
 115    }
 116
 117    /// <summary>
 118    /// Extracts the rotation component from the 4x4 matrix by normalizing the rotation matrix.
 119    /// </summary>
 120    /// <param name="matrix">The matrix from which to extract the rotation.</param>
 121    /// <returns>A FixedQuaternion representing the rotation component.</returns>
 122    public static FixedQuaternion ExtractRotation(Fixed4x4 matrix)
 123    {
 12124        Vector3d scale = ExtractScaleMagnitudes(matrix);
 125
 126        // prevent divide by zero exception
 12127        Fixed64 scaleX = scale.X == Fixed64.Zero ? Fixed64.One : scale.X;
 12128        Fixed64 scaleY = scale.Y == Fixed64.Zero ? Fixed64.One : scale.Y;
 12129        Fixed64 scaleZ = scale.Z == Fixed64.Zero ? Fixed64.One : scale.Z;
 130
 12131        Fixed4x4 normalizedMatrix = new(
 12132            matrix.M11 / scaleX, matrix.M12 / scaleX, matrix.M13 / scaleX, Fixed64.Zero,
 12133            matrix.M21 / scaleY, matrix.M22 / scaleY, matrix.M23 / scaleY, Fixed64.Zero,
 12134            matrix.M31 / scaleZ, matrix.M32 / scaleZ, matrix.M33 / scaleZ, Fixed64.Zero,
 12135            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One
 12136        );
 137
 12138        return FixedQuaternion.FromMatrix(normalizedMatrix);
 139    }
 140
 141    /// <summary>
 142    /// Attempts to decompose an affine 4x4 matrix into strict translation,
 143    /// rotation, and canonical lossy-scale components.
 144    /// </summary>
 145    /// <remarks>
 146    /// Perspective, shear, singular or unrepresentable basis magnitudes, and
 147    /// normalized rotation bases that cannot round-trip within <see cref="Fixed64.Epsilon"/>
 148    /// are rejected. Reconstructed scaled coefficients must also be representable.
 149    /// For each row's extracted scale s and one raw unit q, coefficient reconstruction error
 150    /// is bounded by <c>abs(s) * (Epsilon + q/2) + q/2</c>.
 151    /// Reflections assign their single negative scale sign to X.
 152    /// Every failure writes zero translation, identity rotation, and unit scale.
 153    /// </remarks>
 154    /// <param name="matrix">The 4x4 matrix to decompose.</param>
 155    /// <param name="translation">The extracted translation component.</param>
 156    /// <param name="rotation">The extracted rotation component as a quaternion.</param>
 157    /// <param name="scale">The extracted scale component.</param>
 158    /// <returns>True if decomposition was successful, otherwise false.</returns>
 159    public static bool Decompose(
 160        Fixed4x4 matrix,
 161        out Vector3d translation,
 162        out FixedQuaternion rotation,
 163        out Vector3d scale)
 164    {
 345165        translation = Vector3d.Zero;
 345166        rotation = FixedQuaternion.Identity;
 345167        scale = Vector3d.One;
 168
 345169        if (!matrix.IsAffine)
 4170            return false;
 171
 341172        Vector3d basisX = new(matrix.M11, matrix.M12, matrix.M13);
 341173        Vector3d basisY = new(matrix.M21, matrix.M22, matrix.M23);
 341174        Vector3d basisZ = new(matrix.M31, matrix.M32, matrix.M33);
 341175        if (!Vector3d.TryGetMagnitude(basisX, out Fixed64 scaleX)
 341176            || !Vector3d.TryGetMagnitude(basisY, out Fixed64 scaleY)
 341177            || !Vector3d.TryGetMagnitude(basisZ, out Fixed64 scaleZ)
 341178            || scaleX == Fixed64.Zero
 341179            || scaleY == Fixed64.Zero
 341180            || scaleZ == Fixed64.Zero)
 181        {
 8182            return false;
 183        }
 184
 333185        basisX /= scaleX;
 333186        basisY /= scaleY;
 333187        basisZ /= scaleZ;
 333188        if (!IsNormalizedOrthogonalBasis(basisX, basisY, basisZ))
 18189            return false;
 190
 315191        int handedness = WideGeometry.GetTripleProductSign(
 315192            matrix.M11, matrix.M12, matrix.M13,
 315193            matrix.M21, matrix.M22, matrix.M23,
 315194            matrix.M31, matrix.M32, matrix.M33);
 315195        if (handedness < 0)
 196        {
 272197            scaleX = -scaleX;
 272198            basisX = -basisX;
 199        }
 200
 315201        Fixed3x3 normalizedBasis = new(
 315202            basisX,
 315203            basisY,
 315204            basisZ);
 315205        FixedQuaternion candidateRotation = FixedQuaternion.FromMatrix(normalizedBasis).Normalized;
 315206        Fixed3x3 reconstructed = candidateRotation.ToMatrix3x3();
 315207        if (!reconstructed.FuzzyEqualAbsolute(normalizedBasis, Fixed64.Epsilon))
 1208            return false;
 209
 314210        Vector3d candidateTranslation = new(matrix.M41, matrix.M42, matrix.M43);
 314211        Vector3d candidateScale = new(scaleX, scaleY, scaleZ);
 212        // The normalized gate bounds row error by |scale| * (Epsilon + half a raw unit)
 213        // plus final half-raw rounding. Only representability remains to be checked here.
 314214        if (!TryCreateTransform(
 314215            candidateTranslation,
 314216            reconstructed,
 314217            candidateScale,
 314218            out _))
 219        {
 4220            return false;
 221        }
 222
 310223        translation = candidateTranslation;
 310224        rotation = candidateRotation;
 310225        scale = candidateScale;
 310226        return true;
 227    }
 228
 229    private static bool IsNormalizedOrthogonalBasis(Vector3d x, Vector3d y, Vector3d z) =>
 333230        Vector3d.TryGetMagnitude(x, out Fixed64 xLength)
 333231        && Vector3d.TryGetMagnitude(y, out Fixed64 yLength)
 333232        && Vector3d.TryGetMagnitude(z, out Fixed64 zLength)
 333233        && FixedMath.Abs(xLength - Fixed64.One) <= Fixed64.Epsilon
 333234        && FixedMath.Abs(yLength - Fixed64.One) <= Fixed64.Epsilon
 333235        && FixedMath.Abs(zLength - Fixed64.One) <= Fixed64.Epsilon
 333236        && FixedMath.Abs(Vector3d.Dot(x, y)) <= Fixed64.Epsilon
 333237        && FixedMath.Abs(Vector3d.Dot(x, z)) <= Fixed64.Epsilon
 333238        && FixedMath.Abs(Vector3d.Dot(y, z)) <= Fixed64.Epsilon;
 239
 240    /// <summary>
 241    /// Sets the translation component of the 4x4 matrix.
 242    /// </summary>
 243    /// <param name="matrix">The matrix to modify.</param>
 244    /// <param name="translation">The new translation vector.</param>
 245    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 246    public static Fixed4x4 SetTranslation(Fixed4x4 matrix, Vector3d translation)
 247    {
 1248        matrix.M41 = translation.X;
 1249        matrix.M42 = translation.Y;
 1250        matrix.M43 = translation.Z;
 1251        return matrix;
 252    }
 253
 254    /// <summary>
 255    /// Applies non-uniform scaling to the 4x4 matrix by multiplying the scale vector with the rotation matrix's basis v
 256    /// </summary>
 257    /// <param name="matrix">The matrix to modify. Should already contain a valid rotation component.</param>
 258    /// <param name="scale">The scale vector to apply along the X, Y, and Z axes.</param>
 259    /// <remarks>
 260    /// Use this method when scaling is required in combination with an existing rotation, ensuring proper axis alignmen
 261    /// </remarks>
 262    public static Fixed4x4 ApplyScaleToRotation(Fixed4x4 matrix, Vector3d scale)
 263    {
 264        // Scale each row of the rotation matrix
 1265        matrix.M11 *= scale.X;
 1266        matrix.M12 *= scale.X;
 1267        matrix.M13 *= scale.X;
 268
 1269        matrix.M21 *= scale.Y;
 1270        matrix.M22 *= scale.Y;
 1271        matrix.M23 *= scale.Y;
 272
 1273        matrix.M31 *= scale.Z;
 1274        matrix.M32 *= scale.Z;
 1275        matrix.M33 *= scale.Z;
 276
 1277        return matrix;
 278    }
 279
 280    /// <summary>
 281    /// Replaces the rotation component of the 4x4 matrix using the provided quaternion, without affecting the translati
 282    /// </summary>
 283    /// <param name="matrix">The matrix to modify. The rotation will replace the upper-left 3x3 portion of the matrix.</
 284    /// <param name="rotation">The quaternion representing the new rotation to apply.</param>
 285    /// <remarks>
 286    /// Quaternion magnitude does not affect the replacement rotation; zero
 287    /// represents identity. This method preserves the matrix's translation
 288    /// component. For complete transformation updates, use <see cref="SetTransform"/>.
 289    /// </remarks>
 290    public static Fixed4x4 SetRotation(Fixed4x4 matrix, FixedQuaternion rotation)
 291    {
 6292        Fixed3x3 rotationMatrix = rotation.ToMatrix3x3();
 293
 6294        Vector3d scale = ExtractScaleMagnitudes(matrix);
 295
 296        // Apply rotation to the upper-left 3x3 matrix
 297
 6298        matrix.M11 = rotationMatrix.M11 * scale.X;
 6299        matrix.M12 = rotationMatrix.M12 * scale.X;
 6300        matrix.M13 = rotationMatrix.M13 * scale.X;
 301
 6302        matrix.M21 = rotationMatrix.M21 * scale.Y;
 6303        matrix.M22 = rotationMatrix.M22 * scale.Y;
 6304        matrix.M23 = rotationMatrix.M23 * scale.Y;
 305
 6306        matrix.M31 = rotationMatrix.M31 * scale.Z;
 6307        matrix.M32 = rotationMatrix.M32 * scale.Z;
 6308        matrix.M33 = rotationMatrix.M33 * scale.Z;
 309
 6310        return matrix;
 311    }
 312
 313    /// <summary>
 314    /// Normalizes the rotation component of a 4x4 matrix by ensuring the basis vectors are orthogonal and unit length.
 315    /// </summary>
 316    /// <remarks>
 317    /// This method recalculates the X, Y, and Z basis vectors from the upper-left 3x3 portion of the matrix, ensuring t
 318    /// The remaining components of the matrix are reset to maintain a valid transformation structure.
 319    ///
 320    /// Use Cases:
 321    /// - Ensuring the rotation component remains stable and accurate after multiple transformations.
 322    /// - Used in 3D transformations to prevent numerical drift from affecting the orientation over time.
 323    /// - Essential for cases where precise orientation is required, such as animations or physics simulations.
 324    /// </remarks>
 325    public static Fixed4x4 NormalizeRotationMatrix(Fixed4x4 matrix)
 326    {
 1327        Vector3d basisX = new Vector3d(matrix.M11, matrix.M12, matrix.M13).NormalizeInPlace();
 1328        Vector3d basisY = new Vector3d(matrix.M21, matrix.M22, matrix.M23).NormalizeInPlace();
 1329        Vector3d basisZ = new Vector3d(matrix.M31, matrix.M32, matrix.M33).NormalizeInPlace();
 330
 1331        return new Fixed4x4(
 1332            basisX.X, basisX.Y, basisX.Z, Fixed64.Zero,
 1333            basisY.X, basisY.Y, basisY.Z, Fixed64.Zero,
 1334            basisZ.X, basisZ.Y, basisZ.Z, Fixed64.Zero,
 1335            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One
 1336        );
 337    }
 338
 339    #endregion
 340}

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

#LineLine coverage
 1//=======================================================================
 2// Fixed4x4.Equality.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Globalization;
 10using System.Runtime.CompilerServices;
 11
 12namespace FixedMathSharp;
 13
 14/// <content>
 15/// Provides equality comparison, hash code generation, and string formatting for <see cref="Fixed4x4"/>.
 16/// </content>
 17public partial struct Fixed4x4
 18{
 19    #region Equality and HashCode Overrides
 20
 21    /// <inheritdoc/>
 22    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 523    public override bool Equals(object? obj) => obj is Fixed4x4 x && Equals(x);
 24
 25    /// <inheritdoc/>
 26    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 27    public bool Equals(Fixed4x4 other) =>
 5328        M11 == other.M11 && M12 == other.M12 && M13 == other.M13 && M14 == other.M14 &&
 5329        M21 == other.M21 && M22 == other.M22 && M23 == other.M23 && M24 == other.M24 &&
 5330        M31 == other.M31 && M32 == other.M32 && M33 == other.M33 && M34 == other.M34 &&
 5331        M41 == other.M41 && M42 == other.M42 && M43 == other.M43 && M44 == other.M44;
 32
 33    /// <inheritdoc/>
 34    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 35    public override int GetHashCode()
 36    {
 37        unchecked
 38        {
 439            int hash = 17;
 440            hash = hash * 23 + M11.GetHashCode();
 441            hash = hash * 23 + M12.GetHashCode();
 442            hash = hash * 23 + M13.GetHashCode();
 443            hash = hash * 23 + M14.GetHashCode();
 444            hash = hash * 23 + M21.GetHashCode();
 445            hash = hash * 23 + M22.GetHashCode();
 446            hash = hash * 23 + M23.GetHashCode();
 447            hash = hash * 23 + M24.GetHashCode();
 448            hash = hash * 23 + M31.GetHashCode();
 449            hash = hash * 23 + M32.GetHashCode();
 450            hash = hash * 23 + M33.GetHashCode();
 451            hash = hash * 23 + M34.GetHashCode();
 452            hash = hash * 23 + M41.GetHashCode();
 453            hash = hash * 23 + M42.GetHashCode();
 454            hash = hash * 23 + M43.GetHashCode();
 455            hash = hash * 23 + M44.GetHashCode();
 456            return hash;
 57        }
 58    }
 59
 60    /// <summary>
 61    /// Returns a string that represents the current matrix in a readable format.
 62    /// </summary>
 63    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 8864    public override string ToString() => ToString(null, CultureInfo.InvariantCulture);
 65
 66    /// <summary>
 67    /// Returns a string that represents the current matrix in a readable format.
 68    /// </summary>
 69    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 70    public string ToString(string? format, IFormatProvider? formatProvider)
 71    {
 12072        Fixed4x4 value = this;
 12073        return FixedDiagnosticsFormatter.ToString((Span<char> destination, out int charsWritten) =>
 12074            value.TryFormat(destination, out charsWritten, format.AsSpan(), formatProvider));
 75    }
 76
 77    /// <summary>
 78    /// Formats this matrix into the provided destination buffer.
 79    /// </summary>
 80    public bool TryFormat(
 81        Span<char> destination,
 82        out int charsWritten,
 83        ReadOnlySpan<char> format,
 84        IFormatProvider? provider)
 85    {
 23386        int written = 0;
 23387        if (!FixedDiagnosticsFormatter.Append('[', destination, ref written) ||
 23388            !AppendRow(M11, M12, M13, M14, destination, ref written, format, provider) ||
 23389            !FixedDiagnosticsFormatter.Append("; ", destination, ref written) ||
 23390            !AppendRow(M21, M22, M23, M24, destination, ref written, format, provider) ||
 23391            !FixedDiagnosticsFormatter.Append("; ", destination, ref written) ||
 23392            !AppendRow(M31, M32, M33, M34, destination, ref written, format, provider) ||
 23393            !FixedDiagnosticsFormatter.Append("; ", destination, ref written) ||
 23394            !AppendRow(M41, M42, M43, M44, destination, ref written, format, provider) ||
 23395            !FixedDiagnosticsFormatter.Append(']', destination, ref written))
 96        {
 11297            charsWritten = 0;
 11298            return false;
 99        }
 100
 121101        charsWritten = written;
 121102        return true;
 103    }
 104
 105    private static bool AppendRow(
 106        Fixed64 x,
 107        Fixed64 y,
 108        Fixed64 z,
 109        Fixed64 w,
 110        Span<char> destination,
 111        ref int charsWritten,
 112        ReadOnlySpan<char> format,
 113        IFormatProvider? provider)
 114    {
 769115        return FixedDiagnosticsFormatter.Append(x, destination, ref charsWritten, format, provider) &&
 769116               FixedDiagnosticsFormatter.Append(", ", destination, ref charsWritten) &&
 769117               FixedDiagnosticsFormatter.Append(y, destination, ref charsWritten, format, provider) &&
 769118               FixedDiagnosticsFormatter.Append(", ", destination, ref charsWritten) &&
 769119               FixedDiagnosticsFormatter.Append(z, destination, ref charsWritten, format, provider) &&
 769120               FixedDiagnosticsFormatter.Append(", ", destination, ref charsWritten) &&
 769121               FixedDiagnosticsFormatter.Append(w, destination, ref charsWritten, format, provider);
 122    }
 123
 124    #endregion
 125}

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

#LineLine coverage
 1//=======================================================================
 2// Fixed4x4.Factories.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Runtime.CompilerServices;
 10
 11namespace FixedMathSharp;
 12
 13/// <content>
 14/// Factory methods for constructing <see cref="Fixed4x4"/> matrices, including
 15/// translation, rotation, scale, and other common transformation matrices.
 16/// </content>
 17public partial struct Fixed4x4
 18{
 19    #region Static Matrix Generators and Transformations
 20
 21    /// <summary>
 22    /// Creates a translation matrix from the specified 3-dimensional vector.
 23    /// </summary>
 24    /// <param name="position"></param>
 25    /// <returns>The translation matrix.</returns>
 26    public static Fixed4x4 CreateTranslation(Vector3d position)
 27    {
 1328        Fixed4x4 result = default;
 1329        result.M11 = Fixed64.One;
 1330        result.M12 = Fixed64.Zero;
 1331        result.M13 = Fixed64.Zero;
 1332        result.M14 = Fixed64.Zero;
 1333        result.M21 = Fixed64.Zero;
 1334        result.M22 = Fixed64.One;
 1335        result.M23 = Fixed64.Zero;
 1336        result.M24 = Fixed64.Zero;
 1337        result.M31 = Fixed64.Zero;
 1338        result.M32 = Fixed64.Zero;
 1339        result.M33 = Fixed64.One;
 1340        result.M34 = Fixed64.Zero;
 1341        result.M41 = position.X;
 1342        result.M42 = position.Y;
 1343        result.M43 = position.Z;
 1344        result.M44 = Fixed64.One;
 1345        return result;
 46    }
 47
 48    /// <summary>
 49    /// Creates a translation matrix from the specified coordinates.
 50    /// </summary>
 51    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 52    public static Fixed4x4 CreateTranslation(Fixed64 x, Fixed64 y, Fixed64 z) =>
 153        CreateTranslation(new Vector3d(x, y, z));
 54
 55    /// <summary>
 56    /// Creates a rotation matrix from a quaternion.
 57    /// </summary>
 58    /// <remarks>Quaternion magnitude does not affect the result; zero represents identity.</remarks>
 59    /// <param name="rotation">The quaternion representing the rotation.</param>
 60    /// <returns>A 4x4 matrix representing the rotation.</returns>
 61    public static Fixed4x4 CreateRotation(FixedQuaternion rotation)
 62    {
 1763        Fixed3x3 rotationMatrix = rotation.ToMatrix3x3();
 64
 1765        return new Fixed4x4(
 1766            rotationMatrix.M11, rotationMatrix.M12, rotationMatrix.M13, Fixed64.Zero,
 1767            rotationMatrix.M21, rotationMatrix.M22, rotationMatrix.M23, Fixed64.Zero,
 1768            rotationMatrix.M31, rotationMatrix.M32, rotationMatrix.M33, Fixed64.Zero,
 1769            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One);
 70    }
 71
 72    /// <summary>
 73    /// Creates a rotation matrix around the X axis.
 74    /// </summary>
 75    public static Fixed4x4 CreateRotationX(Fixed64 angle) =>
 676        FromRotationMatrix(Fixed3x3.CreateRotationX(angle));
 77
 78    /// <summary>
 79    /// Creates a rotation matrix around the Y axis.
 80    /// </summary>
 81    public static Fixed4x4 CreateRotationY(Fixed64 angle) =>
 682        FromRotationMatrix(Fixed3x3.CreateRotationY(angle));
 83
 84    /// <summary>
 85    /// Creates a rotation matrix around the Z axis.
 86    /// </summary>
 87    public static Fixed4x4 CreateRotationZ(Fixed64 angle) =>
 588        FromRotationMatrix(Fixed3x3.CreateRotationZ(angle));
 89
 90    /// <summary>
 91    /// Creates a rotation matrix from an axis and angle.
 92    /// </summary>
 93    public static Fixed4x4 CreateFromAxisAngle(Vector3d axis, Fixed64 angle) =>
 194        CreateRotation(FixedQuaternion.FromAxisAngle(axis, angle));
 95
 96    /// <summary>
 97    /// Creates a rotation matrix from pitch, yaw, and roll angles in radians.
 98    /// </summary>
 99    public static Fixed4x4 CreateFromEulerAngles(Fixed64 pitch, Fixed64 yaw, Fixed64 roll) =>
 1100        CreateRotation(FixedQuaternion.FromEulerAngles(pitch, yaw, roll));
 101
 102    /// <summary>
 103    /// Creates a scale matrix from a 3-dimensional vector.
 104    /// </summary>
 105    /// <param name="scale">The vector representing the scale along each axis.</param>
 106    /// <returns>A 4x4 matrix representing the scale transformation.</returns>
 107    public static Fixed4x4 CreateScale(Vector3d scale) =>
 37108        new(
 37109            scale.X, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 37110            Fixed64.Zero, scale.Y, Fixed64.Zero, Fixed64.Zero,
 37111            Fixed64.Zero, Fixed64.Zero, scale.Z, Fixed64.Zero,
 37112            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One);
 113
 114    /// <summary>
 115    /// Creates a uniform scale matrix.
 116    /// </summary>
 117    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1118    public static Fixed4x4 CreateScale(Fixed64 scale) => CreateScale(new Vector3d(scale, scale, scale));
 119
 120    /// <summary>
 121    /// Creates a non-uniform scale matrix from individual scale components.
 122    /// </summary>
 123    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1124    public static Fixed4x4 CreateScale(Fixed64 x, Fixed64 y, Fixed64 z) => CreateScale(new Vector3d(x, y, z));
 125
 126    /// <summary>
 127    /// Creates a view matrix looking from a camera position toward a target.
 128    /// </summary>
 129    /// <remarks>
 130    /// The view direction is expressed in FixedMathSharp's canonical <c>+Z</c>-forward basis and
 131    /// the resulting matrix follows the row-vector convention used by <see cref="TransformPoint(Fixed4x4, Vector3d)"/>.
 132    /// Convert engine-specific camera conventions at adapter boundaries.
 133    /// </remarks>
 134    public static Fixed4x4 CreateLookAt(Vector3d cameraPosition, Vector3d cameraTarget, Vector3d cameraUpVector)
 135    {
 4136        Vector3d forward = cameraTarget - cameraPosition;
 137
 4138        if (forward.MagnitudeSquared == Fixed64.Zero)
 1139            throw new ArgumentException("Camera position and target must be different.");
 140
 3141        forward = forward.NormalizeInPlace();
 3142        Vector3d right = Vector3d.Cross(cameraUpVector, forward);
 143
 3144        if (right.MagnitudeSquared == Fixed64.Zero)
 1145            throw new ArgumentException("Camera up vector must not be parallel to the view direction.");
 146
 2147        right = right.NormalizeInPlace();
 2148        Vector3d up = Vector3d.Cross(forward, right).NormalizeInPlace();
 149
 2150        return new Fixed4x4(
 2151            right.X, right.Y, right.Z, Fixed64.Zero,
 2152            up.X, up.Y, up.Z, Fixed64.Zero,
 2153            forward.X, forward.Y, forward.Z, Fixed64.Zero,
 2154            -Vector3d.Dot(right, cameraPosition),
 2155            -Vector3d.Dot(up, cameraPosition),
 2156            -Vector3d.Dot(forward, cameraPosition),
 2157            Fixed64.One);
 158    }
 159
 160    /// <summary>
 161    /// Creates an orthographic projection matrix centered on the origin.
 162    /// </summary>
 163    public static Fixed4x4 CreateOrthographic(Fixed64 width, Fixed64 height, Fixed64 zNearPlane, Fixed64 zFarPlane)
 164    {
 5165        if (width <= Fixed64.Zero)
 1166            throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than zero.");
 167
 4168        if (height <= Fixed64.Zero)
 1169            throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than zero.");
 170
 3171        Fixed64 halfWidth = width * Fixed64.Half;
 3172        Fixed64 halfHeight = height * Fixed64.Half;
 3173        return CreateOrthographicOffCenter(-halfWidth, halfWidth, -halfHeight, halfHeight, zNearPlane, zFarPlane);
 174    }
 175
 176    /// <summary>
 177    /// Creates an off-center orthographic projection matrix.
 178    /// </summary>
 179    public static Fixed4x4 CreateOrthographicOffCenter(
 180        Fixed64 left,
 181        Fixed64 right,
 182        Fixed64 bottom,
 183        Fixed64 top,
 184        Fixed64 zNearPlane,
 185        Fixed64 zFarPlane)
 186    {
 8187        if (left == right)
 1188            throw new ArgumentOutOfRangeException(nameof(right), "Right must be different from left.");
 189
 7190        if (bottom == top)
 1191            throw new ArgumentOutOfRangeException(nameof(top), "Top must be different from bottom.");
 192
 6193        ValidateDepthRange(zNearPlane, zFarPlane);
 194
 2195        Fixed64 width = right - left;
 2196        Fixed64 height = top - bottom;
 2197        Fixed64 depth = zFarPlane - zNearPlane;
 198
 2199        return new Fixed4x4(
 2200            Fixed64.Two / width, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 2201            Fixed64.Zero, Fixed64.Two / height, Fixed64.Zero, Fixed64.Zero,
 2202            Fixed64.Zero, Fixed64.Zero, Fixed64.One / depth, Fixed64.Zero,
 2203            (left + right) / (left - right),
 2204            (top + bottom) / (bottom - top),
 2205            -zNearPlane / depth,
 2206            Fixed64.One);
 207    }
 208
 209    /// <summary>
 210    /// Creates a perspective projection matrix centered on the near plane.
 211    /// </summary>
 212    public static Fixed4x4 CreatePerspective(
 213        Fixed64 width,
 214        Fixed64 height,
 215        Fixed64 nearPlaneDistance,
 216        Fixed64 farPlaneDistance)
 217    {
 5218        if (width <= Fixed64.Zero)
 1219            throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than zero.");
 220
 4221        if (height <= Fixed64.Zero)
 1222            throw new ArgumentOutOfRangeException(nameof(height), "Height must be greater than zero.");
 223
 3224        ValidatePerspectiveDepthRange(nearPlaneDistance, farPlaneDistance);
 225
 1226        Fixed64 depth = farPlaneDistance - nearPlaneDistance;
 1227        Fixed64 twoNear = Fixed64.Two * nearPlaneDistance;
 228
 1229        return new Fixed4x4(
 1230            twoNear / width, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 1231            Fixed64.Zero, twoNear / height, Fixed64.Zero, Fixed64.Zero,
 1232            Fixed64.Zero, Fixed64.Zero, farPlaneDistance / depth, Fixed64.One,
 1233            Fixed64.Zero, Fixed64.Zero, -(nearPlaneDistance * farPlaneDistance) / depth, Fixed64.Zero);
 234    }
 235
 236    /// <summary>
 237    /// Creates a perspective projection matrix from a vertical field of view.
 238    /// </summary>
 239    public static Fixed4x4 CreatePerspectiveFieldOfView(
 240        Fixed64 fieldOfView,
 241        Fixed64 aspectRatio,
 242        Fixed64 nearPlaneDistance,
 243        Fixed64 farPlaneDistance)
 244    {
 7245        if (fieldOfView <= Fixed64.Zero || fieldOfView >= Fixed64.Pi)
 2246            throw new ArgumentOutOfRangeException(nameof(fieldOfView), "Field of view must be greater than zero and less
 247
 5248        if (aspectRatio <= Fixed64.Zero)
 1249            throw new ArgumentOutOfRangeException(nameof(aspectRatio), "Aspect ratio must be greater than zero.");
 250
 4251        ValidatePerspectiveDepthRange(nearPlaneDistance, farPlaneDistance);
 252
 2253        Fixed64 yScale = Fixed64.One / FixedMath.Tan(fieldOfView * Fixed64.Half);
 2254        Fixed64 xScale = yScale / aspectRatio;
 2255        Fixed64 depth = farPlaneDistance - nearPlaneDistance;
 256
 2257        return new Fixed4x4(
 2258            xScale, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 2259            Fixed64.Zero, yScale, Fixed64.Zero, Fixed64.Zero,
 2260            Fixed64.Zero, Fixed64.Zero, farPlaneDistance / depth, Fixed64.One,
 2261            Fixed64.Zero, Fixed64.Zero, -(nearPlaneDistance * farPlaneDistance) / depth, Fixed64.Zero);
 262    }
 263
 264    /// <summary>
 265    /// Creates an off-center perspective projection matrix.
 266    /// </summary>
 267    public static Fixed4x4 CreatePerspectiveOffCenter(
 268        Fixed64 left,
 269        Fixed64 right,
 270        Fixed64 bottom,
 271        Fixed64 top,
 272        Fixed64 nearPlaneDistance,
 273        Fixed64 farPlaneDistance)
 274    {
 5275        if (left == right)
 1276            throw new ArgumentOutOfRangeException(nameof(right), "Right must be different from left.");
 277
 4278        if (bottom == top)
 1279            throw new ArgumentOutOfRangeException(nameof(top), "Top must be different from bottom.");
 280
 3281        ValidatePerspectiveDepthRange(nearPlaneDistance, farPlaneDistance);
 282
 1283        Fixed64 width = right - left;
 1284        Fixed64 height = top - bottom;
 1285        Fixed64 depth = farPlaneDistance - nearPlaneDistance;
 1286        Fixed64 twoNear = Fixed64.Two * nearPlaneDistance;
 287
 1288        return new Fixed4x4(
 1289            twoNear / width, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 1290            Fixed64.Zero, twoNear / height, Fixed64.Zero, Fixed64.Zero,
 1291            (left + right) / (left - right),
 1292            (top + bottom) / (bottom - top),
 1293            farPlaneDistance / depth,
 1294            Fixed64.One,
 1295            Fixed64.Zero, Fixed64.Zero, -(nearPlaneDistance * farPlaneDistance) / depth, Fixed64.Zero);
 296    }
 297
 298    /// <summary>
 299    /// Creates a world matrix from a position and orientation basis.
 300    /// </summary>
 301    /// <remarks>
 302    /// The <paramref name="forward"/> and <paramref name="up"/> vectors are semantic basis vectors
 303    /// in FixedMathSharp's canonical coordinate space. The returned matrix stores right, up, and
 304    /// forward basis rows for row-vector transforms.
 305    /// </remarks>
 306    public static Fixed4x4 CreateWorld(Vector3d position, Vector3d forward, Vector3d up)
 307    {
 3308        if (forward.MagnitudeSquared == Fixed64.Zero)
 1309            throw new ArgumentException("Forward vector must be non-zero.");
 310
 2311        forward = forward.NormalizeInPlace();
 2312        Vector3d right = Vector3d.Cross(up, forward);
 313
 2314        if (right.MagnitudeSquared == Fixed64.Zero)
 1315            throw new ArgumentException("Up vector must not be parallel to forward.");
 316
 1317        right = right.NormalizeInPlace();
 1318        up = Vector3d.Cross(forward, right).NormalizeInPlace();
 319
 1320        return new Fixed4x4(
 1321            right.X, right.Y, right.Z, Fixed64.Zero,
 1322            up.X, up.Y, up.Z, Fixed64.Zero,
 1323            forward.X, forward.Y, forward.Z, Fixed64.Zero,
 1324            position.X, position.Y, position.Z, Fixed64.One);
 325    }
 326
 327    /// <summary>
 328    /// Constructs a transformation matrix from translation, scale, and rotation.
 329    /// This method ensures that the rotation is properly normalized, applies the scale to the
 330    /// rotational basis, and sets the translation component separately.
 331    /// </summary>
 332    /// <remarks>
 333    /// - Quaternion magnitude does not affect the rotation matrix; zero represents identity.
 334    /// - Applies non-uniform scaling to the rotation before setting translation.
 335    /// - Preferred when ensuring transformations remain mathematically correct.
 336    /// - For explicit matrix-composition order, see <see cref="ScaleRotateTranslate"/>.
 337    /// </remarks>
 338    /// <param name="translation">The translation vector.</param>
 339    /// <param name="scale">The scale vector.</param>
 340    /// <param name="rotation">The rotation quaternion.</param>
 341    /// <returns>A transformation matrix incorporating translation, rotation, and scale.</returns>
 342    public static Fixed4x4 CreateTransform(Vector3d translation, FixedQuaternion rotation, Vector3d scale) =>
 28563343        CreateTransform(translation, rotation.ToMatrix3x3(), scale);
 344
 345    private static Fixed4x4 CreateTransform(Vector3d translation, Fixed3x3 rotation, Vector3d scale)
 346    {
 28563347        return new Fixed4x4(
 28563348            rotation.M11 * scale.X, rotation.M12 * scale.X, rotation.M13 * scale.X, Fixed64.Zero,
 28563349            rotation.M21 * scale.Y, rotation.M22 * scale.Y, rotation.M23 * scale.Y, Fixed64.Zero,
 28563350            rotation.M31 * scale.Z, rotation.M32 * scale.Z, rotation.M33 * scale.Z, Fixed64.Zero,
 28563351            translation.X, translation.Y, translation.Z, Fixed64.One);
 352    }
 353
 354    // Shared checked TRS construction keeps decomposition and hierarchy materialization nonsaturating.
 355    internal static bool TryCreateTransform(
 356        Vector3d translation, Fixed3x3 rotation, Vector3d scale, out Fixed4x4 matrix)
 357    {
 4745358        bool representable = Fixed64.TryMultiplyAdd(rotation.M11, scale.X, Fixed64.Zero, out Fixed64 m11)
 4745359            & Fixed64.TryMultiplyAdd(rotation.M12, scale.X, Fixed64.Zero, out Fixed64 m12)
 4745360            & Fixed64.TryMultiplyAdd(rotation.M13, scale.X, Fixed64.Zero, out Fixed64 m13)
 4745361            & Fixed64.TryMultiplyAdd(rotation.M21, scale.Y, Fixed64.Zero, out Fixed64 m21)
 4745362            & Fixed64.TryMultiplyAdd(rotation.M22, scale.Y, Fixed64.Zero, out Fixed64 m22)
 4745363            & Fixed64.TryMultiplyAdd(rotation.M23, scale.Y, Fixed64.Zero, out Fixed64 m23)
 4745364            & Fixed64.TryMultiplyAdd(rotation.M31, scale.Z, Fixed64.Zero, out Fixed64 m31)
 4745365            & Fixed64.TryMultiplyAdd(rotation.M32, scale.Z, Fixed64.Zero, out Fixed64 m32)
 4745366            & Fixed64.TryMultiplyAdd(rotation.M33, scale.Z, Fixed64.Zero, out Fixed64 m33);
 4745367        if (!representable)
 368        {
 11369            matrix = Fixed4x4.Zero;
 11370            return false;
 371        }
 372
 4734373        matrix = new Fixed4x4(
 4734374            m11, m12, m13, Fixed64.Zero,
 4734375            m21, m22, m23, Fixed64.Zero,
 4734376            m31, m32, m33, Fixed64.Zero,
 4734377            translation.X, translation.Y, translation.Z, Fixed64.One);
 4734378        return true;
 379    }
 380
 381    /// <summary>
 382    /// Constructs a transformation matrix from translation, rotation, and scale by multiplying
 383    /// separate matrices in the order: Scale * Rotation * Translation.
 384    /// </summary>
 385    /// <remarks>
 386    /// - This method directly multiplies the scale, rotation, and translation matrices.
 387    /// - Ensures that scale is applied first to preserve correct axis scaling.
 388    /// - Then rotation is applied so that rotation is not affected by non-uniform scaling.
 389    /// - Finally, translation moves the object to its correct world position.
 390    /// </remarks>
 391    public static Fixed4x4 ScaleRotateTranslate(Vector3d translation, FixedQuaternion rotation, Vector3d scale)
 392    {
 9393        return CreateTransform(translation, rotation, scale);
 394    }
 395
 396    /// <summary>
 397    /// Constructs a transformation matrix from translation, rotation, and scale by multiplying
 398    /// matrices in the order: Translation * Rotation * Scale (T * R * S).
 399    /// </summary>
 400    /// <remarks>
 401    /// - Use this method when transformations need to be applied **relative to an object's local origin**.
 402    /// - Example use cases include **animation systems**, **hierarchical transformations**, and **UI transformations**.
 403    /// - If you need to apply world-space transformations, use
 404    ///   <see cref="CreateTransform(Vector3d, FixedQuaternion, Vector3d)"/> instead.
 405    /// - Quaternion magnitude does not affect the rotation matrix; zero represents identity.
 406    /// </remarks>
 407    public static Fixed4x4 TranslateRotateScale(Vector3d translation, FixedQuaternion rotation, Vector3d scale)
 408    {
 5409        Fixed3x3 rotationMatrix = rotation.ToMatrix3x3();
 410
 5411        return new Fixed4x4(
 5412            rotationMatrix.M11 * scale.X, rotationMatrix.M12 * scale.Y, rotationMatrix.M13 * scale.Z, Fixed64.Zero,
 5413            rotationMatrix.M21 * scale.X, rotationMatrix.M22 * scale.Y, rotationMatrix.M23 * scale.Z, Fixed64.Zero,
 5414            rotationMatrix.M31 * scale.X, rotationMatrix.M32 * scale.Y, rotationMatrix.M33 * scale.Z, Fixed64.Zero,
 5415            (translation.X * rotationMatrix.M11 + translation.Y * rotationMatrix.M21 + translation.Z * rotationMatrix.M3
 5416            (translation.X * rotationMatrix.M12 + translation.Y * rotationMatrix.M22 + translation.Z * rotationMatrix.M3
 5417            (translation.X * rotationMatrix.M13 + translation.Y * rotationMatrix.M23 + translation.Z * rotationMatrix.M3
 5418            Fixed64.One);
 419    }
 420
 421    #endregion
 422}

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

#LineLine coverage
 1//=======================================================================
 2// Fixed4x4.Helpers.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9
 10namespace FixedMathSharp;
 11
 12/// <content>
 13/// Internal helper methods for constructing and validating <see cref="Fixed4x4"/> matrices.
 14/// </content>
 15public partial struct Fixed4x4
 16{
 17    #region Private Helpers
 18
 19    private static Fixed4x4 FromRotationMatrix(Fixed3x3 matrix) =>
 1720        new(
 1721            matrix.M11, matrix.M12, matrix.M13, Fixed64.Zero,
 1722            matrix.M21, matrix.M22, matrix.M23, Fixed64.Zero,
 1723            matrix.M31, matrix.M32, matrix.M33, Fixed64.Zero,
 1724            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One);
 25
 26    private static void ValidateDepthRange(Fixed64 nearPlaneDistance, Fixed64 farPlaneDistance)
 27    {
 628        if (nearPlaneDistance < Fixed64.Zero)
 229            throw new ArgumentOutOfRangeException(nameof(nearPlaneDistance), "Near plane distance must be greater than o
 30
 431        if (farPlaneDistance <= nearPlaneDistance)
 232            throw new ArgumentOutOfRangeException(nameof(farPlaneDistance), "Far plane distance must be greater than or 
 233    }
 34
 35    private static void ValidatePerspectiveDepthRange(Fixed64 nearPlaneDistance, Fixed64 farPlaneDistance)
 36    {
 1037        if (nearPlaneDistance <= Fixed64.Zero)
 338            throw new ArgumentOutOfRangeException(nameof(nearPlaneDistance), "Near plane distance must be greater than z
 39
 740        if (farPlaneDistance <= nearPlaneDistance)
 341            throw new ArgumentOutOfRangeException(nameof(farPlaneDistance), "Far plane distance must be greater than nea
 442    }
 43
 44    #endregion
 45}

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

#LineLine coverage
 1//=======================================================================
 2// Fixed4x4.Operators.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System.Runtime.CompilerServices;
 9
 10namespace FixedMathSharp;
 11
 12/// <content>
 13/// Operator overloads for <see cref="Fixed4x4"/>, including negation, addition,
 14/// subtraction, and multiplication.
 15/// </content>
 16public partial struct Fixed4x4
 17{
 18    #region Operators
 19
 20    /// <summary>
 21    /// Negates the specified matrix by multiplying all its values by -1.
 22    /// </summary>
 23    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 24    public static Fixed4x4 operator -(Fixed4x4 value)
 25    {
 126        Fixed4x4 result = default;
 127        result.M11 = -value.M11;
 128        result.M12 = -value.M12;
 129        result.M13 = -value.M13;
 130        result.M14 = -value.M14;
 131        result.M21 = -value.M21;
 132        result.M22 = -value.M22;
 133        result.M23 = -value.M23;
 134        result.M24 = -value.M24;
 135        result.M31 = -value.M31;
 136        result.M32 = -value.M32;
 137        result.M33 = -value.M33;
 138        result.M34 = -value.M34;
 139        result.M41 = -value.M41;
 140        result.M42 = -value.M42;
 141        result.M43 = -value.M43;
 142        result.M44 = -value.M44;
 143        return result;
 44    }
 45
 46    /// <summary>
 47    /// Adds two matrices element-wise.
 48    /// </summary>
 49    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 50    public static Fixed4x4 operator +(Fixed4x4 lhs, Fixed4x4 rhs) =>
 351        new(
 352            lhs.M11 + rhs.M11, lhs.M12 + rhs.M12, lhs.M13 + rhs.M13, lhs.M14 + rhs.M14,
 353            lhs.M21 + rhs.M21, lhs.M22 + rhs.M22, lhs.M23 + rhs.M23, lhs.M24 + rhs.M24,
 354            lhs.M31 + rhs.M31, lhs.M32 + rhs.M32, lhs.M33 + rhs.M33, lhs.M34 + rhs.M34,
 355            lhs.M41 + rhs.M41, lhs.M42 + rhs.M42, lhs.M43 + rhs.M43, lhs.M44 + rhs.M44);
 56
 57    /// <summary>
 58    /// Subtracts two matrices element-wise.
 59    /// </summary>
 60    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 61    public static Fixed4x4 operator -(Fixed4x4 lhs, Fixed4x4 rhs) =>
 162        new(
 163            lhs.M11 - rhs.M11, lhs.M12 - rhs.M12, lhs.M13 - rhs.M13, lhs.M14 - rhs.M14,
 164            lhs.M21 - rhs.M21, lhs.M22 - rhs.M22, lhs.M23 - rhs.M23, lhs.M24 - rhs.M24,
 165            lhs.M31 - rhs.M31, lhs.M32 - rhs.M32, lhs.M33 - rhs.M33, lhs.M34 - rhs.M34,
 166            lhs.M41 - rhs.M41, lhs.M42 - rhs.M42, lhs.M43 - rhs.M43, lhs.M44 - rhs.M44);
 67
 68    /// <summary>
 69    /// Attempts to multiply two matrices with one exact, round-half-to-even conversion per cell.
 70    /// </summary>
 71    /// <remarks>
 72    /// Unlike <see cref="operator *(Fixed4x4, Fixed4x4)"/>, this method does not saturate
 73    /// intermediate products or partial sums. On failure, <paramref name="result"/> is zero.
 74    /// </remarks>
 75    public static bool TryMultiply(Fixed4x4 lhs, Fixed4x4 rhs, out Fixed4x4 result)
 76    {
 442377        bool representable = TryGetExactProductSum(lhs.M11, rhs.M11, lhs.M12, rhs.M21, lhs.M13, rhs.M31, lhs.M14, rhs.M4
 442378            & TryGetExactProductSum(lhs.M11, rhs.M12, lhs.M12, rhs.M22, lhs.M13, rhs.M32, lhs.M14, rhs.M42, out Fixed64 
 442379            & TryGetExactProductSum(lhs.M11, rhs.M13, lhs.M12, rhs.M23, lhs.M13, rhs.M33, lhs.M14, rhs.M43, out Fixed64 
 442380            & TryGetExactProductSum(lhs.M11, rhs.M14, lhs.M12, rhs.M24, lhs.M13, rhs.M34, lhs.M14, rhs.M44, out Fixed64 
 442381            & TryGetExactProductSum(lhs.M21, rhs.M11, lhs.M22, rhs.M21, lhs.M23, rhs.M31, lhs.M24, rhs.M41, out Fixed64 
 442382            & TryGetExactProductSum(lhs.M21, rhs.M12, lhs.M22, rhs.M22, lhs.M23, rhs.M32, lhs.M24, rhs.M42, out Fixed64 
 442383            & TryGetExactProductSum(lhs.M21, rhs.M13, lhs.M22, rhs.M23, lhs.M23, rhs.M33, lhs.M24, rhs.M43, out Fixed64 
 442384            & TryGetExactProductSum(lhs.M21, rhs.M14, lhs.M22, rhs.M24, lhs.M23, rhs.M34, lhs.M24, rhs.M44, out Fixed64 
 442385            & TryGetExactProductSum(lhs.M31, rhs.M11, lhs.M32, rhs.M21, lhs.M33, rhs.M31, lhs.M34, rhs.M41, out Fixed64 
 442386            & TryGetExactProductSum(lhs.M31, rhs.M12, lhs.M32, rhs.M22, lhs.M33, rhs.M32, lhs.M34, rhs.M42, out Fixed64 
 442387            & TryGetExactProductSum(lhs.M31, rhs.M13, lhs.M32, rhs.M23, lhs.M33, rhs.M33, lhs.M34, rhs.M43, out Fixed64 
 442388            & TryGetExactProductSum(lhs.M31, rhs.M14, lhs.M32, rhs.M24, lhs.M33, rhs.M34, lhs.M34, rhs.M44, out Fixed64 
 442389            & TryGetExactProductSum(lhs.M41, rhs.M11, lhs.M42, rhs.M21, lhs.M43, rhs.M31, lhs.M44, rhs.M41, out Fixed64 
 442390            & TryGetExactProductSum(lhs.M41, rhs.M12, lhs.M42, rhs.M22, lhs.M43, rhs.M32, lhs.M44, rhs.M42, out Fixed64 
 442391            & TryGetExactProductSum(lhs.M41, rhs.M13, lhs.M42, rhs.M23, lhs.M43, rhs.M33, lhs.M44, rhs.M43, out Fixed64 
 442392            & TryGetExactProductSum(lhs.M41, rhs.M14, lhs.M42, rhs.M24, lhs.M43, rhs.M34, lhs.M44, rhs.M44, out Fixed64 
 442393        if (!representable)
 94        {
 695            result = Zero;
 696            return false;
 97        }
 98
 441799        result = new Fixed4x4(
 4417100            m11, m12, m13, m14,
 4417101            m21, m22, m23, m24,
 4417102            m31, m32, m33, m34,
 4417103            m41, m42, m43, m44);
 4417104        return true;
 105    }
 106
 107    /// <summary>
 108    /// Multiplies two 4x4 matrices using standard matrix multiplication.
 109    /// </summary>
 110    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 111    public static Fixed4x4 operator *(Fixed4x4 lhs, Fixed4x4 rhs)
 112    {
 26448113        if (lhs.IsAffine && rhs.IsAffine)
 114        {
 115            // Optimized affine multiplication (skips full 4×4 multiplication)
 26445116            return new Fixed4x4(
 26445117                lhs.M11 * rhs.M11 + lhs.M12 * rhs.M21 + lhs.M13 * rhs.M31,
 26445118                lhs.M11 * rhs.M12 + lhs.M12 * rhs.M22 + lhs.M13 * rhs.M32,
 26445119                lhs.M11 * rhs.M13 + lhs.M12 * rhs.M23 + lhs.M13 * rhs.M33,
 26445120                Fixed64.Zero,
 26445121
 26445122                lhs.M21 * rhs.M11 + lhs.M22 * rhs.M21 + lhs.M23 * rhs.M31,
 26445123                lhs.M21 * rhs.M12 + lhs.M22 * rhs.M22 + lhs.M23 * rhs.M32,
 26445124                lhs.M21 * rhs.M13 + lhs.M22 * rhs.M23 + lhs.M23 * rhs.M33,
 26445125                Fixed64.Zero,
 26445126
 26445127                lhs.M31 * rhs.M11 + lhs.M32 * rhs.M21 + lhs.M33 * rhs.M31,
 26445128                lhs.M31 * rhs.M12 + lhs.M32 * rhs.M22 + lhs.M33 * rhs.M32,
 26445129                lhs.M31 * rhs.M13 + lhs.M32 * rhs.M23 + lhs.M33 * rhs.M33,
 26445130                Fixed64.Zero,
 26445131
 26445132                lhs.M41 * rhs.M11 + lhs.M42 * rhs.M21 + lhs.M43 * rhs.M31 + rhs.M41,
 26445133                lhs.M41 * rhs.M12 + lhs.M42 * rhs.M22 + lhs.M43 * rhs.M32 + rhs.M42,
 26445134                lhs.M41 * rhs.M13 + lhs.M42 * rhs.M23 + lhs.M43 * rhs.M33 + rhs.M43,
 26445135                Fixed64.One
 26445136            );
 137        }
 138
 139        // Full 4×4 multiplication (fallback for perspective matrices)
 3140        return new Fixed4x4(
 3141            // Upper-left 3×3 matrix multiplication (rotation & scale)
 3142            lhs.M11 * rhs.M11 + lhs.M12 * rhs.M21 + lhs.M13 * rhs.M31 + lhs.M14 * rhs.M41,
 3143            lhs.M11 * rhs.M12 + lhs.M12 * rhs.M22 + lhs.M13 * rhs.M32 + lhs.M14 * rhs.M42,
 3144            lhs.M11 * rhs.M13 + lhs.M12 * rhs.M23 + lhs.M13 * rhs.M33 + lhs.M14 * rhs.M43,
 3145            lhs.M11 * rhs.M14 + lhs.M12 * rhs.M24 + lhs.M13 * rhs.M34 + lhs.M14 * rhs.M44,
 3146
 3147            lhs.M21 * rhs.M11 + lhs.M22 * rhs.M21 + lhs.M23 * rhs.M31 + lhs.M24 * rhs.M41,
 3148            lhs.M21 * rhs.M12 + lhs.M22 * rhs.M22 + lhs.M23 * rhs.M32 + lhs.M24 * rhs.M42,
 3149            lhs.M21 * rhs.M13 + lhs.M22 * rhs.M23 + lhs.M23 * rhs.M33 + lhs.M24 * rhs.M43,
 3150            lhs.M21 * rhs.M14 + lhs.M22 * rhs.M24 + lhs.M23 * rhs.M34 + lhs.M24 * rhs.M44,
 3151
 3152            lhs.M31 * rhs.M11 + lhs.M32 * rhs.M21 + lhs.M33 * rhs.M31 + lhs.M34 * rhs.M41,
 3153            lhs.M31 * rhs.M12 + lhs.M32 * rhs.M22 + lhs.M33 * rhs.M32 + lhs.M34 * rhs.M42,
 3154            lhs.M31 * rhs.M13 + lhs.M32 * rhs.M23 + lhs.M33 * rhs.M33 + lhs.M34 * rhs.M43,
 3155            lhs.M31 * rhs.M14 + lhs.M32 * rhs.M24 + lhs.M33 * rhs.M34 + lhs.M34 * rhs.M44,
 3156
 3157            // Compute new translation
 3158            lhs.M41 * rhs.M11 + lhs.M42 * rhs.M21 + lhs.M43 * rhs.M31 + lhs.M44 * rhs.M41,
 3159            lhs.M41 * rhs.M12 + lhs.M42 * rhs.M22 + lhs.M43 * rhs.M32 + lhs.M44 * rhs.M42,
 3160            lhs.M41 * rhs.M13 + lhs.M42 * rhs.M23 + lhs.M43 * rhs.M33 + lhs.M44 * rhs.M43,
 3161            lhs.M41 * rhs.M14 + lhs.M42 * rhs.M24 + lhs.M43 * rhs.M34 + lhs.M44 * rhs.M44
 3162        );
 163    }
 164
 165    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 166    private static bool TryGetExactProductSum(
 167        Fixed64 firstLeft,
 168        Fixed64 firstRight,
 169        Fixed64 secondLeft,
 170        Fixed64 secondRight,
 171        Fixed64 thirdLeft,
 172        Fixed64 thirdRight,
 173        Fixed64 fourthLeft,
 174        Fixed64 fourthRight,
 175        out Fixed64 result)
 176    {
 83878177        Signed192 sum = AddProduct(default, firstLeft, firstRight);
 83878178        sum = AddProduct(sum, secondLeft, secondRight);
 83878179        sum = AddProduct(sum, thirdLeft, thirdRight);
 83878180        sum = AddProduct(sum, fourthLeft, fourthRight);
 83878181        bool negative = sum.Sign < 0;
 83878182        WideArithmetic.GetMagnitude(sum, out ulong high, out ulong middle, out ulong low);
 83878183        if (high != 0UL || (middle >> FixedMath.SHIFT_AMOUNT_I) != 0UL)
 184        {
 1185            result = default;
 1186            return false;
 187        }
 188
 83877189        ulong magnitude = (middle << FixedMath.SHIFT_AMOUNT_I)
 83877190            | (low >> FixedMath.SHIFT_AMOUNT_I);
 83877191        ulong guardMask = 1UL << (FixedMath.SHIFT_AMOUNT_I - 1);
 83877192        if ((low & guardMask) != 0UL
 83877193            && ((low & (guardMask - 1UL)) != 0UL || (magnitude & 1UL) != 0UL))
 194        {
 15343195            if (magnitude == ulong.MaxValue)
 196            {
 1197                result = default;
 1198                return false;
 199            }
 200
 15342201            magnitude++;
 202        }
 203
 83876204        ulong limit = negative ? 1UL << 63 : (ulong)long.MaxValue;
 83876205        if (magnitude > limit)
 206        {
 7207            result = default;
 7208            return false;
 209        }
 210
 83869211        result = Fixed64.FromRaw(negative
 83869212            ? magnitude == 1UL << 63 ? long.MinValue : -(long)magnitude
 83869213            : (long)magnitude);
 83869214        return true;
 215
 216        static Signed192 AddProduct(Signed192 sum, Fixed64 left, Fixed64 right)
 217        {
 218            long leftRaw = left.m_rawValue;
 219            long rightRaw = right.m_rawValue;
 220            Fixed64.Multiply64To128(
 221                Fixed64.AbsToUInt64(leftRaw),
 222                Fixed64.AbsToUInt64(rightRaw),
 223                out ulong productHigh,
 224                out ulong productLow);
 225            Signed192 product = new(0UL, productHigh, productLow);
 226            if ((leftRaw ^ rightRaw) < 0L)
 227                product = WideArithmetic.SubtractSigned192(default, product);
 228
 229            return WideArithmetic.AddSigned192(sum, product);
 230        }
 231    }
 232
 233    /// <summary>
 234    /// Multiplies every matrix component by a scalar.
 235    /// </summary>
 236    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 237    public static Fixed4x4 operator *(Fixed4x4 matrix, Fixed64 scalar) =>
 5238        new(
 5239            matrix.M11 * scalar, matrix.M12 * scalar, matrix.M13 * scalar, matrix.M14 * scalar,
 5240            matrix.M21 * scalar, matrix.M22 * scalar, matrix.M23 * scalar, matrix.M24 * scalar,
 5241            matrix.M31 * scalar, matrix.M32 * scalar, matrix.M33 * scalar, matrix.M34 * scalar,
 5242            matrix.M41 * scalar, matrix.M42 * scalar, matrix.M43 * scalar, matrix.M44 * scalar);
 243
 244    /// <summary>
 245    /// Multiplies every matrix component by a scalar.
 246    /// </summary>
 247    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1248    public static Fixed4x4 operator *(Fixed64 scalar, Fixed4x4 matrix) => matrix * scalar;
 249
 250    /// <summary>
 251    /// Divides every matrix component by a scalar.
 252    /// </summary>
 253    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 254    public static Fixed4x4 operator /(Fixed4x4 matrix, Fixed64 scalar)
 255    {
 2256        Fixed64 inverse = Fixed64.One / scalar;
 2257        return matrix * inverse;
 258    }
 259
 260    /// <summary>
 261    /// Determines whether two Fixed4x4 instances are equal.
 262    /// </summary>
 263    /// <param name="left">The first Fixed4x4 instance to compare.</param>
 264    /// <param name="right">The second Fixed4x4 instance to compare.</param>
 265    /// <returns>true if the specified Fixed4x4 instances are equal; otherwise, false.</returns>
 266    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2267    public static bool operator ==(Fixed4x4 left, Fixed4x4 right) => left.Equals(right);
 268
 269    /// <summary>
 270    /// Determines whether two Fixed4x4 instances are not equal.
 271    /// </summary>
 272    /// <param name="left">The first Fixed4x4 instance to compare.</param>
 273    /// <param name="right">The second Fixed4x4 instance to compare.</param>
 274    /// <returns>true if the specified Fixed4x4 instances are not equal; otherwise, false.</returns>
 275    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1276    public static bool operator !=(Fixed4x4 left, Fixed4x4 right) => !(left == right);
 277
 278    #endregion
 279}

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

#LineLine coverage
 1//=======================================================================
 2// Fixed4x4.Statics.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Runtime.CompilerServices;
 10
 11namespace FixedMathSharp;
 12
 13/// <content>
 14/// Static utility methods for <see cref="Fixed4x4"/>, including interpolation, transposition,
 15/// and component-wise arithmetic operations.
 16/// </content>
 17public partial struct Fixed4x4
 18{
 19    #region Static Matrix Operators
 20
 21    /// <summary>
 22    /// Linearly interpolates between two matrices component-wise.
 23    /// </summary>
 24    public static Fixed4x4 Lerp(Fixed4x4 a, Fixed4x4 b, Fixed64 t) =>
 325         new(
 326            FixedMath.Lerp(a.M11, b.M11, t),
 327            FixedMath.Lerp(a.M12, b.M12, t),
 328            FixedMath.Lerp(a.M13, b.M13, t),
 329            FixedMath.Lerp(a.M14, b.M14, t),
 330            FixedMath.Lerp(a.M21, b.M21, t),
 331            FixedMath.Lerp(a.M22, b.M22, t),
 332            FixedMath.Lerp(a.M23, b.M23, t),
 333            FixedMath.Lerp(a.M24, b.M24, t),
 334            FixedMath.Lerp(a.M31, b.M31, t),
 335            FixedMath.Lerp(a.M32, b.M32, t),
 336            FixedMath.Lerp(a.M33, b.M33, t),
 337            FixedMath.Lerp(a.M34, b.M34, t),
 338            FixedMath.Lerp(a.M41, b.M41, t),
 339            FixedMath.Lerp(a.M42, b.M42, t),
 340            FixedMath.Lerp(a.M43, b.M43, t),
 341            FixedMath.Lerp(a.M44, b.M44, t));
 42
 43    /// <summary>
 44    /// Transposes the matrix by swapping rows and columns.
 45    /// </summary>
 46    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 47    public static Fixed4x4 Transpose(Fixed4x4 matrix) =>
 348         new(
 349            matrix.M11, matrix.M21, matrix.M31, matrix.M41,
 350            matrix.M12, matrix.M22, matrix.M32, matrix.M42,
 351            matrix.M13, matrix.M23, matrix.M33, matrix.M43,
 352            matrix.M14, matrix.M24, matrix.M34, matrix.M44);
 53
 54    /// <summary>
 55    /// Divides each component of one matrix by the corresponding component of another matrix.
 56    /// </summary>
 57    /// <exception cref="DivideByZeroException">
 58    /// Thrown when any divisor component is zero.
 59    /// </exception>
 60    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 61    public static Fixed4x4 ComponentDivide(Fixed4x4 dividend, Fixed4x4 divisor) =>
 262        new(
 263            dividend.M11 / divisor.M11,
 264            dividend.M12 / divisor.M12,
 265            dividend.M13 / divisor.M13,
 266            dividend.M14 / divisor.M14,
 267            dividend.M21 / divisor.M21,
 268            dividend.M22 / divisor.M22,
 269            dividend.M23 / divisor.M23,
 270            dividend.M24 / divisor.M24,
 271            dividend.M31 / divisor.M31,
 272            dividend.M32 / divisor.M32,
 273            dividend.M33 / divisor.M33,
 274            dividend.M34 / divisor.M34,
 275            dividend.M41 / divisor.M41,
 276            dividend.M42 / divisor.M42,
 277            dividend.M43 / divisor.M43,
 278            dividend.M44 / divisor.M44);
 79
 80    /// <summary>
 81    /// Divides one matrix by another using inverse matrix division.
 82    /// </summary>
 83    /// <remarks>
 84    /// This is equivalent to <c>dividend * Invert(divisor)</c>. Use <see cref="ComponentDivide"/>
 85    /// when each matrix component should be divided by the corresponding component of another matrix.
 86    /// </remarks>
 87    /// <exception cref="InvalidOperationException">
 88    /// Thrown when <paramref name="divisor"/> is not invertible.
 89    /// </exception>
 90    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 91    public static Fixed4x4 InverseDivide(Fixed4x4 dividend, Fixed4x4 divisor)
 92    {
 293        if (!Invert(divisor, out Fixed4x4 inverseDivisor))
 194            throw new InvalidOperationException("Matrix divisor is not invertible.");
 95
 196        return dividend * inverseDivisor;
 97    }
 98
 99    /// <summary>
 100    /// Inverts the matrix if it is invertible (i.e., if the determinant is not zero).
 101    /// </summary>
 102    /// <remarks>
 103    /// To Invert a FixedMatrix4x4, we need to calculate the inverse for each element.
 104    /// This involves computing the cofactor for each element,
 105    /// which is the determinant of the submatrix when the row and column of that element are removed,
 106    /// multiplied by a sign based on the element's position.
 107    /// After computing all cofactors, the result is transposed to get the inverse matrix.
 108    /// </remarks>
 109    public static bool Invert(Fixed4x4 matrix, out Fixed4x4 result)
 110    {
 2215111        if (!matrix.IsAffine)
 6112            return FullInvert(matrix, out result);
 113
 2209114        Fixed64 det = matrix.GetDeterminant();
 115
 2209116        if (det == Fixed64.Zero)
 117        {
 7118            result = Identity;
 7119            return false;
 120        }
 121
 2202122        Fixed64 invDet = Fixed64.One / det;
 123
 124        // Invert the 3×3 upper-left rotation/scale matrix
 2202125        result = new Fixed4x4(
 2202126            (matrix.M22 * matrix.M33 - matrix.M23 * matrix.M32) * invDet,
 2202127            (matrix.M13 * matrix.M32 - matrix.M12 * matrix.M33) * invDet,
 2202128            (matrix.M12 * matrix.M23 - matrix.M13 * matrix.M22) * invDet, Fixed64.Zero,
 2202129
 2202130            (matrix.M23 * matrix.M31 - matrix.M21 * matrix.M33) * invDet,
 2202131            (matrix.M11 * matrix.M33 - matrix.M13 * matrix.M31) * invDet,
 2202132            (matrix.M13 * matrix.M21 - matrix.M11 * matrix.M23) * invDet, Fixed64.Zero,
 2202133
 2202134            (matrix.M21 * matrix.M32 - matrix.M22 * matrix.M31) * invDet,
 2202135            (matrix.M12 * matrix.M31 - matrix.M11 * matrix.M32) * invDet,
 2202136            (matrix.M11 * matrix.M22 - matrix.M12 * matrix.M21) * invDet, Fixed64.Zero,
 2202137
 2202138            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One  // Ensure homogeneous coordinate stays valid
 2202139        );
 140
 2202141        result.M41 = -(matrix.M41 * result.M11 + matrix.M42 * result.M21 + matrix.M43 * result.M31);
 2202142        result.M42 = -(matrix.M41 * result.M12 + matrix.M42 * result.M22 + matrix.M43 * result.M32);
 2202143        result.M43 = -(matrix.M41 * result.M13 + matrix.M42 * result.M23 + matrix.M43 * result.M33);
 2202144        result.M44 = Fixed64.One;
 145
 2202146        return true;
 147    }
 148
 149    private static bool FullInvert(Fixed4x4 matrix, out Fixed4x4 result)
 150    {
 6151        Fixed64 det = matrix.GetDeterminant();
 152
 6153        if (det == Fixed64.Zero)
 154        {
 3155            result = Fixed4x4.Identity;
 3156            return false;
 157        }
 158
 3159        Fixed64 invDet = Fixed64.One / det;
 160
 161        // Inversion using cofactors and determinants of 3x3 submatrices
 3162        result = new Fixed4x4
 3163        {
 3164            // First row
 3165            M11 = invDet * ((matrix.M22 * matrix.M33 * matrix.M44 + matrix.M23 * matrix.M34 * matrix.M42 + matrix.M24 * 
 3166                          - (matrix.M24 * matrix.M33 * matrix.M42 + matrix.M22 * matrix.M34 * matrix.M43 + matrix.M23 * 
 3167            M12 = invDet * ((matrix.M12 * matrix.M34 * matrix.M43 + matrix.M13 * matrix.M32 * matrix.M44 + matrix.M14 * 
 3168                          - (matrix.M14 * matrix.M32 * matrix.M43 + matrix.M12 * matrix.M33 * matrix.M44 + matrix.M13 * 
 3169            M13 = invDet * ((matrix.M12 * matrix.M23 * matrix.M44 + matrix.M13 * matrix.M24 * matrix.M42 + matrix.M14 * 
 3170                          - (matrix.M14 * matrix.M23 * matrix.M42 + matrix.M12 * matrix.M24 * matrix.M43 + matrix.M13 * 
 3171            M14 = invDet * ((matrix.M12 * matrix.M24 * matrix.M33 + matrix.M13 * matrix.M22 * matrix.M34 + matrix.M14 * 
 3172                          - (matrix.M14 * matrix.M22 * matrix.M33 + matrix.M12 * matrix.M23 * matrix.M34 + matrix.M13 * 
 3173
 3174            // Second row
 3175            M21 = invDet * ((matrix.M21 * matrix.M34 * matrix.M43 + matrix.M23 * matrix.M31 * matrix.M44 + matrix.M24 * 
 3176                          - (matrix.M24 * matrix.M31 * matrix.M43 + matrix.M21 * matrix.M33 * matrix.M44 + matrix.M23 * 
 3177            M22 = invDet * ((matrix.M11 * matrix.M33 * matrix.M44 + matrix.M13 * matrix.M34 * matrix.M41 + matrix.M14 * 
 3178                          - (matrix.M14 * matrix.M31 * matrix.M43 + matrix.M11 * matrix.M34 * matrix.M43 + matrix.M13 * 
 3179            M23 = invDet * ((matrix.M11 * matrix.M24 * matrix.M43 + matrix.M13 * matrix.M21 * matrix.M44 + matrix.M14 * 
 3180                          - (matrix.M14 * matrix.M21 * matrix.M43 + matrix.M11 * matrix.M23 * matrix.M44 + matrix.M13 * 
 3181            M24 = invDet * ((matrix.M11 * matrix.M23 * matrix.M34 + matrix.M13 * matrix.M24 * matrix.M31 + matrix.M14 * 
 3182                          - (matrix.M14 * matrix.M21 * matrix.M33 + matrix.M11 * matrix.M24 * matrix.M33 + matrix.M13 * 
 3183
 3184            // Third row
 3185            M31 = invDet * ((matrix.M21 * matrix.M32 * matrix.M44 + matrix.M22 * matrix.M34 * matrix.M41 + matrix.M24 * 
 3186                          - (matrix.M24 * matrix.M31 * matrix.M42 + matrix.M21 * matrix.M34 * matrix.M42 + matrix.M22 * 
 3187            M32 = invDet * ((matrix.M11 * matrix.M34 * matrix.M42 + matrix.M12 * matrix.M31 * matrix.M44 + matrix.M14 * 
 3188                          - (matrix.M14 * matrix.M31 * matrix.M42 + matrix.M11 * matrix.M32 * matrix.M44 + matrix.M12 * 
 3189            M33 = invDet * ((matrix.M11 * matrix.M22 * matrix.M44 + matrix.M12 * matrix.M24 * matrix.M41 + matrix.M14 * 
 3190                          - (matrix.M14 * matrix.M21 * matrix.M42 + matrix.M11 * matrix.M24 * matrix.M42 + matrix.M12 * 
 3191            M34 = invDet * ((matrix.M11 * matrix.M24 * matrix.M32 + matrix.M12 * matrix.M21 * matrix.M34 + matrix.M14 * 
 3192                          - (matrix.M14 * matrix.M21 * matrix.M32 + matrix.M11 * matrix.M22 * matrix.M34 + matrix.M12 * 
 3193
 3194            // Fourth row
 3195            M41 = invDet * ((matrix.M21 * matrix.M33 * matrix.M42 + matrix.M22 * matrix.M31 * matrix.M43 + matrix.M23 * 
 3196                          - (matrix.M23 * matrix.M31 * matrix.M42 + matrix.M21 * matrix.M32 * matrix.M43 + matrix.M22 * 
 3197            M42 = invDet * ((matrix.M11 * matrix.M32 * matrix.M43 + matrix.M12 * matrix.M33 * matrix.M41 + matrix.M13 * 
 3198                          - (matrix.M13 * matrix.M31 * matrix.M42 + matrix.M11 * matrix.M33 * matrix.M42 + matrix.M12 * 
 3199            M43 = invDet * ((matrix.M11 * matrix.M23 * matrix.M42 + matrix.M12 * matrix.M21 * matrix.M43 + matrix.M13 * 
 3200                          - (matrix.M13 * matrix.M21 * matrix.M42 + matrix.M11 * matrix.M22 * matrix.M43 + matrix.M12 * 
 3201            M44 = invDet * ((matrix.M11 * matrix.M22 * matrix.M33 + matrix.M12 * matrix.M23 * matrix.M31 + matrix.M13 * 
 3202                          - (matrix.M13 * matrix.M21 * matrix.M32 + matrix.M11 * matrix.M23 * matrix.M32 + matrix.M12 * 
 3203        };
 204
 3205        return true;
 206    }
 207
 208    /// <summary>
 209    /// Transforms a 4D vector by a 4x4 matrix, preserving the computed W component.
 210    /// </summary>
 211    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2212    public static Vector4d Transform(Fixed4x4 matrix, Vector4d vector) => Vector4d.Transform(matrix, vector);
 213
 214    /// <summary>
 215    /// Transforms a point from local space to world space using this transformation matrix.
 216    /// </summary>
 217    /// <remarks>
 218    /// FixedMathSharp applies matrices using a row-vector convention: <c>point * matrix</c>.
 219    /// This is equivalent to <see cref="Vector3d.operator *(Vector3d, Fixed4x4)"/>.
 220    /// </remarks>
 221    /// <param name="matrix">The transformation matrix.</param>
 222    /// <param name="point">The local-space point.</param>
 223    /// <returns>The transformed point in world space.</returns>
 224    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 225    public static Vector3d TransformPoint(Fixed4x4 matrix, Vector3d point)
 226    {
 30227        if (matrix.IsAffine)
 27228            return new Vector3d(
 27229                point.X * matrix.M11 + point.Y * matrix.M21 + point.Z * matrix.M31 + matrix.M41,
 27230                point.X * matrix.M12 + point.Y * matrix.M22 + point.Z * matrix.M32 + matrix.M42,
 27231                point.X * matrix.M13 + point.Y * matrix.M23 + point.Z * matrix.M33 + matrix.M43
 27232            );
 233
 3234        return FullTransformPoint(matrix, point);
 235    }
 236
 237    /// <summary>
 238    /// Attempts to transform a point by an affine matrix without intermediate saturation.
 239    /// </summary>
 240    /// <remarks>
 241    /// FixedMathSharp applies matrices using the row-vector convention
 242    /// <c>point * matrix</c>. Each result coordinate is accumulated exactly and
 243    /// rounded half to even once. Non-affine matrices and unrepresentable final
 244    /// coordinates fail with a zero result.
 245    /// </remarks>
 246    public static bool TryTransformAffinePoint(
 247        Fixed4x4 matrix,
 248        Vector3d point,
 249        out Vector3d result)
 250    {
 4374251        if (!matrix.IsAffine)
 252        {
 4253            result = Vector3d.Zero;
 4254            return false;
 255        }
 256
 4370257        bool representable =
 4370258            TryGetExactProductSum(
 4370259                point.X, matrix.M11,
 4370260                point.Y, matrix.M21,
 4370261                point.Z, matrix.M31,
 4370262                Fixed64.One, matrix.M41,
 4370263                out Fixed64 x)
 4370264            & TryGetExactProductSum(
 4370265                point.X, matrix.M12,
 4370266                point.Y, matrix.M22,
 4370267                point.Z, matrix.M32,
 4370268                Fixed64.One, matrix.M42,
 4370269                out Fixed64 y)
 4370270            & TryGetExactProductSum(
 4370271                point.X, matrix.M13,
 4370272                point.Y, matrix.M23,
 4370273                point.Z, matrix.M33,
 4370274                Fixed64.One, matrix.M43,
 4370275                out Fixed64 z);
 4370276        if (!representable)
 277        {
 3278            result = Vector3d.Zero;
 3279            return false;
 280        }
 281
 4367282        result = new Vector3d(x, y, z);
 4367283        return true;
 284    }
 285
 286    private static Vector3d FullTransformPoint(Fixed4x4 matrix, Vector3d point)
 287    {
 288        // Full 4×4 transformation (needed for perspective projections)
 3289        Fixed64 w = matrix.M14 * point.X + matrix.M24 * point.Y + matrix.M34 * point.Z + matrix.M44;
 4290        if (w == Fixed64.Zero) w = Fixed64.One;  // Prevent divide-by-zero
 291
 3292        return new Vector3d(
 3293            (point.X * matrix.M11 + point.Y * matrix.M21 + point.Z * matrix.M31 + matrix.M41) / w,
 3294            (point.X * matrix.M12 + point.Y * matrix.M22 + point.Z * matrix.M32 + matrix.M42) / w,
 3295            (point.X * matrix.M13 + point.Y * matrix.M23 + point.Z * matrix.M33 + matrix.M43) / w
 3296        );
 297    }
 298
 299    /// <summary>
 300    /// Transforms a point from world space into the local space of the matrix.
 301    /// </summary>
 302    /// <param name="matrix">The transformation matrix.</param>
 303    /// <param name="point">The world-space point.</param>
 304    /// <returns>The local-space point relative to the transformation matrix.</returns>
 305    public static Vector3d InverseTransformPoint(Fixed4x4 matrix, Vector3d point)
 306    {
 307        // Invert the transformation matrix
 7308        if (!Invert(matrix, out Fixed4x4 inverseMatrix))
 1309            throw new InvalidOperationException("Matrix is not invertible.");
 310
 6311        if (inverseMatrix.IsAffine)
 312        {
 4313            return new Vector3d(
 4314                point.X * inverseMatrix.M11 + point.Y * inverseMatrix.M21 + point.Z * inverseMatrix.M31 + inverseMatrix.
 4315                point.X * inverseMatrix.M12 + point.Y * inverseMatrix.M22 + point.Z * inverseMatrix.M32 + inverseMatrix.
 4316                point.X * inverseMatrix.M13 + point.Y * inverseMatrix.M23 + point.Z * inverseMatrix.M33 + inverseMatrix.
 4317            );
 318        }
 319
 2320        return FullInverseTransformPoint(inverseMatrix, point);
 321    }
 322
 323    private static Vector3d FullInverseTransformPoint(Fixed4x4 matrix, Vector3d point)
 324    {
 325        // Full 4×4 transformation (needed for perspective projections)
 2326        Fixed64 w = matrix.M14 * point.X + matrix.M24 * point.Y + matrix.M34 * point.Z + matrix.M44;
 3327        if (w == Fixed64.Zero) w = Fixed64.One;  // Prevent divide-by-zero
 328
 2329        return new Vector3d(
 2330            (point.X * matrix.M11 + point.Y * matrix.M21 + point.Z * matrix.M31 + matrix.M41) / w,
 2331            (point.X * matrix.M12 + point.Y * matrix.M22 + point.Z * matrix.M32 + matrix.M42) / w,
 2332            (point.X * matrix.M13 + point.Y * matrix.M23 + point.Z * matrix.M33 + matrix.M43) / w
 2333        );
 334    }
 335
 336    #endregion
 337}

Methods/Properties

.cctor()
.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)
FromRows(FixedMathSharp.Vector4d,FixedMathSharp.Vector4d,FixedMathSharp.Vector4d,FixedMathSharp.Vector4d)
FromColumns(FixedMathSharp.Vector4d,FixedMathSharp.Vector4d,FixedMathSharp.Vector4d,FixedMathSharp.Vector4d)
get_IsAffine()
get_Translation()
get_Right()
get_Left()
get_Up()
get_Down()
get_Forward()
get_Backward()
get_LossyScale()
get_Rotation()
get_Item(System.Int32)
set_Item(System.Int32,FixedMathSharp.Fixed64)
GetDeterminant()
SetTransform(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d)
ExtractTranslation(FixedMathSharp.Fixed4x4)
ExtractRight(FixedMathSharp.Fixed4x4)
ExtractUp(FixedMathSharp.Fixed4x4)
ExtractForward(FixedMathSharp.Fixed4x4)
ExtractScaleMagnitudes(FixedMathSharp.Fixed4x4)
ExtractLossyScale(FixedMathSharp.Fixed4x4)
TryExtractLossyScale(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d&)
ExtractRotation(FixedMathSharp.Fixed4x4)
Decompose(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d&,FixedMathSharp.FixedQuaternion&,FixedMathSharp.Vector3d&)
IsNormalizedOrthogonalBasis(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
SetTranslation(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)
ApplyScaleToRotation(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)
SetRotation(FixedMathSharp.Fixed4x4,FixedMathSharp.FixedQuaternion)
NormalizeRotationMatrix(FixedMathSharp.Fixed4x4)
Equals(System.Object)
Equals(FixedMathSharp.Fixed4x4)
GetHashCode()
ToString()
ToString(System.String,System.IFormatProvider)
TryFormat(System.Span`1<System.Char>,System.Int32&,System.ReadOnlySpan`1<System.Char>,System.IFormatProvider)
AppendRow(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Span`1<System.Char>,System.Int32&,System.ReadOnlySpan`1<System.Char>,System.IFormatProvider)
CreateTranslation(FixedMathSharp.Vector3d)
CreateTranslation(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
CreateRotation(FixedMathSharp.FixedQuaternion)
CreateRotationX(FixedMathSharp.Fixed64)
CreateRotationY(FixedMathSharp.Fixed64)
CreateRotationZ(FixedMathSharp.Fixed64)
CreateFromAxisAngle(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
CreateFromEulerAngles(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
CreateScale(FixedMathSharp.Vector3d)
CreateScale(FixedMathSharp.Fixed64)
CreateScale(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
CreateLookAt(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
CreateOrthographic(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
CreateOrthographicOffCenter(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
CreatePerspective(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
CreatePerspectiveFieldOfView(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
CreatePerspectiveOffCenter(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
CreateWorld(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
CreateTransform(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d)
CreateTransform(FixedMathSharp.Vector3d,FixedMathSharp.Fixed3x3,FixedMathSharp.Vector3d)
TryCreateTransform(FixedMathSharp.Vector3d,FixedMathSharp.Fixed3x3,FixedMathSharp.Vector3d,FixedMathSharp.Fixed4x4&)
ScaleRotateTranslate(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d)
TranslateRotateScale(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d)
FromRotationMatrix(FixedMathSharp.Fixed3x3)
ValidateDepthRange(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
ValidatePerspectiveDepthRange(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
op_UnaryNegation(FixedMathSharp.Fixed4x4)
op_Addition(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4)
op_Subtraction(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4)
TryMultiply(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4&)
op_Multiply(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4)
TryGetExactProductSum(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
op_Multiply(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed64)
op_Multiply(FixedMathSharp.Fixed64,FixedMathSharp.Fixed4x4)
op_Division(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed64)
op_Equality(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4)
op_Inequality(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4)
Lerp(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed64)
Transpose(FixedMathSharp.Fixed4x4)
ComponentDivide(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4)
InverseDivide(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4)
Invert(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4&)
FullInvert(FixedMathSharp.Fixed4x4,FixedMathSharp.Fixed4x4&)
Transform(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector4d)
TransformPoint(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)
TryTransformAffinePoint(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
FullTransformPoint(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)
InverseTransformPoint(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)
FullInverseTransformPoint(FixedMathSharp.Fixed4x4,FixedMathSharp.Vector3d)