< Summary

Information
Class: FixedMathSharp.FixedQuaternion
Assembly: FixedMathSharp
File(s): /home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Numerics/FixedQuaternion.cs
Line coverage
100%
Covered lines: 350
Uncovered lines: 0
Coverable lines: 350
Total lines: 815
Line coverage: 100%
Branch coverage
94%
Covered branches: 68
Total branches: 72
Branch coverage: 94.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Normal()100%11100%
get_EulerAngles()100%11100%
set_EulerAngles(...)100%11100%
get_Item(...)100%55100%
set_Item(...)100%55100%
Set(...)100%11100%
Normalize()100%11100%
Conjugate()100%11100%
Inverse()100%44100%
Rotate(...)100%11100%
Rotated(...)100%22100%
IsNormalized()100%11100%
GetMagnitude(...)100%66100%
GetNormalized(...)100%44100%
LookRotation(...)100%22100%
FromMatrix(...)100%88100%
FromMatrix(...)100%11100%
FromDirection(...)100%22100%
FromAxisAngle(...)83.33%66100%
FromEulerAnglesInDegrees(...)100%11100%
FromEulerAngles(...)75%1212100%
QuaternionLog(...)100%22100%
ToAngularVelocity(...)100%11100%
Lerp(...)100%11100%
Slerp(...)100%44100%
Angle(...)100%11100%
AngleAxis(...)100%11100%
Dot(...)100%11100%
op_Multiply(...)100%11100%
op_Multiply(...)100%11100%
op_Multiply(...)100%11100%
op_Division(...)100%11100%
op_Division(...)100%11100%
op_Addition(...)100%11100%
op_Equality(...)100%11100%
op_Inequality(...)100%11100%
ToEulerAngles()100%22100%
ToDirection()100%11100%
ToMatrix3x3()100%11100%
Equals(...)100%22100%
Equals(...)100%66100%
GetHashCode()100%11100%
ToString()100%11100%

File(s)

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

#LineLine coverage
 1using MemoryPack;
 2using System;
 3using System.Runtime.CompilerServices;
 4using System.Text.Json.Serialization;
 5
 6namespace FixedMathSharp;
 7
 8/// <summary>
 9/// Represents a quaternion (x, y, z, w) with fixed-point numbers.
 10/// Quaternions are useful for representing rotations and can be used to perform smooth rotations and avoid gimbal lock.
 11/// </summary>
 12[Serializable]
 13[MemoryPackable]
 14public partial struct FixedQuaternion : IEquatable<FixedQuaternion>
 15{
 16    #region Static Readonly Fields
 17
 18    /// <summary>
 19    /// Identity quaternion (0, 0, 0, 1).
 20    /// </summary>
 21    public static readonly FixedQuaternion Identity = new FixedQuaternion(Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixe
 22
 23    /// <summary>
 24    /// Empty quaternion (0, 0, 0, 0).
 25    /// </summary>
 26    public static readonly FixedQuaternion Zero = new FixedQuaternion(Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.
 27
 28    #endregion
 29
 30    #region Fields and Constants
 31
 32    [JsonInclude]
 33    [MemoryPackOrder(0)]
 34    public Fixed64 x;
 35
 36    [JsonInclude]
 37    [MemoryPackOrder(1)]
 38    public Fixed64 y;
 39
 40    [JsonInclude]
 41    [MemoryPackOrder(2)]
 42    public Fixed64 z;
 43
 44    [JsonInclude]
 45    [MemoryPackOrder(3)]
 46    public Fixed64 w;
 47
 48    #endregion
 49
 50    #region Constructors
 51
 52    /// <summary>
 53    /// Creates a new FixedQuaternion with the specified components.
 54    /// </summary>
 55    public FixedQuaternion(Fixed64 x, Fixed64 y, Fixed64 z, Fixed64 w)
 16456    {
 16457        this.x = x;
 16458        this.y = y;
 16459        this.z = z;
 16460        this.w = w;
 16461    }
 62
 63    #endregion
 64
 65    #region Properties
 66
 67    /// <summary>
 68    /// Normalized version of this quaternion.
 69    /// </summary>
 70    [JsonIgnore]
 71    [MemoryPackIgnore]
 72    public FixedQuaternion Normal
 73    {
 74        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1175        get => GetNormalized(this);
 76    }
 77
 78    /// <summary>
 79    /// Returns the Euler angles (in degrees) of this quaternion.
 80    /// </summary>
 81    [JsonIgnore]
 82    [MemoryPackIgnore]
 83    public Vector3d EulerAngles
 84    {
 85        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 186        get => ToEulerAngles();
 87        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 188        set => this = FromEulerAnglesInDegrees(value.x, value.y, value.z);
 89    }
 90
 91    [JsonIgnore]
 92    [MemoryPackIgnore]
 93    public Fixed64 this[int index]
 94    {
 95        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 96        get
 597        {
 598            return index switch
 599            {
 1100                0 => x,
 1101                1 => y,
 1102                2 => z,
 1103                3 => w,
 1104                _ => throw new IndexOutOfRangeException("Invalid FixedQuaternion index!"),
 5105            };
 4106        }
 107        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 108        set
 5109        {
 5110            switch (index)
 111            {
 112                case 0:
 1113                    x = value;
 1114                    break;
 115                case 1:
 1116                    y = value;
 1117                    break;
 118                case 2:
 1119                    z = value;
 1120                    break;
 121                case 3:
 1122                    w = value;
 1123                    break;
 124                default:
 1125                    throw new IndexOutOfRangeException("Invalid FixedQuaternion index!");
 126            }
 4127        }
 128    }
 129
 130    #endregion
 131
 132    #region Methods (Instance)
 133
 134    /// <summary>
 135    /// Set x, y, z and w components of an existing Quaternion.
 136    /// </summary>
 137    /// <param name="newX"></param>
 138    /// <param name="newY"></param>
 139    /// <param name="newZ"></param>
 140    /// <param name="newW"></param>
 141    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 142    public void Set(Fixed64 newX, Fixed64 newY, Fixed64 newZ, Fixed64 newW)
 1143    {
 1144        x = newX;
 1145        y = newY;
 1146        z = newZ;
 1147        w = newW;
 1148    }
 149
 150    /// <summary>
 151    /// Normalizes this quaternion in place.
 152    /// </summary>
 153    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 154    public FixedQuaternion Normalize()
 31155    {
 31156        return this = GetNormalized(this);
 31157    }
 158
 159    /// <summary>
 160    /// Returns the conjugate of this quaternion (inverses the rotational effect).
 161    /// </summary>
 162    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 163    public FixedQuaternion Conjugate()
 1164    {
 1165        return new FixedQuaternion(-x, -y, -z, w);
 1166    }
 167
 168    /// <summary>
 169    /// Returns the inverse of this quaternion.
 170    /// </summary>
 171    public FixedQuaternion Inverse()
 9172    {
 14173        if (this == Identity) return Identity;
 4174        Fixed64 norm = x * x + y * y + z * z + w * w;
 5175        if (norm == Fixed64.Zero) return this; // Handle division by zero by returning the same quaternion
 176
 3177        Fixed64 invNorm = Fixed64.One / norm;
 3178        return new FixedQuaternion(x * -invNorm, y * -invNorm, z * -invNorm, w * invNorm);
 9179    }
 180
 181    /// <summary>
 182    /// Rotates a vector by this quaternion.
 183    /// </summary>
 184    public Vector3d Rotate(Vector3d v)
 1185    {
 1186        FixedQuaternion normalizedQuat = Normal;
 1187        FixedQuaternion vQuat = new FixedQuaternion(v.x, v.y, v.z, Fixed64.Zero);
 1188        FixedQuaternion invQuat = normalizedQuat.Inverse();
 1189        FixedQuaternion rotatedVQuat = normalizedQuat * vQuat * invQuat;
 1190        return new Vector3d(rotatedVQuat.x, rotatedVQuat.y, rotatedVQuat.z).Normalize();
 1191    }
 192
 193    /// <summary>
 194    /// Rotates this quaternion by a given angle around a specified axis (default: Y-axis).
 195    /// </summary>
 196    /// <param name="sin">Sine of the rotation angle.</param>
 197    /// <param name="cos">Cosine of the rotation angle.</param>
 198    /// <param name="axis">The axis to rotate around (default: Vector3d.Up).</param>
 199    /// <returns>A new quaternion representing the rotated result.</returns>
 200    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 201    public FixedQuaternion Rotated(Fixed64 sin, Fixed64 cos, Vector3d? axis = null)
 4202    {
 4203        Vector3d rotateAxis = axis ?? Vector3d.Up;
 204
 205        // The rotation angle is the arc tangent of sin and cos
 4206        Fixed64 angle = FixedMath.Atan2(sin, cos);
 207
 208        // Construct a quaternion representing a rotation around the axis (default is y aka Vector3d.up)
 4209        FixedQuaternion rotationQuat = FromAxisAngle(rotateAxis, angle);
 210
 211        // Apply the rotation and return the result
 4212        return rotationQuat * this;
 4213    }
 214
 215    #endregion
 216
 217    #region Quaternion Operations
 218
 219    /// <summary>
 220    /// Checks if this vector has been normalized by checking if the magnitude is close to 1.
 221    /// </summary>
 222    public bool IsNormalized()
 3223    {
 3224        Fixed64 mag = GetMagnitude(this);
 3225        return FixedMath.Abs(mag - Fixed64.One) <= Fixed64.Epsilon;
 3226    }
 227
 228    public static Fixed64 GetMagnitude(FixedQuaternion q)
 87229    {
 87230        Fixed64 mag = (q.x * q.x) + (q.y * q.y) + (q.z * q.z) + (q.w * q.w);
 231        // If rounding error caused the final magnitude to be slightly above 1, clamp it
 87232        if (mag > Fixed64.One && mag <= Fixed64.One + Fixed64.Epsilon)
 4233            return Fixed64.One;
 234
 83235        return mag != Fixed64.Zero ? FixedMath.Sqrt(mag) : Fixed64.Zero;
 87236    }
 237
 238    /// <summary>
 239    /// Normalizes the quaternion to a unit quaternion.
 240    /// </summary>
 241    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 242    public static FixedQuaternion GetNormalized(FixedQuaternion q)
 81243    {
 81244        Fixed64 mag = GetMagnitude(q);
 245
 246        // If magnitude is zero, return identity quaternion (to avoid divide by zero)
 81247        if (mag == Fixed64.Zero)
 1248            return new FixedQuaternion(Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One);
 249
 250        // If already normalized, return as-is
 80251        if (mag == Fixed64.One)
 62252            return q;
 253
 254        // Normalize it exactly
 18255        return new FixedQuaternion(
 18256            q.x / mag,
 18257            q.y / mag,
 18258            q.z / mag,
 18259            q.w / mag
 18260        );
 81261    }
 262
 263    /// <summary>
 264    /// Creates a quaternion that rotates one vector to align with another.
 265    /// </summary>
 266    /// <param name="forward">The forward direction vector.</param>
 267    /// <param name="upwards">The upwards direction vector (optional, default: Vector3d.Up).</param>
 268    /// <returns>A quaternion representing the rotation from one direction to another.</returns>
 269    public static FixedQuaternion LookRotation(Vector3d forward, Vector3d? upwards = null)
 2270    {
 2271        Vector3d up = upwards ?? Vector3d.Up;
 272
 2273        Vector3d forwardNormalized = forward.Normal;
 2274        Vector3d right = Vector3d.Cross(up.Normal, forwardNormalized);
 2275        up = Vector3d.Cross(forwardNormalized, right);
 276
 2277        return FromMatrix(new Fixed3x3(right.x, up.x, forwardNormalized.x,
 2278                                        right.y, up.y, forwardNormalized.y,
 2279                                        right.z, up.z, forwardNormalized.z));
 2280    }
 281
 282    /// <summary>
 283    /// Converts a rotation matrix into a quaternion representation.
 284    /// </summary>
 285    /// <param name="matrix">The rotation matrix to convert.</param>
 286    /// <returns>A quaternion representing the same rotation as the matrix.</returns>
 287    public static FixedQuaternion FromMatrix(Fixed3x3 matrix)
 29288    {
 29289        Fixed64 trace = matrix.m00 + matrix.m11 + matrix.m22;
 290
 291        Fixed64 w, x, y, z;
 292
 29293        if (trace > Fixed64.Zero)
 24294        {
 24295            Fixed64 s = FixedMath.Sqrt(trace + Fixed64.One);
 24296            w = s * Fixed64.Half;
 24297            s = Fixed64.Half / s;
 24298            x = (matrix.m21 - matrix.m12) * s;
 24299            y = (matrix.m02 - matrix.m20) * s;
 24300            z = (matrix.m10 - matrix.m01) * s;
 24301        }
 5302        else if (matrix.m00 > matrix.m11 && matrix.m00 > matrix.m22)
 1303        {
 1304            Fixed64 s = FixedMath.Sqrt(Fixed64.One + matrix.m00 - matrix.m11 - matrix.m22);
 1305            x = s * Fixed64.Half;
 1306            s = Fixed64.Half / s;
 1307            y = (matrix.m10 + matrix.m01) * s;
 1308            z = (matrix.m02 + matrix.m20) * s;
 1309            w = (matrix.m21 - matrix.m12) * s;
 1310        }
 4311        else if (matrix.m11 > matrix.m22)
 1312        {
 1313            Fixed64 s = FixedMath.Sqrt(Fixed64.One + matrix.m11 - matrix.m00 - matrix.m22);
 1314            y = s * Fixed64.Half;
 1315            s = Fixed64.Half / s;
 1316            z = (matrix.m21 + matrix.m12) * s;
 1317            x = (matrix.m10 + matrix.m01) * s;
 1318            w = (matrix.m02 - matrix.m20) * s;
 1319        }
 320        else
 3321        {
 3322            Fixed64 s = FixedMath.Sqrt(Fixed64.One + matrix.m22 - matrix.m00 - matrix.m11);
 3323            z = s * Fixed64.Half;
 3324            s = Fixed64.Half / s;
 3325            x = (matrix.m02 + matrix.m20) * s;
 3326            y = (matrix.m21 + matrix.m12) * s;
 3327            w = (matrix.m10 - matrix.m01) * s;
 3328        }
 329
 29330        return new FixedQuaternion(x, y, z, w);
 29331    }
 332
 333    /// <summary>
 334    /// Converts a rotation matrix (upper-left 3x3 part of a 4x4 matrix) into a quaternion representation.
 335    /// </summary>
 336    /// <param name="matrix">The 4x4 matrix containing the rotation component.</param>
 337    /// <remarks>Extracts the upper-left 3x3 rotation part of the 4x4</remarks>
 338    /// <returns>A quaternion representing the same rotation as the matrix.</returns>
 339    public static FixedQuaternion FromMatrix(Fixed4x4 matrix)
 23340    {
 341
 23342        var rotationMatrix = new Fixed3x3(
 23343            matrix.m00, matrix.m01, matrix.m02,
 23344            matrix.m10, matrix.m11, matrix.m12,
 23345            matrix.m20, matrix.m21, matrix.m22
 23346        );
 347
 23348        return FromMatrix(rotationMatrix);
 23349    }
 350
 351    /// <summary>
 352    /// Creates a quaternion representing the rotation needed to align the forward vector with the given direction.
 353    /// </summary>
 354    /// <param name="direction">The target direction vector.</param>
 355    /// <returns>A quaternion representing the rotation to align with the direction.</returns>
 356    public static FixedQuaternion FromDirection(Vector3d direction)
 2357    {
 358        // Compute the rotation axis as the cross product of the standard forward vector and the desired direction
 2359        Vector3d axis = Vector3d.Cross(Vector3d.Forward, direction);
 2360        Fixed64 axisLength = axis.Magnitude;
 361
 362        // If the axis length is very close to zero, it means that the desired direction is almost equal to the standard
 2363        if (axisLength.Abs() == Fixed64.Zero)
 1364            return Identity;  // Return the identity quaternion if no rotation is needed
 365
 366        // Normalize the rotation axis
 1367        axis = (axis / axisLength).Normal;
 368
 369        // Compute the angle between the standard forward vector and the desired direction
 1370        Fixed64 angle = FixedMath.Acos(Vector3d.Dot(Vector3d.Forward, direction));
 371
 372        // Compute the rotation quaternion from the axis and angle
 1373        return FromAxisAngle(axis, angle);
 2374    }
 375
 376    /// <summary>
 377    /// Creates a quaternion representing a rotation around a specified axis by a given angle.
 378    /// </summary>
 379    /// <param name="axis">The axis to rotate around (must be normalized).</param>
 380    /// <param name="angle">The rotation angle in radians.</param>
 381    /// <returns>A quaternion representing the rotation.</returns>
 382    public static FixedQuaternion FromAxisAngle(Vector3d axis, Fixed64 angle)
 36383    {
 384        // Check if the axis is a unit vector
 36385        if (!axis.IsNormalized())
 1386            axis = axis.Normalize();
 387
 388        // Check if the angle is in a valid range (-pi, pi)
 36389        if (angle < -FixedMath.PI || angle > FixedMath.PI)
 1390            throw new ArgumentOutOfRangeException(nameof(angle), angle, $"Angle must be in the range ({-FixedMath.PI}, {
 391
 35392        Fixed64 halfAngle = angle / Fixed64.Two;  // Half-angle formula
 35393        Fixed64 sinHalfAngle = FixedMath.Sin(halfAngle);
 35394        Fixed64 cosHalfAngle = FixedMath.Cos(halfAngle);
 395
 35396        return new FixedQuaternion(axis.x * sinHalfAngle, axis.y * sinHalfAngle, axis.z * sinHalfAngle, cosHalfAngle);
 35397    }
 398
 399    /// <summary>
 400    /// Assume the input angles are in degrees and converts them to radians before calling <see cref="FromEulerAngles"/>
 401    /// </summary>
 402    /// <param name="pitch"></param>
 403    /// <param name="yaw"></param>
 404    /// <param name="roll"></param>
 405    /// <returns></returns>
 406    public static FixedQuaternion FromEulerAnglesInDegrees(Fixed64 pitch, Fixed64 yaw, Fixed64 roll)
 27407    {
 408        // Convert input angles from degrees to radians
 27409        pitch = FixedMath.DegToRad(pitch);
 27410        yaw = FixedMath.DegToRad(yaw);
 27411        roll = FixedMath.DegToRad(roll);
 412
 413        // Call the original method that expects angles in radians
 27414        return FromEulerAngles(pitch, yaw, roll).Normalize();
 27415    }
 416
 417    /// <summary>
 418    /// Converts Euler angles (pitch, yaw, roll) to a quaternion and normalizes the result afterwards. Assumes the input
 419    /// </summary>
 420    /// <remarks>
 421    /// The order of operations is YZX or yaw-roll-pitch, commonly used in applications such as robotics.
 422    /// </remarks>
 423    public static FixedQuaternion FromEulerAngles(Fixed64 pitch, Fixed64 yaw, Fixed64 roll)
 32424    {
 425        // Check if the angles are in a valid range (-pi, pi)
 32426        if (pitch < -FixedMath.PI || pitch > FixedMath.PI)
 1427            throw new ArgumentOutOfRangeException(nameof(pitch), pitch, $"Pitch must be in the range ({-FixedMath.PI}, {
 31428        if (yaw < -FixedMath.PI || yaw > FixedMath.PI)
 1429            throw new ArgumentOutOfRangeException(nameof(yaw), yaw, $"Yaw must be in the range ({-FixedMath.PI}, {FixedM
 30430        if (roll < -FixedMath.PI || roll > FixedMath.PI)
 1431            throw new ArgumentOutOfRangeException(nameof(roll), roll, $"Roll must be in the range ({-FixedMath.PI}, {Fix
 432
 29433        Fixed64 c1 = FixedMath.Cos(yaw / Fixed64.Two);
 29434        Fixed64 s1 = FixedMath.Sin(yaw / Fixed64.Two);
 29435        Fixed64 c2 = FixedMath.Cos(roll / Fixed64.Two);
 29436        Fixed64 s2 = FixedMath.Sin(roll / Fixed64.Two);
 29437        Fixed64 c3 = FixedMath.Cos(pitch / Fixed64.Two);
 29438        Fixed64 s3 = FixedMath.Sin(pitch / Fixed64.Two);
 439
 29440        Fixed64 c1c2 = c1 * c2;
 29441        Fixed64 s1s2 = s1 * s2;
 442
 29443        Fixed64 w = c1c2 * c3 - s1s2 * s3;
 29444        Fixed64 x = c1c2 * s3 + s1s2 * c3;
 29445        Fixed64 y = s1 * c2 * c3 + c1 * s2 * s3;
 29446        Fixed64 z = c1 * s2 * c3 - s1 * c2 * s3;
 447
 29448        return GetNormalized(new FixedQuaternion(x, y, z, w));
 29449    }
 450
 451    /// <summary>
 452    /// Computes the logarithm of a quaternion, which represents the rotational displacement.
 453    /// This is useful for interpolation and angular velocity calculations.
 454    /// </summary>
 455    /// <param name="q">The quaternion to compute the logarithm of.</param>
 456    /// <returns>A Vector3d representing the logarithm of the quaternion (axis-angle representation).</returns>
 457    /// <remarks>
 458    /// The logarithm of a unit quaternion is given by:
 459    /// log(q) = (θ * v̂), where:
 460    /// - θ = 2 * acos(w) is the rotation angle.
 461    /// - v̂ = (x, y, z) / ||(x, y, z)|| is the unit vector representing the axis of rotation.
 462    /// If the quaternion is close to identity, the function returns a zero vector to avoid numerical instability.
 463    /// </remarks>
 464    public static Vector3d QuaternionLog(FixedQuaternion q)
 7465    {
 466        // Ensure the quaternion is normalized
 7467        q = q.Normal;
 468
 469        // Extract vector part
 7470        Vector3d v = new Vector3d(q.x, q.y, q.z);
 7471        Fixed64 vLength = v.Magnitude;
 472
 473        // If rotation is very small, avoid division by zero
 7474        if (vLength < Fixed64.FromRaw(0x00001000L)) // Small epsilon
 3475            return Vector3d.Zero;
 476
 477        // Compute angle (theta = 2 * acos(w))
 4478        Fixed64 theta = Fixed64.Two * FixedMath.Acos(q.w);
 479
 480        // Convert to angular velocity
 4481        return (v / vLength) * theta;
 7482    }
 483
 484    /// <summary>
 485    /// Computes the angular velocity required to move from `previousRotation` to `currentRotation` over a given time st
 486    /// </summary>
 487    /// <param name="currentRotation">The current orientation as a quaternion.</param>
 488    /// <param name="previousRotation">The previous orientation as a quaternion.</param>
 489    /// <param name="deltaTime">The time step over which the rotation occurs.</param>
 490    /// <returns>A Vector3d representing the angular velocity (in radians per second).</returns>
 491    /// <remarks>
 492    /// This function calculates the change in rotation over `deltaTime` and converts it into angular velocity.
 493    /// - First, it computes the relative rotation: `rotationDelta = currentRotation * previousRotation.Inverse()`.
 494    /// - Then, it applies `QuaternionLog(rotationDelta)` to extract the axis-angle representation.
 495    /// - Finally, it divides by `deltaTime` to compute the angular velocity.
 496    /// </remarks>
 497    public static Vector3d ToAngularVelocity(
 498        FixedQuaternion currentRotation,
 499        FixedQuaternion previousRotation,
 500        Fixed64 deltaTime)
 4501    {
 4502        FixedQuaternion rotationDelta = currentRotation * previousRotation.Inverse();
 4503        Vector3d angularDisplacement = QuaternionLog(rotationDelta);
 504
 4505        return angularDisplacement / deltaTime; // Convert to angular velocity
 4506    }
 507
 508    /// <summary>
 509    /// Performs a simple linear interpolation between the components of the input quaternions
 510    /// </summary>
 511    public static FixedQuaternion Lerp(FixedQuaternion a, FixedQuaternion b, Fixed64 t)
 2512    {
 2513        t = FixedMath.Clamp01(t);
 514
 515        FixedQuaternion result;
 2516        Fixed64 oneMinusT = Fixed64.One - t;
 2517        result.x = a.x * oneMinusT + b.x * t;
 2518        result.y = a.y * oneMinusT + b.y * t;
 2519        result.z = a.z * oneMinusT + b.z * t;
 2520        result.w = a.w * oneMinusT + b.w * t;
 521
 2522        result.Normalize();
 523
 2524        return result;
 2525    }
 526
 527    /// <summary>
 528    ///  Calculates the spherical linear interpolation, which results in a smoother and more accurate rotation interpola
 529    /// </summary>
 530    public static FixedQuaternion Slerp(FixedQuaternion a, FixedQuaternion b, Fixed64 t)
 2531    {
 2532        t = FixedMath.Clamp01(t);
 533
 2534        Fixed64 cosOmega = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
 535
 536        // If the dot product is negative, negate one of the input quaternions.
 537        // This ensures that the interpolation takes the shortest path around the sphere.
 2538        if (cosOmega < Fixed64.Zero)
 1539        {
 1540            b.x = -b.x;
 1541            b.y = -b.y;
 1542            b.z = -b.z;
 1543            b.w = -b.w;
 1544            cosOmega = -cosOmega;
 1545        }
 546
 547        Fixed64 k0, k1;
 548
 549        // If the quaternions are close, use linear interpolation
 2550        if (cosOmega > Fixed64.One - Fixed64.Precision)
 1551        {
 1552            k0 = Fixed64.One - t;
 1553            k1 = t;
 1554        }
 555        else
 1556        {
 557            // Otherwise, use spherical linear interpolation
 1558            Fixed64 sinOmega = FixedMath.Sqrt(Fixed64.One - cosOmega * cosOmega);
 1559            Fixed64 omega = FixedMath.Atan2(sinOmega, cosOmega);
 560
 1561            k0 = FixedMath.Sin((Fixed64.One - t) * omega) / sinOmega;
 1562            k1 = FixedMath.Sin(t * omega) / sinOmega;
 1563        }
 564
 565        FixedQuaternion result;
 2566        result.x = a.x * k0 + b.x * k1;
 2567        result.y = a.y * k0 + b.y * k1;
 2568        result.z = a.z * k0 + b.z * k1;
 2569        result.w = a.w * k0 + b.w * k1;
 570
 2571        return result;
 2572    }
 573
 574    /// <summary>
 575    /// Returns the angle in degrees between two rotations a and b.
 576    /// </summary>
 577    /// <param name="a">The first rotation.</param>
 578    /// <param name="b">The second rotation.</param>
 579    /// <returns>The angle in degrees between the two rotations.</returns>
 580    public static Fixed64 Angle(FixedQuaternion a, FixedQuaternion b)
 1581    {
 582        // Calculate the dot product of the two quaternions
 1583        Fixed64 dot = Dot(a, b);
 584
 585        // Ensure the dot product is in the range of [-1, 1] to avoid floating-point inaccuracies
 1586        dot = FixedMath.Clamp(dot, -Fixed64.One, Fixed64.One);
 587
 588        // Calculate the angle between the two quaternions using the inverse cosine (arccos)
 589        // arccos(dot(a, b)) gives us the angle in radians, so we convert it to degrees
 1590        Fixed64 angleInRadians = FixedMath.Acos(dot);
 591
 592        // Convert the angle from radians to degrees
 1593        Fixed64 angleInDegrees = FixedMath.RadToDeg(angleInRadians);
 594
 1595        return angleInDegrees;
 1596    }
 597
 598    /// <summary>
 599    /// Creates a quaternion from an angle and axis.
 600    /// </summary>
 601    /// <param name="angle">The angle in degrees.</param>
 602    /// <param name="axis">The axis to rotate around (must be normalized).</param>
 603    /// <returns>A quaternion representing the rotation.</returns>
 604    public static FixedQuaternion AngleAxis(Fixed64 angle, Vector3d axis)
 1605    {
 606        // Convert the angle to radians
 1607        angle = angle.ToRadians();
 608
 609        // Normalize the axis
 1610        axis = axis.Normal;
 611
 612        // Use the half-angle formula (sin(theta / 2), cos(theta / 2))
 1613        Fixed64 halfAngle = angle / Fixed64.Two;
 1614        Fixed64 sinHalfAngle = FixedMath.Sin(halfAngle);
 1615        Fixed64 cosHalfAngle = FixedMath.Cos(halfAngle);
 616
 1617        return new FixedQuaternion(
 1618            axis.x * sinHalfAngle,
 1619            axis.y * sinHalfAngle,
 1620            axis.z * sinHalfAngle,
 1621            cosHalfAngle
 1622        );
 1623    }
 624
 625    /// <summary>
 626    /// Calculates the dot product of two quaternions.
 627    /// </summary>
 628    /// <param name="a">The first quaternion.</param>
 629    /// <param name="b">The second quaternion.</param>
 630    /// <returns>The dot product of the two quaternions.</returns>
 631    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 632    public static Fixed64 Dot(FixedQuaternion a, FixedQuaternion b)
 6633    {
 6634        return a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z;
 6635    }
 636
 637    #endregion
 638
 639    #region Operators
 640
 641    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 642    public static FixedQuaternion operator *(FixedQuaternion a, FixedQuaternion b)
 12643    {
 12644        return new FixedQuaternion(
 12645            a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
 12646            a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
 12647            a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
 12648            a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z
 12649        );
 12650    }
 651
 652    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 653    public static FixedQuaternion operator *(FixedQuaternion q, Fixed64 scalar)
 4654    {
 4655        return new FixedQuaternion(q.x * scalar, q.y * scalar, q.z * scalar, q.w * scalar);
 4656    }
 657
 658    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 659    public static FixedQuaternion operator *(Fixed64 scalar, FixedQuaternion q)
 1660    {
 1661        return new FixedQuaternion(q.x * scalar, q.y * scalar, q.z * scalar, q.w * scalar);
 1662    }
 663
 664    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 665    public static FixedQuaternion operator /(FixedQuaternion q, Fixed64 scalar)
 1666    {
 1667        return new FixedQuaternion(q.x / scalar, q.y / scalar, q.z / scalar, q.w / scalar);
 1668    }
 669
 670    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 671    public static FixedQuaternion operator /(Fixed64 scalar, FixedQuaternion q)
 1672    {
 1673        return new FixedQuaternion(q.x / scalar, q.y / scalar, q.z / scalar, q.w / scalar);
 1674    }
 675
 676    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 677    public static FixedQuaternion operator +(FixedQuaternion q1, FixedQuaternion q2)
 1678    {
 1679        return new FixedQuaternion(q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w);
 1680    }
 681
 682    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 683    public static bool operator ==(FixedQuaternion left, FixedQuaternion right)
 10684    {
 10685        return left.Equals(right);
 10686    }
 687
 688    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 689    public static bool operator !=(FixedQuaternion left, FixedQuaternion right)
 1690    {
 1691        return !left.Equals(right);
 1692    }
 693
 694    #endregion
 695
 696    #region Conversion
 697
 698    /// <summary>
 699    /// Converts this FixedQuaternion to Euler angles (pitch, yaw, roll).
 700    /// </summary>
 701    /// <remarks>
 702    /// Handles the case where the pitch angle (asin of sinp) would be out of the range -π/2 to π/2.
 703    /// This is known as the gimbal lock situation, where the pitch angle reaches ±90 degrees and we lose one degree of 
 704    /// In this case, we simply set the pitch to ±90 degrees depending on the sign of sinp.
 705    /// </remarks>
 706    /// <returns>A Vector3d representing the Euler angles (in degrees) equivalent to this FixedQuaternion in YZX order (
 707    public Vector3d ToEulerAngles()
 3708    {
 709        // roll (x-axis rotation)
 3710        Fixed64 sinr_cosp = 2 * (w * x + y * z);
 3711        Fixed64 cosr_cosp = Fixed64.One - 2 * (x * x + y * y);
 3712        Fixed64 roll = FixedMath.Atan2(sinr_cosp, cosr_cosp);
 713
 714        // pitch (y-axis rotation)
 3715        Fixed64 sinp = 2 * (w * y - z * x);
 716        Fixed64 pitch;
 3717        if (sinp.Abs() >= Fixed64.One)
 1718            pitch = FixedMath.CopySign(FixedMath.PiOver2, sinp); // use 90 degrees if out of range
 719        else
 2720            pitch = FixedMath.Asin(sinp);
 721
 722        // yaw (z-axis rotation)
 3723        Fixed64 siny_cosp = 2 * (w * z + x * y);
 3724        Fixed64 cosy_cosp = Fixed64.One - 2 * (y * y + z * z);
 3725        Fixed64 yaw = FixedMath.Atan2(siny_cosp, cosy_cosp);
 726
 727        // Convert radians to degrees
 3728        roll = FixedMath.RadToDeg(roll);
 3729        pitch = FixedMath.RadToDeg(pitch);
 3730        yaw = FixedMath.RadToDeg(yaw);
 731
 3732        return new Vector3d(roll, pitch, yaw);
 3733    }
 734
 735    /// <summary>
 736    /// Converts this FixedQuaternion to a direction vector.
 737    /// </summary>
 738    /// <returns>A Vector3d representing the direction equivalent to this FixedQuaternion.</returns>
 739    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 740    public Vector3d ToDirection()
 1741    {
 1742        return new Vector3d(
 1743            2 * (x * z - w * y),
 1744            2 * (y * z + w * x),
 1745            Fixed64.One - 2 * (x * x + y * y)
 1746        );
 1747    }
 748
 749    /// <summary>
 750    /// Converts the quaternion into a 3x3 rotation matrix.
 751    /// </summary>
 752    /// <returns>A FixedMatrix3x3 representing the same rotation as the quaternion.</returns>
 753    public Fixed3x3 ToMatrix3x3()
 27754    {
 27755        Fixed64 x2 = x * x;
 27756        Fixed64 y2 = y * y;
 27757        Fixed64 z2 = z * z;
 27758        Fixed64 xy = x * y;
 27759        Fixed64 xz = x * z;
 27760        Fixed64 yz = y * z;
 27761        Fixed64 xw = x * w;
 27762        Fixed64 yw = y * w;
 27763        Fixed64 zw = z * w;
 764
 27765        Fixed3x3 result = new Fixed3x3();
 27766        Fixed64 scale = Fixed64.One * 2;
 767
 27768        result.m00 = Fixed64.One - scale * (y2 + z2);
 27769        result.m01 = scale * (xy - zw);
 27770        result.m02 = scale * (xz + yw);
 771
 27772        result.m10 = scale * (xy + zw);
 27773        result.m11 = Fixed64.One - scale * (x2 + z2);
 27774        result.m12 = scale * (yz - xw);
 775
 27776        result.m20 = scale * (xz - yw);
 27777        result.m21 = scale * (yz + xw);
 27778        result.m22 = Fixed64.One - scale * (x2 + y2);
 779
 27780        return result;
 27781    }
 782
 783    #endregion
 784
 785    #region Equality and HashCode Overrides
 786
 787    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 788    public override bool Equals(object? obj)
 7789    {
 7790        return obj is FixedQuaternion other && Equals(other);
 7791    }
 792
 793    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 794    public bool Equals(FixedQuaternion other)
 31795    {
 31796        return other.x == x && other.y == y && other.z == z && other.w == w;
 31797    }
 798
 799    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 800    public override int GetHashCode()
 2801    {
 2802        return x.GetHashCode() ^ y.GetHashCode() << 2 ^ z.GetHashCode() >> 2 ^ w.GetHashCode();
 2803    }
 804
 805    /// <summary>
 806    /// Returns a formatted string for this quaternion.
 807    /// </summary>
 808    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 809    public override string ToString()
 78810    {
 78811        return $"({x}, {y}, {z}, {w})";
 78812    }
 813
 814    #endregion
 815}

Methods/Properties

.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
get_Normal()
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)
Normalize()
Conjugate()
Inverse()
Rotate(FixedMathSharp.Vector3d)
Rotated(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Nullable`1<FixedMathSharp.Vector3d>)
IsNormalized()
GetMagnitude(FixedMathSharp.FixedQuaternion)
GetNormalized(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)
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_Division(FixedMathSharp.Fixed64,FixedMathSharp.FixedQuaternion)
op_Addition(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion)
op_Equality(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion)
op_Inequality(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion)
ToEulerAngles()
ToDirection()
ToMatrix3x3()
Equals(System.Object)
Equals(FixedMathSharp.FixedQuaternion)
GetHashCode()
ToString()