< Summary

Line coverage
100%
Covered lines: 763
Uncovered lines: 0
Coverable lines: 763
Total lines: 1938
Line coverage: 100%
Branch coverage
100%
Covered branches: 240
Total branches: 240
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: 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 NET8_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    {
 273776184        M11 = m11; M12 = m12; M13 = m13; M14 = m14;
 273776185        M21 = m21; M22 = m22; M23 = m23; M24 = m24;
 273776186        M31 = m31; M32 = m32; M33 = m33; M34 = m34;
 273776187        M41 = m41; M42 = m42; M43 = m43; M44 = m44;
 68444188    }
 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]
 61796228    public readonly bool IsAffine => M44 == Fixed64.One
 61796229        && M14 == Fixed64.Zero
 61796230        && M24 == Fixed64.Zero
 61796231        && M34 == Fixed64.Zero;
 232
 233    /// <inheritdoc cref="ExtractTranslation(Fixed4x4)" />
 234    [JsonIgnore]
 235    [MemoryPackIgnore]
 17236    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        {
 18307            return index switch
 18308            {
 1309                0 => M11,
 1310                1 => M21,
 1311                2 => M31,
 1312                3 => M41,
 1313                4 => M12,
 1314                5 => M22,
 1315                6 => M32,
 1316                7 => M42,
 1317                8 => M13,
 1318                9 => M23,
 1319                10 => M33,
 1320                11 => M43,
 1321                12 => M14,
 1322                13 => M24,
 1323                14 => M34,
 1324                15 => M44,
 2325                _ => throw new IndexOutOfRangeException("Invalid matrix index!"),
 18326            };
 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:
 7339                    M31 = value;
 7340                    break;
 341                case 3:
 1342                    M41 = value;
 1343                    break;
 344                case 4:
 8345                    M12 = value;
 8346                    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:
 6360                    M23 = value;
 6361                    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    {
 2217394        if (IsAffine)
 395        {
 2210396            return M11 * (M22 * M33 - M23 * M32)
 2210397                 - M12 * (M21 * M33 - M23 * M31)
 2210398                 + 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)]
 2027    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) =>
 8055        new(
 8056            new Vector3d(matrix.M11, matrix.M12, matrix.M13).Magnitude,
 8057            new Vector3d(matrix.M21, matrix.M22, matrix.M23).Magnitude,
 8058            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    {
 10124        Vector3d scale = ExtractScaleMagnitudes(matrix);
 125
 126        // prevent divide by zero exception
 10127        Fixed64 scaleX = scale.X == Fixed64.Zero ? Fixed64.One : scale.X;
 10128        Fixed64 scaleY = scale.Y == Fixed64.Zero ? Fixed64.One : scale.Y;
 10129        Fixed64 scaleZ = scale.Z == Fixed64.Zero ? Fixed64.One : scale.Z;
 130
 10131        Fixed4x4 normalizedMatrix = new(
 10132            matrix.M11 / scaleX, matrix.M12 / scaleX, matrix.M13 / scaleX, Fixed64.Zero,
 10133            matrix.M21 / scaleY, matrix.M22 / scaleY, matrix.M23 / scaleY, Fixed64.Zero,
 10134            matrix.M31 / scaleZ, matrix.M32 / scaleZ, matrix.M33 / scaleZ, Fixed64.Zero,
 10135            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One
 10136        );
 137
 10138        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    /// rotation bases that cannot round-trip within <see cref="Fixed64.Epsilon"/>
 148    /// are rejected. Reflections assign their single negative scale sign to X.
 149    /// Every failure writes zero translation, identity rotation, and unit scale.
 150    /// </remarks>
 151    /// <param name="matrix">The 4x4 matrix to decompose.</param>
 152    /// <param name="translation">The extracted translation component.</param>
 153    /// <param name="rotation">The extracted rotation component as a quaternion.</param>
 154    /// <param name="scale">The extracted scale component.</param>
 155    /// <returns>True if decomposition was successful, otherwise false.</returns>
 156    public static bool Decompose(
 157        Fixed4x4 matrix,
 158        out Vector3d translation,
 159        out FixedQuaternion rotation,
 160        out Vector3d scale)
 161    {
 55162        translation = Vector3d.Zero;
 55163        rotation = FixedQuaternion.Identity;
 55164        scale = Vector3d.One;
 165
 55166        if (!matrix.IsAffine)
 4167            return false;
 168
 51169        Vector3d basisX = new(matrix.M11, matrix.M12, matrix.M13);
 51170        Vector3d basisY = new(matrix.M21, matrix.M22, matrix.M23);
 51171        Vector3d basisZ = new(matrix.M31, matrix.M32, matrix.M33);
 51172        if (!Vector3d.TryGetMagnitude(basisX, out Fixed64 scaleX)
 51173            || !Vector3d.TryGetMagnitude(basisY, out Fixed64 scaleY)
 51174            || !Vector3d.TryGetMagnitude(basisZ, out Fixed64 scaleZ)
 51175            || scaleX == Fixed64.Zero
 51176            || scaleY == Fixed64.Zero
 51177            || scaleZ == Fixed64.Zero)
 178        {
 8179            return false;
 180        }
 181
 43182        basisX /= scaleX;
 43183        basisY /= scaleY;
 43184        basisZ /= scaleZ;
 43185        if (!IsNormalizedOrthogonalBasis(basisX, basisY, basisZ))
 12186            return false;
 187
 31188        int handedness = WideGeometry.GetTripleProductSign(
 31189            matrix.M11, matrix.M12, matrix.M13,
 31190            matrix.M21, matrix.M22, matrix.M23,
 31191            matrix.M31, matrix.M32, matrix.M33);
 31192        if (handedness < 0)
 193        {
 7194            scaleX = -scaleX;
 7195            basisX = -basisX;
 196        }
 197
 31198        Fixed3x3 normalizedBasis = new(
 31199            basisX,
 31200            basisY,
 31201            basisZ);
 31202        FixedQuaternion candidateRotation = FixedQuaternion.FromMatrix(normalizedBasis).Normalized;
 31203        Fixed3x3 reconstructed = candidateRotation.ToMatrix3x3();
 31204        if (!reconstructed.FuzzyEqualAbsolute(normalizedBasis, Fixed64.Epsilon))
 1205            return false;
 206
 30207        Vector3d candidateTranslation = new(matrix.M41, matrix.M42, matrix.M43);
 30208        Vector3d candidateScale = new(scaleX, scaleY, scaleZ);
 30209        Fixed4x4 reconstructedTransform = CreateTransform(
 30210            candidateTranslation,
 30211            reconstructed,
 30212            candidateScale);
 30213        if (!reconstructedTransform.FuzzyEqualAbsolute(matrix, Fixed64.Epsilon))
 2214            return false;
 215
 28216        translation = candidateTranslation;
 28217        rotation = candidateRotation;
 28218        scale = candidateScale;
 28219        return true;
 220    }
 221
 222    private static bool IsNormalizedOrthogonalBasis(Vector3d x, Vector3d y, Vector3d z) =>
 43223        Vector3d.TryGetMagnitude(x, out Fixed64 xLength)
 43224        && Vector3d.TryGetMagnitude(y, out Fixed64 yLength)
 43225        && Vector3d.TryGetMagnitude(z, out Fixed64 zLength)
 43226        && FixedMath.Abs(xLength - Fixed64.One) <= Fixed64.Epsilon
 43227        && FixedMath.Abs(yLength - Fixed64.One) <= Fixed64.Epsilon
 43228        && FixedMath.Abs(zLength - Fixed64.One) <= Fixed64.Epsilon
 43229        && FixedMath.Abs(Vector3d.Dot(x, y)) <= Fixed64.Epsilon
 43230        && FixedMath.Abs(Vector3d.Dot(x, z)) <= Fixed64.Epsilon
 43231        && FixedMath.Abs(Vector3d.Dot(y, z)) <= Fixed64.Epsilon;
 232
 233    /// <summary>
 234    /// Sets the translation component of the 4x4 matrix.
 235    /// </summary>
 236    /// <param name="matrix">The matrix to modify.</param>
 237    /// <param name="translation">The new translation vector.</param>
 238    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 239    public static Fixed4x4 SetTranslation(Fixed4x4 matrix, Vector3d translation)
 240    {
 1241        matrix.M41 = translation.X;
 1242        matrix.M42 = translation.Y;
 1243        matrix.M43 = translation.Z;
 1244        return matrix;
 245    }
 246
 247    /// <summary>
 248    /// Applies non-uniform scaling to the 4x4 matrix by multiplying the scale vector with the rotation matrix's basis v
 249    /// </summary>
 250    /// <param name="matrix">The matrix to modify. Should already contain a valid rotation component.</param>
 251    /// <param name="scale">The scale vector to apply along the X, Y, and Z axes.</param>
 252    /// <remarks>
 253    /// Use this method when scaling is required in combination with an existing rotation, ensuring proper axis alignmen
 254    /// </remarks>
 255    public static Fixed4x4 ApplyScaleToRotation(Fixed4x4 matrix, Vector3d scale)
 256    {
 257        // Scale each row of the rotation matrix
 1258        matrix.M11 *= scale.X;
 1259        matrix.M12 *= scale.X;
 1260        matrix.M13 *= scale.X;
 261
 1262        matrix.M21 *= scale.Y;
 1263        matrix.M22 *= scale.Y;
 1264        matrix.M23 *= scale.Y;
 265
 1266        matrix.M31 *= scale.Z;
 1267        matrix.M32 *= scale.Z;
 1268        matrix.M33 *= scale.Z;
 269
 1270        return matrix;
 271    }
 272
 273    /// <summary>
 274    /// Replaces the rotation component of the 4x4 matrix using the provided quaternion, without affecting the translati
 275    /// </summary>
 276    /// <param name="matrix">The matrix to modify. The rotation will replace the upper-left 3x3 portion of the matrix.</
 277    /// <param name="rotation">The quaternion representing the new rotation to apply.</param>
 278    /// <remarks>
 279    /// Quaternion magnitude does not affect the replacement rotation; zero
 280    /// represents identity. This method preserves the matrix's translation
 281    /// component. For complete transformation updates, use <see cref="SetTransform"/>.
 282    /// </remarks>
 283    public static Fixed4x4 SetRotation(Fixed4x4 matrix, FixedQuaternion rotation)
 284    {
 6285        Fixed3x3 rotationMatrix = rotation.ToMatrix3x3();
 286
 6287        Vector3d scale = ExtractScaleMagnitudes(matrix);
 288
 289        // Apply rotation to the upper-left 3x3 matrix
 290
 6291        matrix.M11 = rotationMatrix.M11 * scale.X;
 6292        matrix.M12 = rotationMatrix.M12 * scale.X;
 6293        matrix.M13 = rotationMatrix.M13 * scale.X;
 294
 6295        matrix.M21 = rotationMatrix.M21 * scale.Y;
 6296        matrix.M22 = rotationMatrix.M22 * scale.Y;
 6297        matrix.M23 = rotationMatrix.M23 * scale.Y;
 298
 6299        matrix.M31 = rotationMatrix.M31 * scale.Z;
 6300        matrix.M32 = rotationMatrix.M32 * scale.Z;
 6301        matrix.M33 = rotationMatrix.M33 * scale.Z;
 302
 6303        return matrix;
 304    }
 305
 306    /// <summary>
 307    /// Normalizes the rotation component of a 4x4 matrix by ensuring the basis vectors are orthogonal and unit length.
 308    /// </summary>
 309    /// <remarks>
 310    /// This method recalculates the X, Y, and Z basis vectors from the upper-left 3x3 portion of the matrix, ensuring t
 311    /// The remaining components of the matrix are reset to maintain a valid transformation structure.
 312    ///
 313    /// Use Cases:
 314    /// - Ensuring the rotation component remains stable and accurate after multiple transformations.
 315    /// - Used in 3D transformations to prevent numerical drift from affecting the orientation over time.
 316    /// - Essential for cases where precise orientation is required, such as animations or physics simulations.
 317    /// </remarks>
 318    public static Fixed4x4 NormalizeRotationMatrix(Fixed4x4 matrix)
 319    {
 1320        Vector3d basisX = new Vector3d(matrix.M11, matrix.M12, matrix.M13).NormalizeInPlace();
 1321        Vector3d basisY = new Vector3d(matrix.M21, matrix.M22, matrix.M23).NormalizeInPlace();
 1322        Vector3d basisZ = new Vector3d(matrix.M31, matrix.M32, matrix.M33).NormalizeInPlace();
 323
 1324        return new Fixed4x4(
 1325            basisX.X, basisX.Y, basisX.Z, Fixed64.Zero,
 1326            basisY.X, basisY.Y, basisY.Z, Fixed64.Zero,
 1327            basisZ.X, basisZ.Y, basisZ.Z, Fixed64.Zero,
 1328            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One
 1329        );
 330    }
 331
 332    #endregion
 333}

/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) =>
 5228        M11 == other.M11 && M12 == other.M12 && M13 == other.M13 && M14 == other.M14 &&
 5229        M21 == other.M21 && M22 == other.M22 && M23 == other.M23 && M24 == other.M24 &&
 5230        M31 == other.M31 && M32 == other.M32 && M33 == other.M33 && M34 == other.M34 &&
 5231        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) =>
 34108        new(
 34109            scale.X, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 34110            Fixed64.Zero, scale.Y, Fixed64.Zero, Fixed64.Zero,
 34111            Fixed64.Zero, Fixed64.Zero, scale.Z, Fixed64.Zero,
 34112            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) =>
 28555343        CreateTransform(translation, rotation.ToMatrix3x3(), scale);
 344
 345    private static Fixed4x4 CreateTransform(Vector3d translation, Fixed3x3 rotation, Vector3d scale)
 346    {
 28585347        return new Fixed4x4(
 28585348            rotation.M11 * scale.X, rotation.M12 * scale.X, rotation.M13 * scale.X, Fixed64.Zero,
 28585349            rotation.M21 * scale.Y, rotation.M22 * scale.Y, rotation.M23 * scale.Y, Fixed64.Zero,
 28585350            rotation.M31 * scale.Z, rotation.M32 * scale.Z, rotation.M33 * scale.Z, Fixed64.Zero,
 28585351            translation.X, translation.Y, translation.Z, Fixed64.One);
 352    }
 353
 354    /// <summary>
 355    /// Constructs a transformation matrix from translation, rotation, and scale by multiplying
 356    /// separate matrices in the order: Scale * Rotation * Translation.
 357    /// </summary>
 358    /// <remarks>
 359    /// - This method directly multiplies the scale, rotation, and translation matrices.
 360    /// - Ensures that scale is applied first to preserve correct axis scaling.
 361    /// - Then rotation is applied so that rotation is not affected by non-uniform scaling.
 362    /// - Finally, translation moves the object to its correct world position.
 363    /// </remarks>
 364    public static Fixed4x4 ScaleRotateTranslate(Vector3d translation, FixedQuaternion rotation, Vector3d scale)
 365    {
 9366        return CreateTransform(translation, rotation, scale);
 367    }
 368
 369    /// <summary>
 370    /// Constructs a transformation matrix from translation, rotation, and scale by multiplying
 371    /// matrices in the order: Translation * Rotation * Scale (T * R * S).
 372    /// </summary>
 373    /// <remarks>
 374    /// - Use this method when transformations need to be applied **relative to an object's local origin**.
 375    /// - Example use cases include **animation systems**, **hierarchical transformations**, and **UI transformations**.
 376    /// - If you need to apply world-space transformations, use
 377    ///   <see cref="CreateTransform(Vector3d, FixedQuaternion, Vector3d)"/> instead.
 378    /// - Quaternion magnitude does not affect the rotation matrix; zero represents identity.
 379    /// </remarks>
 380    public static Fixed4x4 TranslateRotateScale(Vector3d translation, FixedQuaternion rotation, Vector3d scale)
 381    {
 5382        Fixed3x3 rotationMatrix = rotation.ToMatrix3x3();
 383
 5384        return new Fixed4x4(
 5385            rotationMatrix.M11 * scale.X, rotationMatrix.M12 * scale.Y, rotationMatrix.M13 * scale.Z, Fixed64.Zero,
 5386            rotationMatrix.M21 * scale.X, rotationMatrix.M22 * scale.Y, rotationMatrix.M23 * scale.Z, Fixed64.Zero,
 5387            rotationMatrix.M31 * scale.X, rotationMatrix.M32 * scale.Y, rotationMatrix.M33 * scale.Z, Fixed64.Zero,
 5388            (translation.X * rotationMatrix.M11 + translation.Y * rotationMatrix.M21 + translation.Z * rotationMatrix.M3
 5389            (translation.X * rotationMatrix.M12 + translation.Y * rotationMatrix.M22 + translation.Z * rotationMatrix.M3
 5390            (translation.X * rotationMatrix.M13 + translation.Y * rotationMatrix.M23 + translation.Z * rotationMatrix.M3
 5391            Fixed64.One);
 392    }
 393
 394    #endregion
 395}

/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    {
 442077        bool representable = TryGetExactProductSum(lhs.M11, rhs.M11, lhs.M12, rhs.M21, lhs.M13, rhs.M31, lhs.M14, rhs.M4
 442078            & TryGetExactProductSum(lhs.M11, rhs.M12, lhs.M12, rhs.M22, lhs.M13, rhs.M32, lhs.M14, rhs.M42, out Fixed64 
 442079            & TryGetExactProductSum(lhs.M11, rhs.M13, lhs.M12, rhs.M23, lhs.M13, rhs.M33, lhs.M14, rhs.M43, out Fixed64 
 442080            & TryGetExactProductSum(lhs.M11, rhs.M14, lhs.M12, rhs.M24, lhs.M13, rhs.M34, lhs.M14, rhs.M44, out Fixed64 
 442081            & TryGetExactProductSum(lhs.M21, rhs.M11, lhs.M22, rhs.M21, lhs.M23, rhs.M31, lhs.M24, rhs.M41, out Fixed64 
 442082            & TryGetExactProductSum(lhs.M21, rhs.M12, lhs.M22, rhs.M22, lhs.M23, rhs.M32, lhs.M24, rhs.M42, out Fixed64 
 442083            & TryGetExactProductSum(lhs.M21, rhs.M13, lhs.M22, rhs.M23, lhs.M23, rhs.M33, lhs.M24, rhs.M43, out Fixed64 
 442084            & TryGetExactProductSum(lhs.M21, rhs.M14, lhs.M22, rhs.M24, lhs.M23, rhs.M34, lhs.M24, rhs.M44, out Fixed64 
 442085            & TryGetExactProductSum(lhs.M31, rhs.M11, lhs.M32, rhs.M21, lhs.M33, rhs.M31, lhs.M34, rhs.M41, out Fixed64 
 442086            & TryGetExactProductSum(lhs.M31, rhs.M12, lhs.M32, rhs.M22, lhs.M33, rhs.M32, lhs.M34, rhs.M42, out Fixed64 
 442087            & TryGetExactProductSum(lhs.M31, rhs.M13, lhs.M32, rhs.M23, lhs.M33, rhs.M33, lhs.M34, rhs.M43, out Fixed64 
 442088            & TryGetExactProductSum(lhs.M31, rhs.M14, lhs.M32, rhs.M24, lhs.M33, rhs.M34, lhs.M34, rhs.M44, out Fixed64 
 442089            & TryGetExactProductSum(lhs.M41, rhs.M11, lhs.M42, rhs.M21, lhs.M43, rhs.M31, lhs.M44, rhs.M41, out Fixed64 
 442090            & TryGetExactProductSum(lhs.M41, rhs.M12, lhs.M42, rhs.M22, lhs.M43, rhs.M32, lhs.M44, rhs.M42, out Fixed64 
 442091            & TryGetExactProductSum(lhs.M41, rhs.M13, lhs.M42, rhs.M23, lhs.M43, rhs.M33, lhs.M44, rhs.M43, out Fixed64 
 442092            & TryGetExactProductSum(lhs.M41, rhs.M14, lhs.M42, rhs.M24, lhs.M43, rhs.M34, lhs.M44, rhs.M44, out Fixed64 
 442093        if (!representable)
 94        {
 695            result = Zero;
 696            return false;
 97        }
 98
 441499        result = new Fixed4x4(
 4414100            m11, m12, m13, m14,
 4414101            m21, m22, m23, m24,
 4414102            m31, m32, m33, m34,
 4414103            m41, m42, m43, m44);
 4414104        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    {
 26446113        if (lhs.IsAffine && rhs.IsAffine)
 114        {
 115            // Optimized affine multiplication (skips full 4×4 multiplication)
 26443116            return new Fixed4x4(
 26443117                lhs.M11 * rhs.M11 + lhs.M12 * rhs.M21 + lhs.M13 * rhs.M31,
 26443118                lhs.M11 * rhs.M12 + lhs.M12 * rhs.M22 + lhs.M13 * rhs.M32,
 26443119                lhs.M11 * rhs.M13 + lhs.M12 * rhs.M23 + lhs.M13 * rhs.M33,
 26443120                Fixed64.Zero,
 26443121
 26443122                lhs.M21 * rhs.M11 + lhs.M22 * rhs.M21 + lhs.M23 * rhs.M31,
 26443123                lhs.M21 * rhs.M12 + lhs.M22 * rhs.M22 + lhs.M23 * rhs.M32,
 26443124                lhs.M21 * rhs.M13 + lhs.M22 * rhs.M23 + lhs.M23 * rhs.M33,
 26443125                Fixed64.Zero,
 26443126
 26443127                lhs.M31 * rhs.M11 + lhs.M32 * rhs.M21 + lhs.M33 * rhs.M31,
 26443128                lhs.M31 * rhs.M12 + lhs.M32 * rhs.M22 + lhs.M33 * rhs.M32,
 26443129                lhs.M31 * rhs.M13 + lhs.M32 * rhs.M23 + lhs.M33 * rhs.M33,
 26443130                Fixed64.Zero,
 26443131
 26443132                lhs.M41 * rhs.M11 + lhs.M42 * rhs.M21 + lhs.M43 * rhs.M31 + rhs.M41,
 26443133                lhs.M41 * rhs.M12 + lhs.M42 * rhs.M22 + lhs.M43 * rhs.M32 + rhs.M42,
 26443134                lhs.M41 * rhs.M13 + lhs.M42 * rhs.M23 + lhs.M43 * rhs.M33 + rhs.M43,
 26443135                Fixed64.One
 26443136            );
 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    {
 83830177        Signed192 sum = AddProduct(default, firstLeft, firstRight);
 83830178        sum = AddProduct(sum, secondLeft, secondRight);
 83830179        sum = AddProduct(sum, thirdLeft, thirdRight);
 83830180        sum = AddProduct(sum, fourthLeft, fourthRight);
 83830181        bool negative = sum.Sign < 0;
 83830182        WideArithmetic.GetMagnitude(sum, out ulong high, out ulong middle, out ulong low);
 83830183        if (high != 0UL || (middle >> FixedMath.SHIFT_AMOUNT_I) != 0UL)
 184        {
 1185            result = default;
 1186            return false;
 187        }
 188
 83829189        ulong magnitude = (middle << FixedMath.SHIFT_AMOUNT_I)
 83829190            | (low >> FixedMath.SHIFT_AMOUNT_I);
 83829191        ulong guardMask = 1UL << (FixedMath.SHIFT_AMOUNT_I - 1);
 83829192        if ((low & guardMask) != 0UL
 83829193            && ((low & (guardMask - 1UL)) != 0UL || (magnitude & 1UL) != 0UL))
 194        {
 15340195            if (magnitude == ulong.MaxValue)
 196            {
 1197                result = default;
 1198                return false;
 199            }
 200
 15339201            magnitude++;
 202        }
 203
 83828204        ulong limit = negative ? 1UL << 63 : (ulong)long.MaxValue;
 83828205        if (magnitude > limit)
 206        {
 7207            result = default;
 7208            return false;
 209        }
 210
 83821211        result = Fixed64.FromRaw(negative
 83821212            ? magnitude == 1UL << 63 ? long.MinValue : -(long)magnitude
 83821213            : (long)magnitude);
 83821214        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    {
 2214111        if (!matrix.IsAffine)
 6112            return FullInvert(matrix, out result);
 113
 2208114        Fixed64 det = matrix.GetDeterminant();
 115
 2208116        if (det == Fixed64.Zero)
 117        {
 7118            result = Identity;
 7119            return false;
 120        }
 121
 2201122        Fixed64 invDet = Fixed64.One / det;
 123
 124        // Invert the 3×3 upper-left rotation/scale matrix
 2201125        result = new Fixed4x4(
 2201126            (matrix.M22 * matrix.M33 - matrix.M23 * matrix.M32) * invDet,
 2201127            (matrix.M13 * matrix.M32 - matrix.M12 * matrix.M33) * invDet,
 2201128            (matrix.M12 * matrix.M23 - matrix.M13 * matrix.M22) * invDet, Fixed64.Zero,
 2201129
 2201130            (matrix.M23 * matrix.M31 - matrix.M21 * matrix.M33) * invDet,
 2201131            (matrix.M11 * matrix.M33 - matrix.M13 * matrix.M31) * invDet,
 2201132            (matrix.M13 * matrix.M21 - matrix.M11 * matrix.M23) * invDet, Fixed64.Zero,
 2201133
 2201134            (matrix.M21 * matrix.M32 - matrix.M22 * matrix.M31) * invDet,
 2201135            (matrix.M12 * matrix.M31 - matrix.M11 * matrix.M32) * invDet,
 2201136            (matrix.M11 * matrix.M22 - matrix.M12 * matrix.M21) * invDet, Fixed64.Zero,
 2201137
 2201138            Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One  // Ensure homogeneous coordinate stays valid
 2201139        );
 140
 2201141        result.M41 = -(matrix.M41 * result.M11 + matrix.M42 * result.M21 + matrix.M43 * result.M31);
 2201142        result.M42 = -(matrix.M41 * result.M12 + matrix.M42 * result.M22 + matrix.M43 * result.M32);
 2201143        result.M43 = -(matrix.M41 * result.M13 + matrix.M42 * result.M23 + matrix.M43 * result.M33);
 2201144        result.M44 = Fixed64.One;
 145
 2201146        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)
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)