diff --git a/src/FixedMathSharp/Numerics/FixedQuaternion.cs b/src/FixedMathSharp/Numerics/FixedQuaternion.cs index 3f4754c..c7151be 100644 --- a/src/FixedMathSharp/Numerics/FixedQuaternion.cs +++ b/src/FixedMathSharp/Numerics/FixedQuaternion.cs @@ -182,19 +182,48 @@ public FixedQuaternion Rotated(Fixed64 sin, Fixed64 cos, Vector3d? axis = null) #region Quaternion Operations + /// + /// Checks if this vector has been normalized by checking if the magnitude is close to 1. + /// + public bool IsNormalized() + { + Fixed64 mag = GetMagnitude(this); + return FixedMath.Abs(mag - Fixed64.One) <= Fixed64.Epsilon; + } + + public static Fixed64 GetMagnitude(FixedQuaternion q) + { + Fixed64 mag = (q.x * q.x) + (q.y * q.y) + (q.z * q.z) + (q.w * q.w); + // If rounding error caused the final magnitude to be slightly above 1, clamp it + if (mag > Fixed64.One && mag <= Fixed64.One + Fixed64.Epsilon) + return Fixed64.One; + + return mag != Fixed64.Zero ? FixedMath.Sqrt(mag) : Fixed64.Zero; + } + /// /// Normalizes the quaternion to a unit quaternion. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static FixedQuaternion GetNormalized(FixedQuaternion q) { - Fixed64 mag = q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w; - if (mag > Fixed64.Zero && mag != Fixed64.One) - { - Fixed64 invMagnitude = Fixed64.One / FixedMath.Sqrt(mag); - return new FixedQuaternion(q.x * invMagnitude, q.y * invMagnitude, q.z * invMagnitude, q.w * invMagnitude); - } - return q; + Fixed64 mag = GetMagnitude(q); + + // If magnitude is zero, return identity quaternion (to avoid divide by zero) + if (mag == Fixed64.Zero) + return new FixedQuaternion(Fixed64.Zero, Fixed64.Zero, Fixed64.Zero, Fixed64.One); + + // If already normalized, return as-is + if (mag == Fixed64.One) + return q; + + // Normalize it exactly + return new FixedQuaternion( + q.x / mag, + q.y / mag, + q.z / mag, + q.w / mag + ); } /// @@ -343,7 +372,7 @@ public static FixedQuaternion FromAxisAngle(Vector3d axis, Fixed64 angle) public static FixedQuaternion FromEulerAnglesInDegrees(Fixed64 pitch, Fixed64 yaw, Fixed64 roll) { // Convert input angles from degrees to radians - pitch = FixedMath.DegToRad(pitch); + pitch = FixedMath.DegToRad(pitch); yaw = FixedMath.DegToRad(yaw); roll = FixedMath.DegToRad(roll); @@ -432,8 +461,8 @@ public static Vector3d QuaternionLog(FixedQuaternion q) /// - Finally, it divides by `deltaTime` to compute the angular velocity. /// public static Vector3d ToAngularVelocity( - FixedQuaternion currentRotation, - FixedQuaternion previousRotation, + FixedQuaternion currentRotation, + FixedQuaternion previousRotation, Fixed64 deltaTime) { FixedQuaternion rotationDelta = currentRotation * previousRotation.Inverse(); diff --git a/src/FixedMathSharp/Numerics/Vector2d.cs b/src/FixedMathSharp/Numerics/Vector2d.cs index 950756e..e41b088 100644 --- a/src/FixedMathSharp/Numerics/Vector2d.cs +++ b/src/FixedMathSharp/Numerics/Vector2d.cs @@ -335,12 +335,23 @@ public Vector2d Normalize() [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2d Normalize(out Fixed64 mag) { - mag = Magnitude; - if (mag > Fixed64.Zero && mag != Fixed64.One) + mag = GetMagnitude(this); + + // If magnitude is zero, return a zero vector to avoid divide-by-zero errors + if (mag == Fixed64.Zero) { - x /= mag; - y /= mag; + x = Fixed64.Zero; + y = Fixed64.Zero; + return this; } + + // If already normalized, return as-is + if (mag == Fixed64.One) + return this; + + x /= mag; + y /= mag; + return this; } @@ -604,13 +615,19 @@ public Fixed64 SqrDistance(Vector2d other) public static Vector2d GetNormalized(Vector2d value) { Fixed64 mag = GetMagnitude(value); - if (mag > Fixed64.Zero && mag != Fixed64.One) - { - Fixed64 xM = value.x / mag; - Fixed64 yM = value.y / mag; - return new Vector2d(xM, yM); - } - return value; + + if (mag == Fixed64.Zero) + return new Vector2d(Fixed64.Zero, Fixed64.Zero); + + // If already normalized, return as-is + if (mag == Fixed64.One) + return value; + + // Normalize it exactly + return new Vector2d( + value.x / mag, + value.y / mag + ); } /// @@ -621,8 +638,13 @@ public static Vector2d GetNormalized(Vector2d value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Fixed64 GetMagnitude(Vector2d vector) { - Fixed64 temp1 = (vector.x * vector.x) + (vector.y * vector.y); - return temp1.Abs() > Fixed64.Zero ? FixedMath.Sqrt(temp1) : Fixed64.Zero; + Fixed64 mag = (vector.x * vector.x) + (vector.y * vector.y); + + // If rounding error pushed magnitude slightly above 1, clamp it + if (mag > Fixed64.One && mag <= Fixed64.One + Fixed64.Epsilon) + return Fixed64.One; + + return mag.Abs() > Fixed64.Zero ? FixedMath.Sqrt(mag) : Fixed64.Zero; } /// diff --git a/src/FixedMathSharp/Numerics/Vector3d.cs b/src/FixedMathSharp/Numerics/Vector3d.cs index ecbf381..6f909a9 100644 --- a/src/FixedMathSharp/Numerics/Vector3d.cs +++ b/src/FixedMathSharp/Numerics/Vector3d.cs @@ -374,17 +374,26 @@ public Vector3d Normalize() /// If the vector is zero-length or already normalized, no operation is performed, but the original magnitude will still be output. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector3d Normalize(out Fixed64 m) + public Vector3d Normalize(out Fixed64 mag) { - Fixed64 mag = Magnitude; - if (mag > Fixed64.Zero && mag != Fixed64.One) + mag = GetMagnitude(this); + + // If magnitude is zero, return a zero vector to avoid divide-by-zero errors + if (mag == Fixed64.Zero) { - x /= mag; - y /= mag; - z /= mag; + x = Fixed64.Zero; + y = Fixed64.Zero; + z = Fixed64.Zero; + return this; } - m = mag; + // If already normalized, return as-is + if (mag == Fixed64.One) + return this; + + x /= mag; + y /= mag; + z /= mag; return this; } @@ -394,7 +403,7 @@ public Vector3d Normalize(out Fixed64 m) /// public bool IsNormalized() { - return Magnitude.Round() - Fixed64.One == Fixed64.Zero; + return FixedMath.Abs(Magnitude - Fixed64.One) <= Fixed64.Epsilon; } /// @@ -561,14 +570,21 @@ public static Vector3d Slerp(Vector3d start, Vector3d end, Fixed64 percent) public static Vector3d GetNormalized(Vector3d value) { Fixed64 mag = GetMagnitude(value); - if (mag > Fixed64.Zero && mag != Fixed64.One) - { - Fixed64 xM = value.x / mag; - Fixed64 yM = value.y / mag; - Fixed64 zM = value.z / mag; - return new Vector3d(xM, yM, zM); - } - return value; + + // If magnitude is zero, return a zero vector to avoid divide-by-zero errors + if (mag == Fixed64.Zero) + return new Vector3d(Fixed64.Zero, Fixed64.Zero, Fixed64.Zero); + + // If already normalized, return as-is + if (mag == Fixed64.One) + return value; + + // Normalize it exactly + return new Vector3d( + value.x / mag, + value.y / mag, + value.z / mag + ); } /// @@ -579,8 +595,13 @@ public static Vector3d GetNormalized(Vector3d value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Fixed64 GetMagnitude(Vector3d vector) { - Fixed64 temp1 = (vector.x * vector.x) + (vector.y * vector.y) + (vector.z * vector.z); - return temp1 != Fixed64.Zero ? FixedMath.Sqrt(temp1) : Fixed64.Zero; + Fixed64 mag = (vector.x * vector.x) + (vector.y * vector.y) + (vector.z * vector.z); + + // If rounding error pushed magnitude slightly above 1, clamp it + if (mag > Fixed64.One && mag <= Fixed64.One + Fixed64.Epsilon) + return Fixed64.One; + + return mag != Fixed64.Zero ? FixedMath.Sqrt(mag) : Fixed64.Zero; } /// @@ -1010,7 +1031,7 @@ public static Vector3d InverseRotate(Vector3d source, Vector3d position, FixedQu [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3d operator *(Fixed4x4 matrix, Vector3d point) { - if(matrix.IsAffine) + if (matrix.IsAffine) { return new Vector3d( matrix.m00 * point.x + matrix.m01 * point.y + matrix.m02 * point.z + matrix.m03 + matrix.m30,