| | | 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 | | |
| | | 8 | | using System; |
| | | 9 | | using System.Runtime.CompilerServices; |
| | | 10 | | |
| | | 11 | | namespace FixedMathSharp; |
| | | 12 | | |
| | | 13 | | /// <content> |
| | | 14 | | /// Static helper methods and operations for <see cref="FixedQuaternion"/>, including |
| | | 15 | | /// normalization, magnitude calculations, and related utility functions. |
| | | 16 | | /// </content> |
| | | 17 | | public partial struct FixedQuaternion |
| | | 18 | | { |
| | | 19 | | #region Quaternion Operations |
| | | 20 | | |
| | | 21 | | /// <summary> |
| | | 22 | | /// Checks whether this nonzero quaternion's squared magnitude is within |
| | | 23 | | /// epsilon of one. |
| | | 24 | | /// </summary> |
| | | 25 | | public bool IsNormalized() |
| | | 26 | | { |
| | 31840 | 27 | | Fixed64 squaredMagnitude = GetNormalizationSquaredMagnitude(this); |
| | 31840 | 28 | | return squaredMagnitude != Fixed64.Zero |
| | 31840 | 29 | | && FixedMath.Abs(squaredMagnitude - Fixed64.One) <= Fixed64.Epsilon; |
| | | 30 | | } |
| | | 31 | | |
| | | 32 | | /// <summary> |
| | | 33 | | /// Calculates the magnitude (or length) of the specified quaternion. |
| | | 34 | | /// </summary> |
| | | 35 | | /// <remarks> |
| | | 36 | | /// Component squares are accumulated exactly and the integer square root is |
| | | 37 | | /// rounded once. Only an unrepresentable rounded result saturates. |
| | | 38 | | /// </remarks> |
| | | 39 | | /// <param name="q">The quaternion for which to compute the magnitude.</param> |
| | | 40 | | /// <returns>The magnitude of the quaternion as a Fixed64 value. Returns 0 if the quaternion is the zero quaternion. |
| | | 41 | | public static Fixed64 GetMagnitude(FixedQuaternion q) |
| | 20028 | 42 | | => Fixed64.GetRoundedMagnitude(q.X, q.Y, q.Z, q.W); |
| | | 43 | | |
| | | 44 | | private static Fixed64 GetNormalizationSquaredMagnitude(FixedQuaternion q) => |
| | 47150 | 45 | | (q.X * q.X) + (q.Y * q.Y) + (q.Z * q.Z) + (q.W * q.W); |
| | | 46 | | |
| | | 47 | | private static Fixed64 GetNormalizationMagnitude( |
| | | 48 | | FixedQuaternion q, |
| | | 49 | | out bool isNormalized) |
| | | 50 | | { |
| | 15310 | 51 | | Fixed64 mag = GetNormalizationSquaredMagnitude(q); |
| | 15310 | 52 | | isNormalized = mag != Fixed64.Zero |
| | 15310 | 53 | | && FixedMath.Abs(mag - Fixed64.One) <= Fixed64.Epsilon; |
| | | 54 | | |
| | 15310 | 55 | | if (mag == Fixed64.MaxValue || mag <= FixedMath.ScaleSafeMagnitudeSquaredThreshold) |
| | 13 | 56 | | return FixedMath.GetScaledMagnitude(q.X, q.Y, q.Z, q.W); |
| | | 57 | | |
| | 15297 | 58 | | if (isNormalized) |
| | 15226 | 59 | | return Fixed64.One; |
| | | 60 | | |
| | 71 | 61 | | return FixedMath.Sqrt(mag); |
| | | 62 | | } |
| | | 63 | | |
| | | 64 | | /// <summary> |
| | | 65 | | /// Normalizes the quaternion to a unit quaternion. |
| | | 66 | | /// </summary> |
| | | 67 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 68 | | public static FixedQuaternion GetNormalized(FixedQuaternion q) |
| | | 69 | | { |
| | 15310 | 70 | | Fixed64 mag = GetNormalizationMagnitude(q, out bool isNormalized); |
| | | 71 | | |
| | | 72 | | // If magnitude is zero, return identity quaternion (to avoid divide by zero) |
| | 15310 | 73 | | if (mag == Fixed64.Zero) |
| | 4 | 74 | | return Identity; |
| | | 75 | | |
| | 15306 | 76 | | if (isNormalized) |
| | 15226 | 77 | | return q; |
| | | 78 | | |
| | 80 | 79 | | if (mag == Fixed64.MaxValue || mag == Fixed64.One) |
| | 6 | 80 | | return WideNormalization.GetNormalized(q); |
| | | 81 | | |
| | 74 | 82 | | if (mag <= FixedMath.ScaleSafeMagnitudeThreshold) |
| | 2 | 83 | | return GetScaleNormalized(q); |
| | | 84 | | |
| | 72 | 85 | | var normalized = new FixedQuaternion( |
| | 72 | 86 | | q.X / mag, |
| | 72 | 87 | | q.Y / mag, |
| | 72 | 88 | | q.Z / mag, |
| | 72 | 89 | | q.W / mag); |
| | 72 | 90 | | return normalized.IsNormalized() |
| | 72 | 91 | | ? normalized |
| | 72 | 92 | | : WideNormalization.GetNormalized(q); |
| | | 93 | | } |
| | | 94 | | |
| | | 95 | | private static FixedQuaternion GetScaleNormalized(FixedQuaternion q) |
| | | 96 | | { |
| | 2 | 97 | | Fixed64 scale = FixedMath.Max( |
| | 2 | 98 | | FixedMath.Max(q.X.Abs(), q.Y.Abs()), |
| | 2 | 99 | | FixedMath.Max(q.Z.Abs(), q.W.Abs())); |
| | 2 | 100 | | FixedQuaternion scaled = q / scale; |
| | 2 | 101 | | Fixed64 scaledMagnitude = FixedMath.GetScaledMagnitude( |
| | 2 | 102 | | scaled.X, |
| | 2 | 103 | | scaled.Y, |
| | 2 | 104 | | scaled.Z, |
| | 2 | 105 | | scaled.W); |
| | 2 | 106 | | return scaled / scaledMagnitude; |
| | | 107 | | } |
| | | 108 | | |
| | | 109 | | /// <summary> |
| | | 110 | | /// Divides one quaternion by another using inverse quaternion multiplication. |
| | | 111 | | /// </summary> |
| | | 112 | | /// <remarks> |
| | | 113 | | /// This is equivalent to <c>dividend * Inverse(divisor)</c>. |
| | | 114 | | /// </remarks> |
| | | 115 | | /// <exception cref="InvalidOperationException"> |
| | | 116 | | /// Thrown when <paramref name="divisor"/> is not invertible. |
| | | 117 | | /// </exception> |
| | | 118 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 119 | | public static FixedQuaternion Divide(FixedQuaternion dividend, FixedQuaternion divisor) |
| | | 120 | | { |
| | 2 | 121 | | Fixed64 divisorMagnitudeSquared = divisor.MagnitudeSquared; |
| | | 122 | | |
| | 2 | 123 | | if (divisorMagnitudeSquared == Fixed64.Zero) |
| | 1 | 124 | | throw new InvalidOperationException("Quaternion divisor is not invertible."); |
| | | 125 | | |
| | 1 | 126 | | Fixed64 invNorm = Fixed64.One / divisorMagnitudeSquared; |
| | 1 | 127 | | FixedQuaternion inverseDivisor = new( |
| | 1 | 128 | | -divisor.X * invNorm, |
| | 1 | 129 | | -divisor.Y * invNorm, |
| | 1 | 130 | | -divisor.Z * invNorm, |
| | 1 | 131 | | divisor.W * invNorm); |
| | | 132 | | |
| | 1 | 133 | | return dividend * inverseDivisor; |
| | | 134 | | } |
| | | 135 | | |
| | | 136 | | /// <summary> |
| | | 137 | | /// Creates a quaternion whose canonical forward direction aligns with the specified direction. |
| | | 138 | | /// </summary> |
| | | 139 | | /// <remarks> |
| | | 140 | | /// The <paramref name="forward"/> and <paramref name="upwards"/> vectors are expressed in |
| | | 141 | | /// FixedMathSharp's canonical basis: <c>+X</c> right, <c>+Y</c> up, and <c>+Z</c> forward. |
| | | 142 | | /// Use <see cref="CoordinateConvention3d"/> or adapter-specific basis conversion before |
| | | 143 | | /// calling this method when external APIs use different semantic axes. |
| | | 144 | | /// </remarks> |
| | | 145 | | /// <param name="forward">The forward direction vector.</param> |
| | | 146 | | /// <param name="upwards">The upwards direction vector (optional, default: Vector3d.Up).</param> |
| | | 147 | | /// <returns>A quaternion representing the rotation from one direction to another.</returns> |
| | | 148 | | public static FixedQuaternion LookRotation(Vector3d forward, Vector3d? upwards = null) |
| | | 149 | | { |
| | 3 | 150 | | Vector3d up = upwards ?? Vector3d.Up; |
| | | 151 | | |
| | 3 | 152 | | Vector3d forwardNormalized = forward.Normalized; |
| | 3 | 153 | | Vector3d right = Vector3d.Cross(up.Normalized, forwardNormalized); |
| | 3 | 154 | | up = Vector3d.Cross(forwardNormalized, right); |
| | | 155 | | |
| | 3 | 156 | | return FromMatrix(new Fixed3x3( |
| | 3 | 157 | | right.X, right.Y, right.Z, |
| | 3 | 158 | | up.X, up.Y, up.Z, |
| | 3 | 159 | | forwardNormalized.X, forwardNormalized.Y, forwardNormalized.Z)); |
| | | 160 | | } |
| | | 161 | | |
| | | 162 | | /// <summary> |
| | | 163 | | /// Converts a rotation matrix into a quaternion representation. |
| | | 164 | | /// </summary> |
| | | 165 | | /// <param name="matrix">The rotation matrix to convert.</param> |
| | | 166 | | /// <returns>A quaternion representing the same rotation as the matrix.</returns> |
| | | 167 | | public static FixedQuaternion FromMatrix(Fixed3x3 matrix) |
| | | 168 | | { |
| | 50 | 169 | | Fixed64 trace = matrix.M11 + matrix.M22 + matrix.M33; |
| | | 170 | | |
| | | 171 | | Fixed64 w, x, y, z; |
| | | 172 | | |
| | 50 | 173 | | if (trace > Fixed64.Zero) |
| | | 174 | | { |
| | 42 | 175 | | Fixed64 s = FixedMath.Sqrt(trace + Fixed64.One); |
| | 42 | 176 | | w = s * Fixed64.Half; |
| | 42 | 177 | | s = Fixed64.Half / s; |
| | 42 | 178 | | x = (matrix.M23 - matrix.M32) * s; |
| | 42 | 179 | | y = (matrix.M31 - matrix.M13) * s; |
| | 42 | 180 | | z = (matrix.M12 - matrix.M21) * s; |
| | | 181 | | } |
| | 8 | 182 | | else if (matrix.M11 > matrix.M22 && matrix.M11 > matrix.M33) |
| | | 183 | | { |
| | 2 | 184 | | Fixed64 s = FixedMath.Sqrt(Fixed64.One + matrix.M11 - matrix.M22 - matrix.M33); |
| | 2 | 185 | | x = s * Fixed64.Half; |
| | 2 | 186 | | s = Fixed64.Half / s; |
| | 2 | 187 | | y = (matrix.M21 + matrix.M12) * s; |
| | 2 | 188 | | z = (matrix.M13 + matrix.M31) * s; |
| | 2 | 189 | | w = (matrix.M23 - matrix.M32) * s; |
| | | 190 | | } |
| | 6 | 191 | | else if (matrix.M22 > matrix.M33) |
| | | 192 | | { |
| | 2 | 193 | | Fixed64 s = FixedMath.Sqrt(Fixed64.One + matrix.M22 - matrix.M11 - matrix.M33); |
| | 2 | 194 | | y = s * Fixed64.Half; |
| | 2 | 195 | | s = Fixed64.Half / s; |
| | 2 | 196 | | z = (matrix.M32 + matrix.M23) * s; |
| | 2 | 197 | | x = (matrix.M21 + matrix.M12) * s; |
| | 2 | 198 | | w = (matrix.M31 - matrix.M13) * s; |
| | | 199 | | } |
| | | 200 | | else |
| | | 201 | | { |
| | 4 | 202 | | Fixed64 s = FixedMath.Sqrt(Fixed64.One + matrix.M33 - matrix.M11 - matrix.M22); |
| | 4 | 203 | | z = s * Fixed64.Half; |
| | 4 | 204 | | s = Fixed64.Half / s; |
| | 4 | 205 | | x = (matrix.M13 + matrix.M31) * s; |
| | 4 | 206 | | y = (matrix.M32 + matrix.M23) * s; |
| | 4 | 207 | | w = (matrix.M12 - matrix.M21) * s; |
| | | 208 | | } |
| | | 209 | | |
| | 50 | 210 | | return new FixedQuaternion(x, y, z, w); |
| | | 211 | | } |
| | | 212 | | |
| | | 213 | | /// <summary> |
| | | 214 | | /// Converts a rotation matrix (upper-left 3x3 part of a 4x4 matrix) into a quaternion representation. |
| | | 215 | | /// </summary> |
| | | 216 | | /// <param name="matrix">The 4x4 matrix containing the rotation component.</param> |
| | | 217 | | /// <remarks>Extracts the upper-left 3x3 rotation part of the 4x4</remarks> |
| | | 218 | | /// <returns>A quaternion representing the same rotation as the matrix.</returns> |
| | | 219 | | public static FixedQuaternion FromMatrix(Fixed4x4 matrix) |
| | | 220 | | { |
| | 12 | 221 | | Fixed3x3 rotationMatrix = new( |
| | 12 | 222 | | matrix.M11, matrix.M12, matrix.M13, |
| | 12 | 223 | | matrix.M21, matrix.M22, matrix.M23, |
| | 12 | 224 | | matrix.M31, matrix.M32, matrix.M33 |
| | 12 | 225 | | ); |
| | | 226 | | |
| | 12 | 227 | | return FromMatrix(rotationMatrix); |
| | | 228 | | } |
| | | 229 | | |
| | | 230 | | /// <summary> |
| | | 231 | | /// Creates a quaternion representing the rotation needed to align canonical <c>+Z</c> forward with the given direct |
| | | 232 | | /// </summary> |
| | | 233 | | /// <remarks> |
| | | 234 | | /// <see cref="Vector3d.Forward"/> returns <see cref="FixedQuaternion.Identity"/>. If an external API names |
| | | 235 | | /// <c>-Z</c> as forward, convert that direction into the canonical convention before calling |
| | | 236 | | /// this method. |
| | | 237 | | /// </remarks> |
| | | 238 | | /// <param name="direction">The target direction vector.</param> |
| | | 239 | | /// <returns>A quaternion representing the rotation to align with the direction.</returns> |
| | | 240 | | public static FixedQuaternion FromDirection(Vector3d direction) |
| | | 241 | | { |
| | 12 | 242 | | if (direction == Vector3d.Zero) |
| | 1 | 243 | | return Identity; |
| | | 244 | | |
| | 11 | 245 | | if (!direction.IsNormalized()) |
| | 2 | 246 | | direction = direction.Normalized; |
| | | 247 | | |
| | 11 | 248 | | Fixed64 dot = direction.Z; |
| | 11 | 249 | | if (dot <= -Fixed64.One + Fixed64.Epsilon) |
| | 1 | 250 | | return FromAxisAngle(Vector3d.Up, Fixed64.Pi); |
| | | 251 | | |
| | 10 | 252 | | if (dot >= Fixed64.One - Fixed64.Epsilon) |
| | 2 | 253 | | return Identity; |
| | | 254 | | |
| | 8 | 255 | | if (dot < Fixed64.FromRaw(NearOppositeDirectionDotRaw)) |
| | | 256 | | { |
| | 1 | 257 | | Vector3d axis = new Vector3d(-direction.Y, direction.X, Fixed64.Zero).Normalized; |
| | | 258 | | |
| | 1 | 259 | | return FromAxisAngle(axis, FixedMath.Acos(dot)); |
| | | 260 | | } |
| | | 261 | | |
| | 7 | 262 | | Fixed64 scale = FixedMath.Sqrt((Fixed64.One + dot) * Fixed64.Two); |
| | 7 | 263 | | return new FixedQuaternion( |
| | 7 | 264 | | -direction.Y / scale, |
| | 7 | 265 | | direction.X / scale, |
| | 7 | 266 | | Fixed64.Zero, |
| | 7 | 267 | | scale * Fixed64.Half); |
| | | 268 | | } |
| | | 269 | | |
| | | 270 | | /// <summary> |
| | | 271 | | /// Creates a quaternion representing a rotation around a specified axis by a given angle. |
| | | 272 | | /// </summary> |
| | | 273 | | /// <param name="axis">The axis to rotate around. Nonzero inputs are normalized; zero returns identity.</param> |
| | | 274 | | /// <param name="angle">The rotation angle in radians.</param> |
| | | 275 | | /// <returns>A quaternion representing the rotation.</returns> |
| | | 276 | | public static FixedQuaternion FromAxisAngle(Vector3d axis, Fixed64 angle) |
| | | 277 | | { |
| | 697 | 278 | | if (axis == Vector3d.Zero) |
| | 2 | 279 | | return Identity; |
| | | 280 | | |
| | 695 | 281 | | if (!axis.IsNormalized()) |
| | 18 | 282 | | axis = axis.Normalized; |
| | | 283 | | |
| | 695 | 284 | | Fixed64 halfAngle = angle / Fixed64.Two; // Half-angle formula |
| | 695 | 285 | | Fixed64 sinHalfAngle = FixedMath.Sin(halfAngle); |
| | 695 | 286 | | Fixed64 cosHalfAngle = FixedMath.Cos(halfAngle); |
| | | 287 | | |
| | 695 | 288 | | return new FixedQuaternion( |
| | 695 | 289 | | axis.X * sinHalfAngle, |
| | 695 | 290 | | axis.Y * sinHalfAngle, |
| | 695 | 291 | | axis.Z * sinHalfAngle, |
| | 695 | 292 | | cosHalfAngle); |
| | | 293 | | } |
| | | 294 | | |
| | | 295 | | /// <summary> |
| | | 296 | | /// Assume the input angles are in degrees and converts them to radians before calling <see cref="FromEulerAngles"/> |
| | | 297 | | /// </summary> |
| | | 298 | | /// <param name="pitch"></param> |
| | | 299 | | /// <param name="yaw"></param> |
| | | 300 | | /// <param name="roll"></param> |
| | | 301 | | /// <returns></returns> |
| | | 302 | | public static FixedQuaternion FromEulerAnglesInDegrees(Fixed64 pitch, Fixed64 yaw, Fixed64 roll) |
| | | 303 | | { |
| | | 304 | | // Convert input angles from degrees to radians |
| | 129 | 305 | | pitch = FixedMath.DegToRad(pitch); |
| | 129 | 306 | | yaw = FixedMath.DegToRad(yaw); |
| | 129 | 307 | | roll = FixedMath.DegToRad(roll); |
| | | 308 | | |
| | | 309 | | // Call the original method that expects angles in radians |
| | 129 | 310 | | return FromEulerAngles(pitch, yaw, roll); |
| | | 311 | | } |
| | | 312 | | |
| | | 313 | | /// <summary> |
| | | 314 | | /// Converts Euler angles (pitch, yaw, roll) to a quaternion and normalizes the result afterwards. |
| | | 315 | | /// Assumes the input angles are in radians. |
| | | 316 | | /// </summary> |
| | | 317 | | /// <remarks> |
| | | 318 | | /// The order of operations is YXZ or yaw-pitch-roll |
| | | 319 | | /// </remarks> |
| | | 320 | | public static FixedQuaternion FromEulerAngles(Fixed64 pitch, Fixed64 yaw, Fixed64 roll) |
| | | 321 | | { |
| | 149 | 322 | | Fixed64 halfPitch = pitch / Fixed64.Two; |
| | 149 | 323 | | Fixed64 halfYaw = yaw / Fixed64.Two; |
| | 149 | 324 | | Fixed64 halfRoll = roll / Fixed64.Two; |
| | | 325 | | |
| | 149 | 326 | | Fixed64 sx = FixedMath.Sin(halfPitch); |
| | 149 | 327 | | Fixed64 cx = FixedMath.Cos(halfPitch); |
| | 149 | 328 | | Fixed64 sy = FixedMath.Sin(halfYaw); |
| | 149 | 329 | | Fixed64 cy = FixedMath.Cos(halfYaw); |
| | 149 | 330 | | Fixed64 sz = FixedMath.Sin(halfRoll); |
| | 149 | 331 | | Fixed64 cz = FixedMath.Cos(halfRoll); |
| | | 332 | | |
| | | 333 | | // q = qy * qx * qz |
| | 149 | 334 | | Fixed64 x = (cx * sy * sz) + (cy * cz * sx); |
| | 149 | 335 | | Fixed64 y = (cx * cz * sy) - (cy * sx * sz); |
| | 149 | 336 | | Fixed64 z = (cx * cy * sz) - (cz * sx * sy); |
| | 149 | 337 | | Fixed64 w = (cx * cy * cz) + (sx * sy * sz); |
| | | 338 | | |
| | 149 | 339 | | return GetNormalized(new FixedQuaternion(x, y, z, w)); |
| | | 340 | | } |
| | | 341 | | |
| | | 342 | | /// <summary> |
| | | 343 | | /// Computes the logarithm of a quaternion, which represents the rotational displacement. |
| | | 344 | | /// This is useful for interpolation and angular velocity calculations. |
| | | 345 | | /// </summary> |
| | | 346 | | /// <param name="q">The quaternion to compute the logarithm of.</param> |
| | | 347 | | /// <returns>A Vector3d representing the logarithm of the quaternion (axis-angle representation).</returns> |
| | | 348 | | /// <remarks> |
| | | 349 | | /// The logarithm of a unit quaternion is given by: |
| | | 350 | | /// log(q) = (θ * v̀‚), where: |
| | | 351 | | /// - θ = 2 * acos(w) is the rotation angle. |
| | | 352 | | /// - v̀‚ = (x, y, z) / ||(x, y, z)|| is the unit vector representing the axis of rotation. |
| | | 353 | | /// If the quaternion is close to identity, the function returns a zero vector to avoid numerical instability. |
| | | 354 | | /// </remarks> |
| | | 355 | | public static Vector3d QuaternionLog(FixedQuaternion q) |
| | | 356 | | { |
| | | 357 | | // Ensure the quaternion is normalized |
| | 23 | 358 | | q = GetNormalized(q); |
| | | 359 | | |
| | | 360 | | // Extract vector part |
| | 23 | 361 | | Vector3d v = new(q.X, q.Y, q.Z); |
| | 23 | 362 | | Fixed64 vLength = v.Magnitude; |
| | | 363 | | |
| | | 364 | | // If rotation is very small, avoid division by zero |
| | 23 | 365 | | if (vLength < Fixed64.FromRaw(QuaternionLogVectorThresholdRaw)) |
| | 5 | 366 | | return Vector3d.Zero; |
| | | 367 | | |
| | | 368 | | // Compute angle (theta = 2 * acos(w)) |
| | 18 | 369 | | Fixed64 normalizedW = FixedMath.Clamp(q.W, -Fixed64.One, Fixed64.One); |
| | 18 | 370 | | Fixed64 theta = Fixed64.Two * FixedMath.Acos(normalizedW); |
| | | 371 | | |
| | | 372 | | // Convert to angular velocity |
| | 18 | 373 | | return (v / vLength) * theta; |
| | | 374 | | } |
| | | 375 | | |
| | | 376 | | /// <summary> |
| | | 377 | | /// Computes the angular velocity required to move from `previousRotation` to `currentRotation` over a given time st |
| | | 378 | | /// </summary> |
| | | 379 | | /// <param name="currentRotation">The current orientation as a quaternion.</param> |
| | | 380 | | /// <param name="previousRotation">The previous orientation as a quaternion.</param> |
| | | 381 | | /// <param name="deltaTime">The time step over which the rotation occurs.</param> |
| | | 382 | | /// <returns>A Vector3d representing the angular velocity (in radians per second).</returns> |
| | | 383 | | /// <remarks> |
| | | 384 | | /// This function calculates the change in rotation over `deltaTime` and converts it into angular velocity. |
| | | 385 | | /// - First, it computes the relative rotation: `rotationDelta = currentRotation * previousRotation.Inverse()`. |
| | | 386 | | /// - Then, it applies `QuaternionLog(rotationDelta)` to extract the axis-angle representation. |
| | | 387 | | /// - Finally, it divides by `deltaTime` to compute the angular velocity. |
| | | 388 | | /// </remarks> |
| | | 389 | | public static Vector3d ToAngularVelocity( |
| | | 390 | | FixedQuaternion currentRotation, |
| | | 391 | | FixedQuaternion previousRotation, |
| | | 392 | | Fixed64 deltaTime) |
| | | 393 | | { |
| | 4 | 394 | | FixedQuaternion rotationDelta = currentRotation * previousRotation.Inverse(); |
| | 4 | 395 | | Vector3d angularDisplacement = QuaternionLog(rotationDelta); |
| | | 396 | | |
| | 4 | 397 | | return angularDisplacement / deltaTime; // Convert to angular velocity |
| | | 398 | | } |
| | | 399 | | |
| | | 400 | | /// <summary> |
| | | 401 | | /// Performs a simple linear interpolation between the components of the input quaternions |
| | | 402 | | /// </summary> |
| | | 403 | | public static FixedQuaternion Lerp(FixedQuaternion a, FixedQuaternion b, Fixed64 t) |
| | | 404 | | { |
| | 5 | 405 | | t = FixedMath.Clamp01(t); |
| | | 406 | | |
| | 5 | 407 | | if (Dot(a, b) < Fixed64.Zero) |
| | 1 | 408 | | b = -b; |
| | | 409 | | |
| | | 410 | | FixedQuaternion result; |
| | 5 | 411 | | Fixed64 oneMinusT = Fixed64.One - t; |
| | 5 | 412 | | result.X = a.X * oneMinusT + b.X * t; |
| | 5 | 413 | | result.Y = a.Y * oneMinusT + b.Y * t; |
| | 5 | 414 | | result.Z = a.Z * oneMinusT + b.Z * t; |
| | 5 | 415 | | result.W = a.W * oneMinusT + b.W * t; |
| | | 416 | | |
| | 5 | 417 | | result.NormalizeInPlace(); |
| | | 418 | | |
| | 5 | 419 | | return result; |
| | | 420 | | } |
| | | 421 | | |
| | | 422 | | /// <summary> |
| | | 423 | | /// Calculates the spherical linear interpolation, which results in a smoother and more accurate rotation interpola |
| | | 424 | | /// </summary> |
| | | 425 | | public static FixedQuaternion Slerp(FixedQuaternion a, FixedQuaternion b, Fixed64 t) |
| | | 426 | | { |
| | 4 | 427 | | t = FixedMath.Clamp01(t); |
| | | 428 | | |
| | 4 | 429 | | Fixed64 cosOmega = a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W; |
| | | 430 | | |
| | | 431 | | // If the dot product is negative, negate one of the input quaternions. |
| | | 432 | | // This ensures that the interpolation takes the shortest path around the sphere. |
| | 4 | 433 | | if (cosOmega < Fixed64.Zero) |
| | | 434 | | { |
| | 1 | 435 | | b.X = -b.X; |
| | 1 | 436 | | b.Y = -b.Y; |
| | 1 | 437 | | b.Z = -b.Z; |
| | 1 | 438 | | b.W = -b.W; |
| | 1 | 439 | | cosOmega = -cosOmega; |
| | | 440 | | } |
| | | 441 | | |
| | | 442 | | Fixed64 k0, k1; |
| | | 443 | | |
| | | 444 | | // If the quaternions are close, use linear interpolation |
| | 4 | 445 | | if (cosOmega > Fixed64.One - Fixed64.Epsilon) |
| | | 446 | | { |
| | 1 | 447 | | k0 = Fixed64.One - t; |
| | 1 | 448 | | k1 = t; |
| | | 449 | | } |
| | | 450 | | else |
| | | 451 | | { |
| | | 452 | | // Otherwise, use spherical linear interpolation |
| | 3 | 453 | | Fixed64 sinOmega = FixedMath.Sqrt(Fixed64.One - cosOmega * cosOmega); |
| | 3 | 454 | | Fixed64 omega = FixedMath.Atan2(sinOmega, cosOmega); |
| | | 455 | | |
| | 3 | 456 | | k0 = FixedMath.Sin((Fixed64.One - t) * omega) / sinOmega; |
| | 3 | 457 | | k1 = FixedMath.Sin(t * omega) / sinOmega; |
| | | 458 | | } |
| | | 459 | | |
| | | 460 | | FixedQuaternion result; |
| | 4 | 461 | | result.X = a.X * k0 + b.X * k1; |
| | 4 | 462 | | result.Y = a.Y * k0 + b.Y * k1; |
| | 4 | 463 | | result.Z = a.Z * k0 + b.Z * k1; |
| | 4 | 464 | | result.W = a.W * k0 + b.W * k1; |
| | | 465 | | |
| | 4 | 466 | | return result; |
| | | 467 | | } |
| | | 468 | | |
| | | 469 | | /// <summary> |
| | | 470 | | /// Returns the angle in degrees between two rotations a and b. |
| | | 471 | | /// </summary> |
| | | 472 | | /// <param name="a">The first rotation.</param> |
| | | 473 | | /// <param name="b">The second rotation.</param> |
| | | 474 | | /// <returns>The angle in degrees between the two rotations.</returns> |
| | | 475 | | public static Fixed64 Angle(FixedQuaternion a, FixedQuaternion b) |
| | | 476 | | { |
| | 5 | 477 | | FixedQuaternion normalizedA = a.Normalized; |
| | 5 | 478 | | FixedQuaternion normalizedB = b.Normalized; |
| | 5 | 479 | | FixedQuaternion relative = normalizedA.Conjugate() * normalizedB; |
| | 5 | 480 | | _ = Vector3d.TryGetMagnitude( |
| | 5 | 481 | | new Vector3d(relative.X, relative.Y, relative.Z), |
| | 5 | 482 | | out Fixed64 vectorMagnitude); |
| | 5 | 483 | | Fixed64 halfAngle = FixedMath.Atan2(vectorMagnitude, relative.W.Abs()); |
| | 5 | 484 | | return FixedMath.RadToDeg(halfAngle * Fixed64.Two); |
| | | 485 | | } |
| | | 486 | | |
| | | 487 | | /// <summary> |
| | | 488 | | /// Creates a quaternion from an angle and axis. |
| | | 489 | | /// </summary> |
| | | 490 | | /// <param name="angle">The angle in degrees.</param> |
| | | 491 | | /// <param name="axis">The axis to rotate around. Nonzero inputs are normalized; zero returns identity.</param> |
| | | 492 | | /// <returns>A quaternion representing the rotation.</returns> |
| | | 493 | | public static FixedQuaternion AngleAxis(Fixed64 angle, Vector3d axis) => |
| | 11 | 494 | | FromAxisAngle(axis, FixedMath.DegToRad(angle)); |
| | | 495 | | |
| | | 496 | | /// <summary> |
| | | 497 | | /// Calculates the dot product of two quaternions. |
| | | 498 | | /// </summary> |
| | | 499 | | /// <param name="a">The first quaternion.</param> |
| | | 500 | | /// <param name="b">The second quaternion.</param> |
| | | 501 | | /// <returns>The dot product of the two quaternions.</returns> |
| | | 502 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 12 | 503 | | public static Fixed64 Dot(FixedQuaternion a, FixedQuaternion b) => a.W * b.W + a.X * b.X + a.Y * b.Y + a.Z * b.Z; |
| | | 504 | | |
| | | 505 | | #endregion |
| | | 506 | | } |