Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/FixedMathSharp/Bounds/BoundingBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@
public static Vector3d FindClosestPointsBetweenBoxes(BoundingBox a, BoundingBox b)
{
Vector3d closestPoint = Vector3d.Zero;
Fixed64 minDistance = Fixed64.MaxValue;
Fixed64 minDistance = Fixed64.MAX_VALUE;
for (int i = 0; i < b.Vertices.Length; i++)
{
Vector3d point = a.ClosestPointOnSurface(b.Vertices[i]);
Expand Down Expand Up @@ -380,7 +380,7 @@
#region Equality and HashCode Overrides

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj) => obj is BoundingBox other && Equals(other);

Check warning on line 383 in src/FixedMathSharp/Bounds/BoundingBox.cs

View workflow job for this annotation

GitHub Actions / build-and-test-windows

Nullability of type of parameter 'obj' doesn't match overridden member (possibly because of nullability attributes).

Check warning on line 383 in src/FixedMathSharp/Bounds/BoundingBox.cs

View workflow job for this annotation

GitHub Actions / build-and-test-linux

Nullability of type of parameter 'obj' doesn't match overridden member (possibly because of nullability attributes).

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(BoundingBox other) => _center.Equals(other._center) && Scope.Equals(other.Scope);
Expand Down
6 changes: 3 additions & 3 deletions src/FixedMathSharp/Core/FixedMath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public static Fixed64 Clamp01(Fixed64 value)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Fixed64 Clamp(Fixed64 f1, Fixed64 min, Fixed64? max = null)
{
Fixed64 m = max ?? Fixed64.MaxValue;
Fixed64 m = max ?? Fixed64.MAX_VALUE;
return f1 < min ? min : f1 > m ? m : f1;
}

Expand All @@ -100,7 +100,7 @@ public static Fixed64 Abs(Fixed64 value)
{
// For the minimum value, return the max to avoid overflow
if (value.m_rawValue == MIN_VALUE_L)
return Fixed64.MaxValue;
return Fixed64.MAX_VALUE;

// Use branchless absolute value calculation
long mask = value.m_rawValue >> 63; // If negative, mask will be all 1s; if positive, all 0s
Expand Down Expand Up @@ -335,7 +335,7 @@ public static Fixed64 MoveTowards(Fixed64 from, Fixed64 to, Fixed64 maxAmount)
/// <returns>The sum of <paramref name="x"/> and <paramref name="y"/>.</returns>
/// <remarks>
/// Overflow is detected by checking for a change in the sign bit that indicates a wrap-around.
/// Additionally, a special check is performed for adding <see cref="Fixed64.MinValue"/> and -1,
/// Additionally, a special check is performed for adding <see cref="Fixed64.MIN_VALUE"/> and -1,
/// as this is a known edge case for overflow.
/// </remarks>
public static long AddOverflowHelper(long x, long y, ref bool overflow)
Expand Down
90 changes: 54 additions & 36 deletions src/FixedMathSharp/Core/FixedTrigonometry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,30 @@ public static partial class FixedMath

// Trigonometric and logarithmic constants
internal const double PI_D = 3.14159265358979323846;
public static readonly Fixed64 PI = new Fixed64(PI_D);
public static readonly Fixed64 PI = (Fixed64)PI_D;
public static readonly Fixed64 TwoPI = PI * 2;
public static readonly Fixed64 PiOver2 = PI / 2;
public static readonly Fixed64 PiOver3 = PI / 3;
public static readonly Fixed64 PiOver4 = PI / 4;
public static readonly Fixed64 PiOver6 = PI / 6;
public static readonly Fixed64 Ln2 = new Fixed64(0.6931471805599453); // Natural logarithm of 2
public static readonly Fixed64 Ln2 = (Fixed64)0.6931471805599453; // Natural logarithm of 2

public static readonly Fixed64 Log2Max = new Fixed64(63L * ONE_L);
public static readonly Fixed64 Log2Min = new Fixed64(-64L * ONE_L);
public static readonly Fixed64 LOG_2_MAX = new Fixed64(63L * ONE_L);
public static readonly Fixed64 LOG_2_MIN = new Fixed64(-64L * ONE_L);

internal const double DEG2RAD_D = 0.01745329251994329576; // π / 180
public static readonly Fixed64 Deg2Rad = new Fixed64(DEG2RAD_D); // Degrees to radians conversion factor
public static readonly Fixed64 Deg2Rad = (Fixed64)DEG2RAD_D; // Degrees to radians conversion factor
internal const double RAD2DEG_D = 57.2957795130823208767; // 180 / π
public static readonly Fixed64 Rad2Deg = new Fixed64(RAD2DEG_D); // Radians to degrees conversion factor
public static readonly Fixed64 Rad2Deg = (Fixed64)RAD2DEG_D; // Radians to degrees conversion factor

// Asin Padé approximations
private static readonly Fixed64 PadeA1 = new Fixed64(0.183320102);
private static readonly Fixed64 PadeA2 = new Fixed64(0.0218804099);
private static readonly Fixed64 PADE_A1 = (Fixed64)0.183320102;
private static readonly Fixed64 PADE_A2 = (Fixed64)0.0218804099;

// Carefully optimized polynomial coefficients for sin(x), ensuring maximum precision in Fixed64 math.
private static readonly Fixed64 SIN_COEFF_3 = (Fixed64)0.16666667605750262737274169921875d; // 1/3!
private static readonly Fixed64 SIN_COEFF_5 = (Fixed64)0.0083328341133892536163330078125d; // 1/5!
private static readonly Fixed64 SIN_COEFF_7 = (Fixed64)0.00019588856957852840423583984375d; // 1/7!

#endregion

Expand Down Expand Up @@ -93,11 +98,11 @@ public static Fixed64 Pow2(Fixed64 x)
if (x == Fixed64.One)
return neg ? Fixed64.One / Fixed64.Two : Fixed64.Two;

if (x >= Log2Max)
return neg ? Fixed64.One / Fixed64.MaxValue : Fixed64.MaxValue;
if (x >= LOG_2_MAX)
return neg ? Fixed64.One / Fixed64.MAX_VALUE : Fixed64.MAX_VALUE;

if (x <= Log2Min)
return neg ? Fixed64.MaxValue : Fixed64.Zero;
if (x <= LOG_2_MIN)
return neg ? Fixed64.MAX_VALUE : Fixed64.Zero;

/*
* Taylor series expansion for exp(x)
Expand Down Expand Up @@ -269,19 +274,30 @@ public static Fixed64 DegToRad(Fixed64 deg)
}

/// <summary>
/// Returns the sine of a specified angle in radians.
/// Computes the sine of a given angle in radians using an optimized
/// minimax polynomial approximation.
/// </summary>
/// <param name="x">The angle in radians.</param>
/// <returns>The sine of the given angle, in fixed-point format.</returns>
/// <remarks>
/// The relative error is less than 1E-10 for x in [-2PI, 2PI], and less than 1E-7 in the worst case.
/// - This function uses a Chebyshev-polynomial-based approximation to ensure high accuracy
/// while maintaining performance in fixed-point arithmetic.
/// - The coefficients have been carefully tuned to minimize fixed-point truncation errors.
/// - The error is less than 1 ULP (unit in the last place) at key reference points,
/// ensuring <c>Sin(π/4) = 0.707106781192124</c> exactly within Fixed64 precision.
/// - The function automatically normalizes input values to the range [-π, π] for stability.
/// </remarks>
public static Fixed64 Sin(Fixed64 x)
{
// Check for special cases
if (x == Fixed64.Zero) return Fixed64.Zero;
if (x == PiOver2) return Fixed64.One;
if (x == -PiOver2) return -Fixed64.One;

// Ensure x is in the range [-2π, 2π]
if (x == Fixed64.Zero) return Fixed64.Zero; // sin(0) = 0
if (x == PiOver2) return Fixed64.One; // sin(π/2) = 1
if (x == -PiOver2) return -Fixed64.One; // sin(-π/2) = -1
if (x == PI) return Fixed64.Zero; // sin(π) = 0
if (x == -PI) return Fixed64.Zero; // sin(-π) = 0
if (x == TwoPI || x == -TwoPI) return Fixed64.Zero; // sin(2π) = 0

// Normalize x to [-π, π]
x %= TwoPI;
if (x < -PI)
x += TwoPI;
Expand All @@ -298,31 +314,33 @@ public static Fixed64 Sin(Fixed64 x)
if (x > PiOver2)
x = PI - x;

// Use Taylor series approximation
Fixed64 result = x;
Fixed64 term = x;
// Precompute x^2
Fixed64 x2 = x * x;
int sign = -1;

for (int i = 3; i < 15; i += 2)
{
term *= x2 / (i * (i - 1));
if (term.Abs() < Fixed64.Epsilon)
break;

result += term * sign;
sign = -sign;
}
// Optimized Chebyshev Polynomial for Sin(x)
Fixed64 result = x * (Fixed64.One
- x2 * SIN_COEFF_3
+ (x2 * x2) * SIN_COEFF_5
- (x2 * x2 * x2) * SIN_COEFF_7);

return flip ? -result : result;
}

/// <summary>
/// Returns the cosine of x.
/// The relative error is less than 1E-10 for x in [-2PI, 2PI], and less than 1E-7 in the worst case.
/// Computes the cosine of a given angle in radians using a sine-based identity transformation.
/// </summary>
/// <param name="x">The angle in radians.</param>
/// <returns>The cosine of the given angle, in fixed-point format.</returns>
/// <remarks>
/// - Instead of directly approximating cosine, this function derives <c>cos(x)</c> using
/// the identity <c>cos(x) = sin(x + π/2)</c>. This ensures maximum accuracy.
/// - The underlying sine function is computed using a highly optimized minimax polynomial approximation.
/// - By leveraging this transformation, cosine achieves the same precision guarantees
/// as sine, including <c>Cos(π/4) = 0.707106781192124</c> exactly within Fixed64 precision.
/// - The function automatically normalizes input values to the range [-π, π] for stability.
/// </remarks>
public static Fixed64 Cos(Fixed64 x)
{
{
long xl = x.m_rawValue;
long rawAngle = xl + (xl > 0 ? -PI.m_rawValue - PiOver2.m_rawValue : PiOver2.m_rawValue);
return Sin(Fixed64.FromRaw(rawAngle));
Expand Down Expand Up @@ -401,7 +419,7 @@ public static Fixed64 Asin(Fixed64 x)
{
// Padé approximation of asin(x) for |x| < 0.5
Fixed64 xSquared = x * x;
Fixed64 numerator = x * (Fixed64.One + (xSquared * (PadeA1 + (xSquared * PadeA2))));
Fixed64 numerator = x * (Fixed64.One + (xSquared * (PADE_A1 + (xSquared * PADE_A2))));
return numerator;
}

Expand Down
27 changes: 9 additions & 18 deletions src/FixedMathSharp/Extensions/Fixed4x4.Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,29 @@ public static class Fixed4x4Extensions
{
#region Extraction, and Setters

/// <inheritdoc cref="Fixed4x4.ExtractScale(Fixed4x4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3d ExtractScale(this Fixed4x4 matrix)
{
return Fixed4x4.ExtractScale(matrix);
}

/// <inheritdoc cref="Fixed4x4.ExtractLossyScale(Fixed4x4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3d ExtractLossyScale(this Fixed4x4 matrix)
{
return Fixed4x4.ExtractLossyScale(matrix);
}

/// <inheritdoc cref="Fixed4x4.ExtractTranslation(Fixed4x4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3d ExtractTranslation(this Fixed4x4 matrix)
/// <inheritdoc cref="Fixed4x4.SetGlobalScale(Fixed4x4, Vector3d)" />
public static Fixed4x4 SetGlobalScale(this ref Fixed4x4 matrix, Vector3d globalScale)
{
return Fixed4x4.ExtractTranslation(matrix);
return matrix = Fixed4x4.SetGlobalScale(matrix, globalScale);
}

/// <inheritdoc cref="Fixed4x4.ExtractRotation(Fixed4x4)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static FixedQuaternion ExtractRotation(this Fixed4x4 matrix)
/// <inheritdoc cref="Fixed4x4.SetTranslation(Fixed4x4, Vector3d)" />
public static Fixed4x4 SetTranslation(this ref Fixed4x4 matrix, Vector3d position)
{
return Fixed4x4.ExtractRotation(matrix);
return matrix = Fixed4x4.SetTranslation(matrix, position);
}

/// <inheritdoc cref="Fixed4x4.SetGlobalScale(Fixed4x4, Vector3d)" />
public static Fixed4x4 SetGlobalScale(this ref Fixed4x4 matrix, Vector3d globalScale)
/// <inheritdoc cref="Fixed4x4.SetRotation(Fixed4x4, FixedQuaternion)" />
public static Fixed4x4 SetRotation(this ref Fixed4x4 matrix, FixedQuaternion rotation)
{
return matrix = Fixed4x4.SetGlobalScale(matrix, globalScale);
return matrix = Fixed4x4.SetRotation(matrix, rotation);
}

/// <inheritdoc cref="Fixed4x4.TransformPoint(Fixed4x4, Vector3d)" />
Expand Down
5 changes: 5 additions & 0 deletions src/FixedMathSharp/Extensions/Vector3d.Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public static Vector3d ClampOneInPlace(this Vector3d v)
return v;
}

public static Vector3d ClampMagnitude(this Vector3d value, Fixed64 maxMagnitude)
{
return Vector3d.ClampMagnitude(value, maxMagnitude);
}

/// <summary>
/// Checks if the distance between two vectors is less than or equal to a specified factor.
/// </summary>
Expand Down
30 changes: 21 additions & 9 deletions src/FixedMathSharp/Numerics/Fixed4x4.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,16 @@ public Fixed4x4(

public readonly bool IsAffine => (m33 == Fixed64.One) && (m03 == Fixed64.Zero && m13 == Fixed64.Zero && m23 == Fixed64.Zero);

/// <summary>
/// Gets or sets the translation component of this matrix.
/// </summary>
/// <returns>
/// The translation component of the current instance.
/// </returns>
public readonly Vector3d Translation => this.ExtractTranslation();
/// <inheritdoc cref="ExtractTranslation(Fixed4x4)" />
public readonly Vector3d Translation => ExtractTranslation(this);

public readonly Vector3d Up => ExtractUp(this);

public readonly Vector3d Scale => this.ExtractScale();
/// <inheritdoc cref="ExtractScale(Fixed4x4)" />
public readonly Vector3d Scale => ExtractScale(this);

public readonly FixedQuaternion Rotation => this.ExtractRotation();
/// <inheritdoc cref="ExtractRotation(Fixed4x4)" />
public readonly FixedQuaternion Rotation => ExtractRotation(this);

/// <summary>
/// Calculates the determinant of a 4x4 matrix.
Expand Down Expand Up @@ -275,6 +274,19 @@ public static Vector3d ExtractTranslation(Fixed4x4 matrix)
return new Vector3d(matrix.m30, matrix.m31, matrix.m32);
}

/// <summary>
/// Extracts the up direction from the 4x4 matrix.
/// </summary>
/// <remarks>
/// This is the surface normal if the matrix represents ground orientation.
/// </remarks>
/// <param name="matrix"></param>
/// <returns>A <see cref="Vector3d"/> representing the up direction.</returns>
public static Vector3d ExtractUp(Fixed4x4 matrix)
{
return new Vector3d(matrix.m10, matrix.m11, matrix.m12).Normalize();
}

/// <summary>
/// Extracts the scaling factors from the matrix by calculating the magnitudes of the basis vectors (non-lossy).
/// </summary>
Expand Down
16 changes: 8 additions & 8 deletions src/FixedMathSharp/Numerics/Fixed64.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ public partial struct Fixed64 : IEquatable<Fixed64>, IComparable<Fixed64>, IEqua
/// </summary>
public long m_rawValue;

public static readonly Fixed64 MaxValue = new Fixed64(FixedMath.MAX_VALUE_L);
public static readonly Fixed64 MinValue = new Fixed64(FixedMath.MIN_VALUE_L);
public static readonly Fixed64 MAX_VALUE = new Fixed64(FixedMath.MAX_VALUE_L);
public static readonly Fixed64 MIN_VALUE = new Fixed64(FixedMath.MIN_VALUE_L);

public static readonly Fixed64 One = new Fixed64(FixedMath.ONE_L);
public static readonly Fixed64 Two = One * 2;
Expand Down Expand Up @@ -332,18 +332,18 @@ public static explicit operator decimal(Fixed64 value)
if (opSignsEqual)
{
if (sum < 0 || (overflow && xl > 0))
return MaxValue;
return MAX_VALUE;
}
else
{
if (sum > 0)
return MinValue;
return MIN_VALUE;
}

// Final overflow check: if the high 32 bits are non-zero or non-sign-extended, it's an overflow
long topCarry = hihi >> FixedMath.SHIFT_AMOUNT_I;
if (topCarry != 0 && topCarry != -1)
return opSignsEqual ? MaxValue : MinValue;
return opSignsEqual ? MAX_VALUE : MIN_VALUE;

// Negative overflow check
if (!opSignsEqual)
Expand All @@ -352,7 +352,7 @@ public static explicit operator decimal(Fixed64 value)
long negOp = xl < yl ? xl : yl;

if (sum > negOp && negOp < -FixedMath.ONE_L && posOp > FixedMath.ONE_L)
return MinValue;
return MIN_VALUE;
}

return new Fixed64(sum);
Expand Down Expand Up @@ -413,7 +413,7 @@ public static explicit operator decimal(Fixed64 value)

// Detect overflow
if ((div & ~(0xFFFFFFFFFFFFFFFF >> bitPos)) != 0)
return ((xl ^ yl) & FixedMath.MIN_VALUE_L) == 0 ? MaxValue : MinValue;
return ((xl ^ yl) & FixedMath.MIN_VALUE_L) == 0 ? MAX_VALUE : MIN_VALUE;

remainder <<= 1;
--bitPos;
Expand Down Expand Up @@ -456,7 +456,7 @@ public static explicit operator decimal(Fixed64 value)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Fixed64 operator -(Fixed64 x)
{
return x.m_rawValue == FixedMath.MIN_VALUE_L ? MaxValue : new Fixed64(-x.m_rawValue);
return x.m_rawValue == FixedMath.MIN_VALUE_L ? MAX_VALUE : new Fixed64(-x.m_rawValue);
}

/// <summary>
Expand Down
Loading
Loading