< Summary

Line coverage
100%
Covered lines: 338
Uncovered lines: 0
Coverable lines: 338
Total lines: 1132
Line coverage: 100%
Branch coverage
93%
Covered branches: 97
Total branches: 104
Branch coverage: 93.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: ToEulerAngles()100%22100%
File 1: ToDirection()100%11100%
File 1: ToMatrix3x3()100%11100%
File 1: Deconstruct(...)100%11100%
File 1: Deconstruct(...)100%11100%
File 1: Deconstruct(...)100%11100%
File 1: Deconstruct(...)100%11100%
File 2: get_Identity()100%11100%
File 2: get_Zero()100%11100%
File 2: .ctor(...)100%11100%
File 2: get_Normalized()100%11100%
File 2: get_Magnitude()100%11100%
File 2: get_MagnitudeSquared()100%11100%
File 2: get_EulerAngles()100%11100%
File 2: set_EulerAngles(...)100%11100%
File 2: get_Item(...)100%55100%
File 2: set_Item(...)100%55100%
File 2: Set(...)100%11100%
File 2: NormalizeInPlace()100%11100%
File 2: Conjugate()100%11100%
File 2: Inverse()100%44100%
File 2: Rotate(...)100%11100%
File 2: Rotated(...)100%22100%
File 3: Equals(...)100%22100%
File 3: Equals(...)100%66100%
File 3: GetHashCode()100%11100%
File 3: ToString()100%11100%
File 3: ToString(...)100%11100%
File 3: TryFormat(...)61.11%1818100%
File 4: op_Multiply(...)100%11100%
File 4: op_Multiply(...)100%11100%
File 4: op_Multiply(...)100%11100%
File 4: op_Division(...)100%11100%
File 4: op_Addition(...)100%11100%
File 4: op_Subtraction(...)100%11100%
File 4: op_UnaryNegation(...)100%11100%
File 4: op_Equality(...)100%11100%
File 4: op_Inequality(...)100%11100%
File 5: IsNormalized()100%11100%
File 5: GetMagnitude(...)100%66100%
File 5: GetNormalized(...)100%44100%
File 5: Divide(...)100%22100%
File 5: LookRotation(...)100%22100%
File 5: FromMatrix(...)100%88100%
File 5: FromMatrix(...)100%11100%
File 5: FromDirection(...)100%1010100%
File 5: FromAxisAngle(...)100%88100%
File 5: FromEulerAnglesInDegrees(...)100%11100%
File 5: FromEulerAngles(...)100%1212100%
File 5: QuaternionLog(...)100%22100%
File 5: ToAngularVelocity(...)100%11100%
File 5: Lerp(...)100%22100%
File 5: Slerp(...)100%44100%
File 5: Angle(...)100%11100%
File 5: AngleAxis(...)100%11100%
File 5: Dot(...)100%11100%

File(s)

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Numerics/Rotations/FixedQuaternion.Conversions.cs

#LineLine coverage
 1//=======================================================================
 2// FixedQuaternion.Conversions.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
 12public partial struct FixedQuaternion
 13{
 14    #region Conversion
 15
 16    /// <summary>
 17    /// Converts this quaternion to Euler angles in degrees.
 18    /// Returns angles as (pitch, yaw, roll), where:
 19    /// pitch = rotation around X
 20    /// yaw   = rotation around Y
 21    /// roll  = rotation around Z
 22    ///
 23    /// The extraction matches FromEulerAngles(), which composes rotations in YXZ order:
 24    /// q = qy * qx * qz
 25    /// </summary>
 26    public Vector3d ToEulerAngles()
 27    {
 528        Fixed3x3 m = ToMatrix3x3();
 29
 30        Fixed64 pitch;
 31        Fixed64 yaw;
 32        Fixed64 roll;
 33
 34        // For YXZ:
 35        // m32 = -sin(pitch)
 36        // m31 =  sin(yaw) * cos(pitch)
 37        // m33 =  cos(yaw) * cos(pitch)
 38        // m12 =  sin(roll) * cos(pitch)
 39        // m22 =  cos(roll) * cos(pitch)
 40
 541        Fixed64 sinPitch = -m.M32;
 42
 543        if (sinPitch.Abs() >= Fixed64.One)
 44        {
 45            // Gimbal lock: pitch is ±90°, yaw/roll are coupled.
 446            pitch = FixedMath.CopySign(Fixed64.HalfPi, sinPitch);
 47
 48            // Choose roll = 0 and solve remaining yaw from matrix.
 449            roll = Fixed64.Zero;
 450            yaw = FixedMath.Atan2(-m.M13, m.M11);
 51        }
 52        else
 53        {
 154            pitch = FixedMath.Asin(sinPitch);
 155            yaw = FixedMath.Atan2(m.M31, m.M33);
 156            roll = FixedMath.Atan2(m.M12, m.M22);
 57        }
 58
 559        return new Vector3d(
 560            FixedMath.RadToDeg(pitch),
 561            FixedMath.RadToDeg(yaw),
 562            FixedMath.RadToDeg(roll));
 63    }
 64
 65    /// <summary>
 66    /// Converts this FixedQuaternion to the rotated canonical forward direction.
 67    /// </summary>
 68    /// <remarks>
 69    /// The identity quaternion returns <see cref="Vector3d.Forward"/> because FixedMathSharp's
 70    /// canonical 3D forward direction is <c>+Z</c>.
 71    /// </remarks>
 72    /// <returns>A Vector3d representing the rotated canonical forward direction.</returns>
 73    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 74    public Vector3d ToDirection() =>
 175        new(2 * (X * Z - W * Y),
 176            2 * (Y * Z + W * X),
 177            Fixed64.One - 2 * (X * X + Y * Y));
 78
 79
 80    /// <summary>
 81    /// Converts the quaternion into a 3x3 rotation matrix.
 82    /// </summary>
 83    /// <returns>A FixedMatrix3x3 representing the same rotation as the quaternion.</returns>
 84    public Fixed3x3 ToMatrix3x3()
 85    {
 5586        Fixed64 x2 = X * X;
 5587        Fixed64 y2 = Y * Y;
 5588        Fixed64 z2 = Z * Z;
 5589        Fixed64 xy = X * Y;
 5590        Fixed64 xz = X * Z;
 5591        Fixed64 yz = Y * Z;
 5592        Fixed64 xw = X * W;
 5593        Fixed64 yw = Y * W;
 5594        Fixed64 zw = Z * W;
 95
 5596        Fixed3x3 result = new();
 5597        Fixed64 scale = Fixed64.One * 2;
 98
 5599        result.M11 = Fixed64.One - scale * (y2 + z2);
 55100        result.M12 = scale * (xy + zw);
 55101        result.M13 = scale * (xz - yw);
 102
 55103        result.M21 = scale * (xy - zw);
 55104        result.M22 = Fixed64.One - scale * (x2 + z2);
 55105        result.M23 = scale * (yz + xw);
 106
 55107        result.M31 = scale * (xz + yw);
 55108        result.M32 = scale * (yz - xw);
 55109        result.M33 = Fixed64.One - scale * (x2 + y2);
 110
 55111        return result;
 112    }
 113
 114    /// <summary>
 115    /// Deconstructs the quaternion into its four Fixed64 components.
 116    /// </summary>
 117    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 118    public void Deconstruct(out Fixed64 x, out Fixed64 y, out Fixed64 z, out Fixed64 w)
 119    {
 1120        x = X;
 1121        y = Y;
 1122        z = Z;
 1123        w = W;
 1124    }
 125
 126    /// <summary>
 127    /// Deconstructs the quaternion into its four int components.
 128    /// </summary>
 129    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 130    public void Deconstruct(out int x, out int y, out int z, out int w)
 131    {
 1132        x = X.RoundToInt();
 1133        y = Y.RoundToInt();
 1134        z = Z.RoundToInt();
 1135        w = W.RoundToInt();
 1136    }
 137
 138    /// <summary>
 139    /// Deconstructs the quaternion into its four long components.
 140    /// </summary>
 141    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 142    public void Deconstruct(out long x, out long y, out long z, out long w)
 143    {
 1144        x = X.m_rawValue;
 1145        y = Y.m_rawValue;
 1146        z = Z.m_rawValue;
 1147        w = W.m_rawValue;
 1148    }
 149
 150    /// <summary>
 151    /// Deconstructs the quaternion into its four double components.
 152    /// </summary>
 153    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 154    public void Deconstruct(out double x, out double y, out double z, out double w)
 155    {
 1156        x = (double)X;
 1157        y = (double)Y;
 1158        z = (double)Z;
 1159        w = (double)W;
 1160    }
 161
 162    #endregion
 163}

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Numerics/Rotations/FixedQuaternion.cs

#LineLine coverage
 1//=======================================================================
 2// FixedQuaternion.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 MemoryPack;
 9using System;
 10using System.Runtime.CompilerServices;
 11using System.Text.Json.Serialization;
 12
 13namespace FixedMathSharp;
 14
 15/// <summary>
 16/// Represents a quaternion (x, y, z, w) with fixed-point numbers.
 17/// Quaternions are useful for representing rotations and can be used to perform smooth rotations and avoid gimbal lock.
 18/// </summary>
 19/// <remarks>
 20/// Direction-oriented quaternion APIs use FixedMathSharp's canonical 3D convention: <c>+X</c>
 21/// right, <c>+Y</c> up, and <c>+Z</c> forward. Convert external engine or tool directions
 22/// before calling these APIs when their semantic basis differs.
 23/// </remarks>
 24[Serializable]
 25[MemoryPackable]
 26public partial struct FixedQuaternion : IEquatable<FixedQuaternion>, IFormattable
 27#if NET8_0_OR_GREATER
 28    , ISpanFormattable
 29#endif
 30{
 31    #region Static Readonly Fields
 32
 33    /// <summary>
 34    /// Identity quaternion (0, 0, 0, 1).
 35    /// </summary>
 7536    public static FixedQuaternion Identity => new(Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One);
 37
 38    /// <summary>
 39    /// Empty quaternion (0, 0, 0, 0).
 40    /// </summary>
 841    public static FixedQuaternion Zero => new(Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.Zero);
 42
 43    #endregion
 44    #region Fields and Constants
 45
 46    private const long NearOppositeDirectionDotRaw = -4290672328L;
 47
 48    /// <summary>
 49    /// Represents the X component of the vector as a fixed-point value.
 50    /// </summary>
 51    [JsonInclude]
 52    [MemoryPackOrder(0)]
 53    public Fixed64 X;
 54
 55    /// <summary>
 56    /// Represents the Y component of the vector as a fixed-point value.
 57    /// </summary>
 58    [JsonInclude]
 59    [MemoryPackOrder(1)]
 60    public Fixed64 Y;
 61
 62    /// <summary>
 63    /// Represents the Z component of the vector as a fixed-point value.
 64    /// </summary>
 65    [JsonInclude]
 66    [MemoryPackOrder(2)]
 67    public Fixed64 Z;
 68
 69    /// <summary>
 70    /// Represents the W component of the vector as a fixed-point value.
 71    /// </summary>
 72    [JsonInclude]
 73    [MemoryPackOrder(3)]
 74    public Fixed64 W;
 75
 76    #endregion
 77    #region Constructors
 78
 79    /// <summary>
 80    /// Creates a new FixedQuaternion with the specified components.
 81    /// </summary>
 82    public FixedQuaternion(Fixed64 x, Fixed64 y, Fixed64 z, Fixed64 w)
 83    {
 35684        X = x;
 35685        Y = y;
 35686        Z = z;
 35687        W = w;
 35688    }
 89
 90    #endregion
 91    #region Properties
 92
 93    /// <summary>
 94    /// Normalized version of this quaternion.
 95    /// </summary>
 96    [JsonIgnore]
 97    [MemoryPackIgnore]
 98    public FixedQuaternion Normalized
 99    {
 100        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 23101        get => GetNormalized(this);
 102    }
 103
 104    /// <summary>
 105    /// Gets the magnitude of this quaternion.
 106    /// </summary>
 107    [JsonIgnore]
 108    [MemoryPackIgnore]
 109    public Fixed64 Magnitude
 110    {
 111        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 9112        get => GetMagnitude(this);
 113    }
 114
 115    /// <summary>
 116    /// Gets the squared magnitude of this quaternion.
 117    /// </summary>
 118    [JsonIgnore]
 119    [MemoryPackIgnore]
 120    public Fixed64 MagnitudeSquared
 121    {
 122        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 6123        get => X * X + Y * Y + Z * Z + W * W;
 124    }
 125
 126    /// <summary>
 127    /// Returns the Euler angles (in degrees) of this quaternion.
 128    /// </summary>
 129    [JsonIgnore]
 130    [MemoryPackIgnore]
 131    public Vector3d EulerAngles
 132    {
 133        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 3134        get => ToEulerAngles();
 135        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1136        set => this = FromEulerAnglesInDegrees(value.X, value.Y, value.Z);
 137    }
 138
 139    /// <summary>
 140    /// Gets or sets the component value at the specified index.
 141    /// </summary>
 142    /// <remarks>Index 0 corresponds to the x component, 1 to y, 2 to z, and 3 to w.</remarks>
 143    /// <param name="index">The zero-based index of the component to access. Valid values are 0 (x), 1 (y), 2 (z), and 3
 144    /// <returns>The value of the component at the specified index.</returns>
 145    /// <exception cref="IndexOutOfRangeException">Thrown when the specified index is less than 0 or greater than 3.</ex
 146    [JsonIgnore]
 147    [MemoryPackIgnore]
 148    public Fixed64 this[int index]
 149    {
 150        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 151        get
 152        {
 5153            return index switch
 5154            {
 1155                0 => X,
 1156                1 => Y,
 1157                2 => Z,
 1158                3 => W,
 1159                _ => throw new IndexOutOfRangeException("Invalid FixedQuaternion index!"),
 5160            };
 161        }
 162        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 163        set
 164        {
 165            switch (index)
 166            {
 167                case 0:
 1168                    X = value;
 1169                    break;
 170                case 1:
 1171                    Y = value;
 1172                    break;
 173                case 2:
 1174                    Z = value;
 1175                    break;
 176                case 3:
 1177                    W = value;
 1178                    break;
 179                default:
 1180                    throw new IndexOutOfRangeException("Invalid FixedQuaternion index!");
 181            }
 182        }
 183    }
 184
 185    #endregion
 186    #region Methods (Instance)
 187
 188    /// <summary>
 189    /// Set x, y, z and w components of an existing Quaternion.
 190    /// </summary>
 191    /// <param name="newX"></param>
 192    /// <param name="newY"></param>
 193    /// <param name="newZ"></param>
 194    /// <param name="newW"></param>
 195    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 196    public void Set(Fixed64 newX, Fixed64 newY, Fixed64 newZ, Fixed64 newW)
 197    {
 1198        X = newX;
 1199        Y = newY;
 1200        Z = newZ;
 1201        W = newW;
 1202    }
 203
 204    /// <summary>
 205    /// Normalizes this quaternion in place.
 206    /// </summary>
 207    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 14208    public FixedQuaternion NormalizeInPlace() => this = GetNormalized(this);
 209
 210    /// <summary>
 211    /// Returns the conjugate of this quaternion (inverses the rotational effect).
 212    /// </summary>
 213    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 5214    public FixedQuaternion Conjugate() => new(-X, -Y, -Z, W);
 215
 216    /// <summary>
 217    /// Returns the inverse of this quaternion.
 218    /// </summary>
 219    public FixedQuaternion Inverse()
 220    {
 13221        if (this == Identity) return Identity;
 3222        Fixed64 norm = MagnitudeSquared;
 4223        if (norm == Fixed64.Zero) return this; // Handle division by zero by returning the same quaternion
 224
 2225        Fixed64 invNorm = Fixed64.One / norm;
 2226        return new FixedQuaternion(X * -invNorm, Y * -invNorm, Z * -invNorm, W * invNorm);
 227    }
 228
 229    /// <summary>
 230    /// Rotates a vector by this quaternion.
 231    /// </summary>
 232    public Vector3d Rotate(Vector3d v)
 233    {
 4234        FixedQuaternion normalizedQuat = Normalized;
 4235        FixedQuaternion vQuat = new(v.X, v.Y, v.Z, Fixed64.Zero);
 4236        FixedQuaternion invQuat = normalizedQuat.Conjugate();
 4237        FixedQuaternion rotatedVQuat = (normalizedQuat * vQuat) * invQuat;
 4238        return new Vector3d(rotatedVQuat.X, rotatedVQuat.Y, rotatedVQuat.Z);
 239    }
 240
 241    /// <summary>
 242    /// Rotates this quaternion by a given angle around a specified axis (default: Y-axis).
 243    /// </summary>
 244    /// <param name="sin">Sine of the rotation angle.</param>
 245    /// <param name="cos">Cosine of the rotation angle.</param>
 246    /// <param name="axis">The axis to rotate around (default: Vector3d.Up).</param>
 247    /// <returns>A new quaternion representing the rotated result.</returns>
 248    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 249    public FixedQuaternion Rotated(Fixed64 sin, Fixed64 cos, Vector3d? axis = null)
 250    {
 4251        Vector3d rotateAxis = axis ?? Vector3d.Up;
 252
 253        // The rotation angle is the arc tangent of sin and cos
 4254        Fixed64 angle = FixedMath.Atan2(sin, cos);
 255
 256        // Construct a quaternion representing a rotation around the axis (default is y aka Vector3d.up)
 4257        FixedQuaternion rotationQuat = FromAxisAngle(rotateAxis, angle);
 258
 259        // Apply the rotation and return the result
 4260        return rotationQuat * this;
 261    }
 262
 263    #endregion
 264}

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Numerics/Rotations/FixedQuaternion.Equality.cs

#LineLine coverage
 1//=======================================================================
 2// FixedQuaternion.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
 14public partial struct FixedQuaternion
 15{
 16    #region Equality and HashCode Overrides
 17
 18    /// <inheritdoc/>
 19    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 720    public override bool Equals(object? obj) => obj is FixedQuaternion other && Equals(other);
 21
 22    /// <inheritdoc/>
 23    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 24    public bool Equals(FixedQuaternion other) =>
 4825        X == other.X && Y == other.Y && Z == other.Z && W == other.W;
 26
 27    /// <inheritdoc/>
 28    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 29    public override int GetHashCode() =>
 230        X.GetHashCode() ^ Y.GetHashCode() << 2 ^ Z.GetHashCode() >> 2 ^ W.GetHashCode();
 31
 32    /// <summary>
 33    /// Returns a string that represents the current object in the format "(x, y, z, w)".
 34    /// </summary>
 35    /// <returns>A string containing the values of the object formatted as a tuple.</returns>
 36    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 4537    public override string ToString() => ToString(null, CultureInfo.InvariantCulture);
 38
 39    /// <summary>
 40    /// Returns a string that represents the current object in the format "(x, y, z, w)".
 41    /// </summary>
 42    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 43    public string ToString(string? format, IFormatProvider? formatProvider)
 44    {
 4645        FixedQuaternion value = this;
 4646        return FixedDiagnosticsFormatter.ToString((Span<char> destination, out int charsWritten) =>
 4647            value.TryFormat(destination, out charsWritten, format.AsSpan(), formatProvider));
 48    }
 49
 50    /// <summary>
 51    /// Formats this quaternion into the provided destination buffer.
 52    /// </summary>
 53    public bool TryFormat(
 54        Span<char> destination,
 55        out int charsWritten,
 56        ReadOnlySpan<char> format,
 57        IFormatProvider? provider)
 58    {
 9859        int written = 0;
 9860        if (!FixedDiagnosticsFormatter.Append('(', destination, ref written) ||
 9861            !FixedDiagnosticsFormatter.Append(X, destination, ref written, format, provider) ||
 9862            !FixedDiagnosticsFormatter.Append(", ", destination, ref written) ||
 9863            !FixedDiagnosticsFormatter.Append(Y, destination, ref written, format, provider) ||
 9864            !FixedDiagnosticsFormatter.Append(", ", destination, ref written) ||
 9865            !FixedDiagnosticsFormatter.Append(Z, destination, ref written, format, provider) ||
 9866            !FixedDiagnosticsFormatter.Append(", ", destination, ref written) ||
 9867            !FixedDiagnosticsFormatter.Append(W, destination, ref written, format, provider) ||
 9868            !FixedDiagnosticsFormatter.Append(')', destination, ref written))
 69        {
 170            charsWritten = 0;
 171            return false;
 72        }
 73
 9774        charsWritten = written;
 9775        return true;
 76    }
 77
 78    #endregion
 79}

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Numerics/Rotations/FixedQuaternion.Operators.cs

#LineLine coverage
 1//=======================================================================
 2// FixedQuaternion.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
 12public partial struct FixedQuaternion
 13{
 14    #region Operators
 15
 16    /// <summary>
 17    /// Multiplies two quaternions, combining their rotations into a single quaternion.
 18    /// </summary>
 19    /// <remarks>
 20    /// Quaternion multiplication is not commutative; the order of operands affects the result.
 21    /// This operation is commonly used to concatenate rotations.
 22    /// </remarks>
 23    /// <param name="a">The first quaternion to multiply.</param>
 24    /// <param name="b">The second quaternion to multiply.</param>
 25    /// <returns>A new FixedQuaternion representing the combined rotation of the two input quaternions.</returns>
 26    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 27    public static FixedQuaternion operator *(FixedQuaternion a, FixedQuaternion b) =>
 1928        new((a.W * b.X) + (a.X * b.W) + (a.Y * b.Z) - (a.Z * b.Y),
 1929            (a.W * b.Y) - (a.X * b.Z) + (a.Y * b.W) + (a.Z * b.X),
 1930            (a.W * b.Z) + (a.X * b.Y) - (a.Y * b.X) + (a.Z * b.W),
 1931            (a.W * b.W) - (a.X * b.X) - (a.Y * b.Y) - (a.Z * b.Z));
 32
 33    /// <summary>
 34    /// Multiplies each component of the specified quaternion by the given scalar value.
 35    /// </summary>
 36    /// <param name="q">The quaternion whose components are to be multiplied.</param>
 37    /// <param name="scalar">The scalar value by which to multiply each component of the quaternion.</param>
 38    /// <returns>A new FixedQuaternion whose components are the result of multiplying the corresponding components of th
 39    /// quaternion by the scalar value.</returns>
 40    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 41    public static FixedQuaternion operator *(FixedQuaternion q, Fixed64 scalar) =>
 542        new(q.X * scalar, q.Y * scalar, q.Z * scalar, q.W * scalar);
 43
 44    /// <inheritdoc cref="operator *(FixedQuaternion, Fixed64)"/>
 45    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 46    public static FixedQuaternion operator *(Fixed64 scalar, FixedQuaternion q) =>
 147        new(q.X * scalar, q.Y * scalar, q.Z * scalar, q.W * scalar);
 48
 49    /// <summary>
 50    /// Divides each component of the specified quaternion by the given scalar value.
 51    /// </summary>
 52    /// <remarks>Division by zero will result in an exception or undefined behavior.</remarks>
 53    /// <param name="q">The quaternion whose components are to be divided.</param>
 54    /// <param name="scalar">The scalar value by which to divide each component of the quaternion.</param>
 55    /// <returns>A new FixedQuaternion whose components are the result of dividing the corresponding components of the i
 56    /// quaternion by the scalar value.</returns>
 57    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 58    public static FixedQuaternion operator /(FixedQuaternion q, Fixed64 scalar) =>
 159        new(q.X / scalar, q.Y / scalar, q.Z / scalar, q.W / scalar);
 60
 61    /// <summary>
 62    /// Adds two quaternions component-wise and returns the resulting quaternion.
 63    /// </summary>
 64    /// <remarks>
 65    /// This operation performs a simple component-wise addition.
 66    /// </remarks>
 67    /// <param name="q1">The first quaternion to add.</param>
 68    /// <param name="q2">The second quaternion to add.</param>
 69    /// <returns>A new FixedQuaternion whose components are the sums of the corresponding components of q1 and q2.</retu
 70    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 71    public static FixedQuaternion operator +(FixedQuaternion q1, FixedQuaternion q2) =>
 172        new(q1.X + q2.X, q1.Y + q2.Y, q1.Z + q2.Z, q1.W + q2.W);
 73
 74    /// <summary>
 75    /// Subtracts two quaternions component-wise and returns the resulting quaternion.
 76    /// </summary>
 77    /// <remarks>
 78    /// This operation performs component-wise subtraction.
 79    /// </remarks>
 80    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 81    public static FixedQuaternion operator -(FixedQuaternion q1, FixedQuaternion q2) =>
 182        new(q1.X - q2.X, q1.Y - q2.Y, q1.Z - q2.Z, q1.W - q2.W);
 83
 84    /// <summary>
 85    /// Negates each component of the specified quaternion.
 86    /// </summary>
 87    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 88    public static FixedQuaternion operator -(FixedQuaternion q) =>
 289        new(-q.X, -q.Y, -q.Z, -q.W);
 90
 91    /// <summary>
 92    /// Determines whether two FixedQuaternion instances are equal.
 93    /// </summary>
 94    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 995    public static bool operator ==(FixedQuaternion left, FixedQuaternion right) => left.Equals(right);
 96
 97    /// <summary>
 98    /// Determines whether two FixedQuaternion instances are not equal.
 99    /// </summary>
 100    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1101    public static bool operator !=(FixedQuaternion left, FixedQuaternion right) => !left.Equals(right);
 102
 103    #endregion
 104}

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Numerics/Rotations/FixedQuaternion.Statics.cs

#LineLine coverage
 1//=======================================================================
 2// FixedQuaternion.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
 13public partial struct FixedQuaternion
 14{
 15    #region Quaternion Operations
 16
 17    /// <summary>
 18    /// Checks if this vector has been normalized by checking if the magnitude is close to 1.
 19    /// </summary>
 20    public bool IsNormalized()
 21    {
 322        Fixed64 mag = GetMagnitude(this);
 323        return FixedMath.Abs(mag - Fixed64.One) <= Fixed64.Epsilon;
 24    }
 25
 26    /// <summary>
 27    /// Calculates the magnitude (or length) of the specified quaternion.
 28    /// </summary>
 29    /// <remarks>
 30    /// If rounding errors cause the computed magnitude to be slightly greater than 1 but within epsilon, the result is 
 31    /// This helps maintain numerical stability when working with normalized quaternions.</remarks>
 32    /// <param name="q">The quaternion for which to compute the magnitude.</param>
 33    /// <returns>The magnitude of the quaternion as a Fixed64 value. Returns 0 if the quaternion is the zero quaternion.
 34    public static Fixed64 GetMagnitude(FixedQuaternion q)
 35    {
 10736        Fixed64 mag = (q.X * q.X) + (q.Y * q.Y) + (q.Z * q.Z) + (q.W * q.W);
 37        // If rounding error caused the final magnitude to be slightly above 1, clamp it
 10738        if (mag > Fixed64.One && mag <= Fixed64.One + Fixed64.Epsilon)
 139            return Fixed64.One;
 40
 10641        return mag != Fixed64.Zero ? FixedMath.Sqrt(mag) : Fixed64.Zero;
 42    }
 43
 44    /// <summary>
 45    /// Normalizes the quaternion to a unit quaternion.
 46    /// </summary>
 47    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 48    public static FixedQuaternion GetNormalized(FixedQuaternion q)
 49    {
 9050        Fixed64 mag = GetMagnitude(q);
 51
 52        // If magnitude is zero, return identity quaternion (to avoid divide by zero)
 9053        if (mag == Fixed64.Zero)
 354            return new FixedQuaternion(Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One);
 55
 56        // If already normalized, return as-is
 8757        if (FixedMath.Abs(mag - Fixed64.One) <= Fixed64.Epsilon)
 4258            return q;
 59
 60        // Normalize it exactly
 4561        return new FixedQuaternion(
 4562            q.X / mag,
 4563            q.Y / mag,
 4564            q.Z / mag,
 4565            q.W / mag
 4566        );
 67    }
 68
 69    /// <summary>
 70    /// Divides one quaternion by another using inverse quaternion multiplication.
 71    /// </summary>
 72    /// <remarks>
 73    /// This is equivalent to <c>dividend * Inverse(divisor)</c>.
 74    /// </remarks>
 75    /// <exception cref="InvalidOperationException">
 76    /// Thrown when <paramref name="divisor"/> is not invertible.
 77    /// </exception>
 78    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 79    public static FixedQuaternion Divide(FixedQuaternion dividend, FixedQuaternion divisor)
 80    {
 281        Fixed64 divisorMagnitudeSquared = divisor.MagnitudeSquared;
 82
 283        if (divisorMagnitudeSquared == Fixed64.Zero)
 184            throw new InvalidOperationException("Quaternion divisor is not invertible.");
 85
 186        Fixed64 invNorm = Fixed64.One / divisorMagnitudeSquared;
 187        FixedQuaternion inverseDivisor = new(
 188            -divisor.X * invNorm,
 189            -divisor.Y * invNorm,
 190            -divisor.Z * invNorm,
 191            divisor.W * invNorm);
 92
 193        return dividend * inverseDivisor;
 94    }
 95
 96    /// <summary>
 97    /// Creates a quaternion whose canonical forward direction aligns with the specified direction.
 98    /// </summary>
 99    /// <remarks>
 100    /// The <paramref name="forward"/> and <paramref name="upwards"/> vectors are expressed in
 101    /// FixedMathSharp's canonical basis: <c>+X</c> right, <c>+Y</c> up, and <c>+Z</c> forward.
 102    /// Use <see cref="CoordinateConvention3d"/> or adapter-specific basis conversion before
 103    /// calling this method when external APIs use different semantic axes.
 104    /// </remarks>
 105    /// <param name="forward">The forward direction vector.</param>
 106    /// <param name="upwards">The upwards direction vector (optional, default: Vector3d.Up).</param>
 107    /// <returns>A quaternion representing the rotation from one direction to another.</returns>
 108    public static FixedQuaternion LookRotation(Vector3d forward, Vector3d? upwards = null)
 109    {
 3110        Vector3d up = upwards ?? Vector3d.Up;
 111
 3112        Vector3d forwardNormalized = forward.Normalized;
 3113        Vector3d right = Vector3d.Cross(up.Normalized, forwardNormalized);
 3114        up = Vector3d.Cross(forwardNormalized, right);
 115
 3116        return FromMatrix(new Fixed3x3(
 3117            right.X, right.Y, right.Z,
 3118            up.X, up.Y, up.Z,
 3119            forwardNormalized.X, forwardNormalized.Y, forwardNormalized.Z));
 120    }
 121
 122    /// <summary>
 123    /// Converts a rotation matrix into a quaternion representation.
 124    /// </summary>
 125    /// <param name="matrix">The rotation matrix to convert.</param>
 126    /// <returns>A quaternion representing the same rotation as the matrix.</returns>
 127    public static FixedQuaternion FromMatrix(Fixed3x3 matrix)
 128    {
 36129        Fixed64 trace = matrix.M11 + matrix.M22 + matrix.M33;
 130
 131        Fixed64 w, x, y, z;
 132
 36133        if (trace > Fixed64.Zero)
 134        {
 31135            Fixed64 s = FixedMath.Sqrt(trace + Fixed64.One);
 31136            w = s * Fixed64.Half;
 31137            s = Fixed64.Half / s;
 31138            x = (matrix.M23 - matrix.M32) * s;
 31139            y = (matrix.M31 - matrix.M13) * s;
 31140            z = (matrix.M12 - matrix.M21) * s;
 141        }
 5142        else if (matrix.M11 > matrix.M22 && matrix.M11 > matrix.M33)
 143        {
 1144            Fixed64 s = FixedMath.Sqrt(Fixed64.One + matrix.M11 - matrix.M22 - matrix.M33);
 1145            x = s * Fixed64.Half;
 1146            s = Fixed64.Half / s;
 1147            y = (matrix.M21 + matrix.M12) * s;
 1148            z = (matrix.M13 + matrix.M31) * s;
 1149            w = (matrix.M23 - matrix.M32) * s;
 150        }
 4151        else if (matrix.M22 > matrix.M33)
 152        {
 1153            Fixed64 s = FixedMath.Sqrt(Fixed64.One + matrix.M22 - matrix.M11 - matrix.M33);
 1154            y = s * Fixed64.Half;
 1155            s = Fixed64.Half / s;
 1156            z = (matrix.M32 + matrix.M23) * s;
 1157            x = (matrix.M21 + matrix.M12) * s;
 1158            w = (matrix.M31 - matrix.M13) * s;
 159        }
 160        else
 161        {
 3162            Fixed64 s = FixedMath.Sqrt(Fixed64.One + matrix.M33 - matrix.M11 - matrix.M22);
 3163            z = s * Fixed64.Half;
 3164            s = Fixed64.Half / s;
 3165            x = (matrix.M13 + matrix.M31) * s;
 3166            y = (matrix.M32 + matrix.M23) * s;
 3167            w = (matrix.M12 - matrix.M21) * s;
 168        }
 169
 36170        return new FixedQuaternion(x, y, z, w);
 171    }
 172
 173    /// <summary>
 174    /// Converts a rotation matrix (upper-left 3x3 part of a 4x4 matrix) into a quaternion representation.
 175    /// </summary>
 176    /// <param name="matrix">The 4x4 matrix containing the rotation component.</param>
 177    /// <remarks>Extracts the upper-left 3x3 rotation part of the 4x4</remarks>
 178    /// <returns>A quaternion representing the same rotation as the matrix.</returns>
 179    public static FixedQuaternion FromMatrix(Fixed4x4 matrix)
 180    {
 29181        Fixed3x3 rotationMatrix = new(
 29182            matrix.M11, matrix.M12, matrix.M13,
 29183            matrix.M21, matrix.M22, matrix.M23,
 29184            matrix.M31, matrix.M32, matrix.M33
 29185        );
 186
 29187        return FromMatrix(rotationMatrix);
 188    }
 189
 190    /// <summary>
 191    /// Creates a quaternion representing the rotation needed to align canonical <c>+Z</c> forward with the given direct
 192    /// </summary>
 193    /// <remarks>
 194    /// <see cref="Vector3d.Forward"/> returns <see cref="FixedQuaternion.Identity"/>. If an external API names
 195    /// <c>-Z</c> as forward, convert that direction into the canonical convention before calling
 196    /// this method.
 197    /// </remarks>
 198    /// <param name="direction">The target direction vector.</param>
 199    /// <returns>A quaternion representing the rotation to align with the direction.</returns>
 200    public static FixedQuaternion FromDirection(Vector3d direction)
 201    {
 9202        Fixed64 directionMagnitudeSquared = direction.MagnitudeSquared;
 9203        if (directionMagnitudeSquared == Fixed64.Zero)
 1204            return Identity;
 205
 8206        if (FixedMath.Abs(directionMagnitudeSquared - Fixed64.One) > Fixed64.Epsilon)
 207        {
 1208            Fixed64 directionMagnitude = FixedMath.Sqrt(directionMagnitudeSquared);
 1209            direction = new Vector3d(
 1210                direction.X / directionMagnitude,
 1211                direction.Y / directionMagnitude,
 1212                direction.Z / directionMagnitude);
 213        }
 214
 8215        Fixed64 dot = direction.Z;
 8216        if (dot <= -Fixed64.One + Fixed64.Epsilon)
 1217            return FromAxisAngle(Vector3d.Up, Fixed64.Pi);
 218
 7219        if (dot >= Fixed64.One - Fixed64.Epsilon)
 2220            return Identity;
 221
 5222        if (dot < Fixed64.FromRaw(NearOppositeDirectionDotRaw))
 223        {
 1224            Vector3d axis = new(-direction.Y, direction.X, Fixed64.Zero);
 1225            Fixed64 axisMagnitude = axis.Magnitude;
 1226            axis = new Vector3d(
 1227                axis.X / axisMagnitude,
 1228                axis.Y / axisMagnitude,
 1229                Fixed64.Zero);
 230
 1231            return FromAxisAngle(axis, FixedMath.Acos(dot));
 232        }
 233
 4234        Fixed64 scale = FixedMath.Sqrt((Fixed64.One + dot) * Fixed64.Two);
 4235        return new FixedQuaternion(
 4236            -direction.Y / scale,
 4237            direction.X / scale,
 4238            Fixed64.Zero,
 4239            scale * Fixed64.Half);
 240    }
 241
 242    /// <summary>
 243    /// Creates a quaternion representing a rotation around a specified axis by a given angle.
 244    /// </summary>
 245    /// <param name="axis">The axis to rotate around (must be normalized).</param>
 246    /// <param name="angle">The rotation angle in radians.</param>
 247    /// <returns>A quaternion representing the rotation.</returns>
 248    public static FixedQuaternion FromAxisAngle(Vector3d axis, Fixed64 angle)
 249    {
 250        // Check if the angle is in a valid range (-pi, pi)
 48251        if (angle < -Fixed64.Pi || angle > Fixed64.Pi)
 3252            throw new ArgumentOutOfRangeException(nameof(angle), $"Angle must be in the range ({-Fixed64.Pi}, {Fixed64.P
 253
 45254        Fixed64 axisMagnitudeSquared = axis.MagnitudeSquared;
 45255        if (axisMagnitudeSquared == Fixed64.Zero)
 1256            return Identity;
 257
 44258        if (FixedMath.Abs(axisMagnitudeSquared - Fixed64.One) > Fixed64.Epsilon)
 259        {
 2260            Fixed64 axisMagnitude = FixedMath.Sqrt(axisMagnitudeSquared);
 2261            axis = new Vector3d(
 2262                axis.X / axisMagnitude,
 2263                axis.Y / axisMagnitude,
 2264                axis.Z / axisMagnitude);
 265        }
 266
 44267        Fixed64 halfAngle = angle / Fixed64.Two;  // Half-angle formula
 44268        Fixed64 sinHalfAngle = FixedMath.Sin(halfAngle);
 44269        Fixed64 cosHalfAngle = FixedMath.Cos(halfAngle);
 270
 44271        return new FixedQuaternion(
 44272            axis.X * sinHalfAngle,
 44273            axis.Y * sinHalfAngle,
 44274            axis.Z * sinHalfAngle,
 44275            cosHalfAngle);
 276    }
 277
 278    /// <summary>
 279    /// Assume the input angles are in degrees and converts them to radians before calling <see cref="FromEulerAngles"/>
 280    /// </summary>
 281    /// <param name="pitch"></param>
 282    /// <param name="yaw"></param>
 283    /// <param name="roll"></param>
 284    /// <returns></returns>
 285    public static FixedQuaternion FromEulerAnglesInDegrees(Fixed64 pitch, Fixed64 yaw, Fixed64 roll)
 286    {
 287        // Convert input angles from degrees to radians
 37288        pitch = FixedMath.DegToRad(pitch);
 37289        yaw = FixedMath.DegToRad(yaw);
 37290        roll = FixedMath.DegToRad(roll);
 291
 292        // Call the original method that expects angles in radians
 37293        return FromEulerAngles(pitch, yaw, roll);
 294    }
 295
 296    /// <summary>
 297    /// Converts Euler angles (pitch, yaw, roll) to a quaternion and normalizes the result afterwards.
 298    /// Assumes the input angles are in radians.
 299    /// </summary>
 300    /// <remarks>
 301    /// The order of operations is YXZ or yaw-pitch-roll
 302    /// </remarks>
 303    public static FixedQuaternion FromEulerAngles(Fixed64 pitch, Fixed64 yaw, Fixed64 roll)
 304    {
 305        // Check if the angles are in a valid range (-pi, pi)
 52306        if (pitch < -Fixed64.Pi || pitch > Fixed64.Pi)
 3307            throw new ArgumentOutOfRangeException(nameof(pitch), $"Pitch must be in the range ({-Fixed64.Pi}, {Fixed64.P
 308
 49309        if (yaw < -Fixed64.Pi || yaw > Fixed64.Pi)
 3310            throw new ArgumentOutOfRangeException(nameof(yaw), $"Yaw must be in the range ({-Fixed64.Pi}, {Fixed64.Pi}),
 311
 46312        if (roll < -Fixed64.Pi || roll > Fixed64.Pi)
 3313            throw new ArgumentOutOfRangeException(nameof(roll), $"Roll must be in the range ({-Fixed64.Pi}, {Fixed64.Pi}
 314
 43315        Fixed64 halfPitch = pitch / Fixed64.Two;
 43316        Fixed64 halfYaw = yaw / Fixed64.Two;
 43317        Fixed64 halfRoll = roll / Fixed64.Two;
 318
 43319        Fixed64 sx = FixedMath.Sin(halfPitch);
 43320        Fixed64 cx = FixedMath.Cos(halfPitch);
 43321        Fixed64 sy = FixedMath.Sin(halfYaw);
 43322        Fixed64 cy = FixedMath.Cos(halfYaw);
 43323        Fixed64 sz = FixedMath.Sin(halfRoll);
 43324        Fixed64 cz = FixedMath.Cos(halfRoll);
 325
 326        // q = qy * qx * qz
 43327        Fixed64 x = (cx * sy * sz) + (cy * cz * sx);
 43328        Fixed64 y = (cx * cz * sy) - (cy * sx * sz);
 43329        Fixed64 z = (cx * cy * sz) - (cz * sx * sy);
 43330        Fixed64 w = (cx * cy * cz) + (sx * sy * sz);
 331
 43332        return GetNormalized(new FixedQuaternion(x, y, z, w));
 333    }
 334
 335    /// <summary>
 336    /// Computes the logarithm of a quaternion, which represents the rotational displacement.
 337    /// This is useful for interpolation and angular velocity calculations.
 338    /// </summary>
 339    /// <param name="q">The quaternion to compute the logarithm of.</param>
 340    /// <returns>A Vector3d representing the logarithm of the quaternion (axis-angle representation).</returns>
 341    /// <remarks>
 342    /// The logarithm of a unit quaternion is given by:
 343    /// log(q) = (θ * v̀‚), where:
 344    /// - Î¸ = 2 * acos(w) is the rotation angle.
 345    /// - v̀‚ = (x, y, z) / ||(x, y, z)|| is the unit vector representing the axis of rotation.
 346    /// If the quaternion is close to identity, the function returns a zero vector to avoid numerical instability.
 347    /// </remarks>
 348    public static Vector3d QuaternionLog(FixedQuaternion q)
 349    {
 350        // Ensure the quaternion is normalized
 9351        q = q.Normalized;
 352
 353        // Extract vector part
 9354        Vector3d v = new(q.X, q.Y, q.Z);
 9355        Fixed64 vLength = v.Magnitude;
 356
 357        // If rotation is very small, avoid division by zero
 9358        if (vLength < Fixed64.FromRaw(0x00001000L)) // Small epsilon
 3359            return Vector3d.Zero;
 360
 361        // Compute angle (theta = 2 * acos(w))
 6362        Fixed64 theta = Fixed64.Two * FixedMath.Acos(q.W);
 363
 364        // Convert to angular velocity
 6365        return (v / vLength) * theta;
 366    }
 367
 368    /// <summary>
 369    /// Computes the angular velocity required to move from `previousRotation` to `currentRotation` over a given time st
 370    /// </summary>
 371    /// <param name="currentRotation">The current orientation as a quaternion.</param>
 372    /// <param name="previousRotation">The previous orientation as a quaternion.</param>
 373    /// <param name="deltaTime">The time step over which the rotation occurs.</param>
 374    /// <returns>A Vector3d representing the angular velocity (in radians per second).</returns>
 375    /// <remarks>
 376    /// This function calculates the change in rotation over `deltaTime` and converts it into angular velocity.
 377    /// - First, it computes the relative rotation: `rotationDelta = currentRotation * previousRotation.Inverse()`.
 378    /// - Then, it applies `QuaternionLog(rotationDelta)` to extract the axis-angle representation.
 379    /// - Finally, it divides by `deltaTime` to compute the angular velocity.
 380    /// </remarks>
 381    public static Vector3d ToAngularVelocity(
 382        FixedQuaternion currentRotation,
 383        FixedQuaternion previousRotation,
 384        Fixed64 deltaTime)
 385    {
 4386        FixedQuaternion rotationDelta = currentRotation * previousRotation.Inverse();
 4387        Vector3d angularDisplacement = QuaternionLog(rotationDelta);
 388
 4389        return angularDisplacement / deltaTime; // Convert to angular velocity
 390    }
 391
 392    /// <summary>
 393    /// Performs a simple linear interpolation between the components of the input quaternions
 394    /// </summary>
 395    public static FixedQuaternion Lerp(FixedQuaternion a, FixedQuaternion b, Fixed64 t)
 396    {
 5397        t = FixedMath.Clamp01(t);
 398
 5399        if (Dot(a, b) < Fixed64.Zero)
 1400            b = -b;
 401
 402        FixedQuaternion result;
 5403        Fixed64 oneMinusT = Fixed64.One - t;
 5404        result.X = a.X * oneMinusT + b.X * t;
 5405        result.Y = a.Y * oneMinusT + b.Y * t;
 5406        result.Z = a.Z * oneMinusT + b.Z * t;
 5407        result.W = a.W * oneMinusT + b.W * t;
 408
 5409        result.NormalizeInPlace();
 410
 5411        return result;
 412    }
 413
 414    /// <summary>
 415    ///  Calculates the spherical linear interpolation, which results in a smoother and more accurate rotation interpola
 416    /// </summary>
 417    public static FixedQuaternion Slerp(FixedQuaternion a, FixedQuaternion b, Fixed64 t)
 418    {
 4419        t = FixedMath.Clamp01(t);
 420
 4421        Fixed64 cosOmega = a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W;
 422
 423        // If the dot product is negative, negate one of the input quaternions.
 424        // This ensures that the interpolation takes the shortest path around the sphere.
 4425        if (cosOmega < Fixed64.Zero)
 426        {
 1427            b.X = -b.X;
 1428            b.Y = -b.Y;
 1429            b.Z = -b.Z;
 1430            b.W = -b.W;
 1431            cosOmega = -cosOmega;
 432        }
 433
 434        Fixed64 k0, k1;
 435
 436        // If the quaternions are close, use linear interpolation
 4437        if (cosOmega > Fixed64.One - Fixed64.Epsilon)
 438        {
 1439            k0 = Fixed64.One - t;
 1440            k1 = t;
 441        }
 442        else
 443        {
 444            // Otherwise, use spherical linear interpolation
 3445            Fixed64 sinOmega = FixedMath.Sqrt(Fixed64.One - cosOmega * cosOmega);
 3446            Fixed64 omega = FixedMath.Atan2(sinOmega, cosOmega);
 447
 3448            k0 = FixedMath.Sin((Fixed64.One - t) * omega) / sinOmega;
 3449            k1 = FixedMath.Sin(t * omega) / sinOmega;
 450        }
 451
 452        FixedQuaternion result;
 4453        result.X = a.X * k0 + b.X * k1;
 4454        result.Y = a.Y * k0 + b.Y * k1;
 4455        result.Z = a.Z * k0 + b.Z * k1;
 4456        result.W = a.W * k0 + b.W * k1;
 457
 4458        return result;
 459    }
 460
 461    /// <summary>
 462    /// Returns the angle in degrees between two rotations a and b.
 463    /// </summary>
 464    /// <param name="a">The first rotation.</param>
 465    /// <param name="b">The second rotation.</param>
 466    /// <returns>The angle in degrees between the two rotations.</returns>
 467    public static Fixed64 Angle(FixedQuaternion a, FixedQuaternion b)
 468    {
 469        // Calculate the dot product of the two quaternions
 3470        Fixed64 dot = Dot(a, b);
 471
 472        // Ensure the dot product is in the range of [-1, 1] to avoid floating-point inaccuracies
 3473        dot = FixedMath.Clamp(dot, -Fixed64.One, Fixed64.One);
 474
 475        // Calculate the angle between the two quaternions using the inverse cosine (arccos)
 476        // arccos(dot(a, b)) gives us the angle in radians, so we convert it to degrees
 3477        Fixed64 angleInRadians = FixedMath.Acos(dot);
 478
 479        // Convert the angle from radians to degrees
 3480        Fixed64 angleInDegrees = FixedMath.RadToDeg(angleInRadians);
 481
 3482        return angleInDegrees;
 483    }
 484
 485    /// <summary>
 486    /// Creates a quaternion from an angle and axis.
 487    /// </summary>
 488    /// <param name="angle">The angle in degrees.</param>
 489    /// <param name="axis">The axis to rotate around (must be normalized).</param>
 490    /// <returns>A quaternion representing the rotation.</returns>
 491    public static FixedQuaternion AngleAxis(Fixed64 angle, Vector3d axis)
 492    {
 493        // Convert the angle to radians
 1494        angle = angle.ToRadians();
 495
 496        // Normalize the axis
 1497        axis = axis.Normalized;
 498
 499        // Use the half-angle formula (sin(theta / 2), cos(theta / 2))
 1500        Fixed64 halfAngle = angle / Fixed64.Two;
 1501        Fixed64 sinHalfAngle = FixedMath.Sin(halfAngle);
 1502        Fixed64 cosHalfAngle = FixedMath.Cos(halfAngle);
 503
 1504        return new FixedQuaternion(
 1505            axis.X * sinHalfAngle,
 1506            axis.Y * sinHalfAngle,
 1507            axis.Z * sinHalfAngle,
 1508            cosHalfAngle
 1509        );
 510    }
 511
 512    /// <summary>
 513    /// Calculates the dot product of two quaternions.
 514    /// </summary>
 515    /// <param name="a">The first quaternion.</param>
 516    /// <param name="b">The second quaternion.</param>
 517    /// <returns>The dot product of the two quaternions.</returns>
 518    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 15519    public static Fixed64 Dot(FixedQuaternion a, FixedQuaternion b) => a.W * b.W + a.X * b.X + a.Y * b.Y + a.Z * b.Z;
 520
 521    #endregion
 522}

Methods/Properties

ToEulerAngles()
ToDirection()
ToMatrix3x3()
Deconstruct(FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&)
Deconstruct(System.Int32&,System.Int32&,System.Int32&,System.Int32&)
Deconstruct(System.Int64&,System.Int64&,System.Int64&,System.Int64&)
Deconstruct(System.Double&,System.Double&,System.Double&,System.Double&)
get_Identity()
get_Zero()
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
get_Normalized()
get_Magnitude()
get_MagnitudeSquared()
get_EulerAngles()
set_EulerAngles(FixedMathSharp.Vector3d)
get_Item(System.Int32)
set_Item(System.Int32,FixedMathSharp.Fixed64)
Set(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
NormalizeInPlace()
Conjugate()
Inverse()
Rotate(FixedMathSharp.Vector3d)
Rotated(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Nullable`1<FixedMathSharp.Vector3d>)
Equals(System.Object)
Equals(FixedMathSharp.FixedQuaternion)
GetHashCode()
ToString()
ToString(System.String,System.IFormatProvider)
TryFormat(System.Span`1<System.Char>,System.Int32&,System.ReadOnlySpan`1<System.Char>,System.IFormatProvider)
op_Multiply(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion)
op_Multiply(FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64)
op_Multiply(FixedMathSharp.Fixed64,FixedMathSharp.FixedQuaternion)
op_Division(FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64)
op_Addition(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion)
op_Subtraction(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion)
op_UnaryNegation(FixedMathSharp.FixedQuaternion)
op_Equality(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion)
op_Inequality(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion)
IsNormalized()
GetMagnitude(FixedMathSharp.FixedQuaternion)
GetNormalized(FixedMathSharp.FixedQuaternion)
Divide(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion)
LookRotation(FixedMathSharp.Vector3d,System.Nullable`1<FixedMathSharp.Vector3d>)
FromMatrix(FixedMathSharp.Fixed3x3)
FromMatrix(FixedMathSharp.Fixed4x4)
FromDirection(FixedMathSharp.Vector3d)
FromAxisAngle(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
FromEulerAnglesInDegrees(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
FromEulerAngles(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
QuaternionLog(FixedMathSharp.FixedQuaternion)
ToAngularVelocity(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64)
Lerp(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64)
Slerp(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64)
Angle(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion)
AngleAxis(FixedMathSharp.Fixed64,FixedMathSharp.Vector3d)
Dot(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion)