Skip to content

Commit 69ff680

Browse files
committed
feat(vector3d): add SnapSmallComponentsToZero and improve epsilon-based vector guards
1 parent 44582df commit 69ff680

4 files changed

Lines changed: 66 additions & 11 deletions

File tree

src/FixedMathSharp/Numerics/Extensions/Fixed64.Extensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ public static bool FuzzyComponentEqual(this Fixed64 a, Fixed64 b, Fixed64 percen
236236
var allowedErr = a.Abs() * percentage;
237237
// Compare directly to percentage if a is zero
238238
// Otherwise, use percentage of a's magnitude
239-
return a == Fixed64.Zero ? diff <= percentage : diff <= allowedErr;
239+
return a.LessThanEpsilon() ? diff <= percentage : diff <= allowedErr;
240240
}
241241

242242
#endregion

src/FixedMathSharp/Numerics/Vector2d.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ public static Vector2d GetNormalized(Vector2d value)
652652
return new Vector2d(Fixed64.Zero, Fixed64.Zero);
653653

654654
// If already normalized, return as-is
655-
if (mag == Fixed64.One)
655+
if (FixedMath.Abs(mag - Fixed64.One) <= Fixed64.Epsilon)
656656
return value;
657657

658658
// Normalize it exactly
@@ -1011,7 +1011,7 @@ public bool NotZero()
10111011
[MethodImpl(MethodImplOptions.AggressiveInlining)]
10121012
public bool AllComponentsGreaterThanEpsilon()
10131013
{
1014-
return x > Fixed64.Epsilon && y > Fixed64.Epsilon;
1014+
return x.Abs() > Fixed64.Epsilon && y.Abs() > Fixed64.Epsilon;
10151015
}
10161016

10171017
[MethodImpl(MethodImplOptions.AggressiveInlining)]

src/FixedMathSharp/Numerics/Vector3d.cs

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,30 @@ public bool IsNormalized()
449449
[MethodImpl(MethodImplOptions.AggressiveInlining)]
450450
public bool AllComponentsGreaterThanEpsilon()
451451
{
452-
return x > Fixed64.Epsilon && y > Fixed64.Epsilon && z > Fixed64.Epsilon;
452+
return x.Abs() > Fixed64.Epsilon && y.Abs() > Fixed64.Epsilon && z.Abs() > Fixed64.Epsilon;
453+
}
454+
455+
/// <summary>
456+
/// Returns a new vector with components whose absolute values are less than the specified threshold set to zero.
457+
/// </summary>
458+
/// <remarks>
459+
/// This method is useful for eliminating insignificant floating-point errors by zeroing out very small vector components.
460+
/// The default threshold is suitable for most cases where near-zero values are considered noise.
461+
/// </remarks>
462+
/// <param name="threshold">
463+
/// The minimum absolute value a component must have to be retained.
464+
/// If null, a default epsilon value is used.
465+
/// </param>
466+
/// <returns>A new Vector3d instance with small components snapped to zero based on the specified threshold.</returns>
467+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
468+
public Vector3d SnapSmallComponentsToZero(Fixed64? threshold = null)
469+
{
470+
Fixed64 effectiveThreshold = threshold ?? Fixed64.Epsilon;
471+
return new Vector3d(
472+
x.Abs() < effectiveThreshold ? Fixed64.Zero : x,
473+
y.Abs() < effectiveThreshold ? Fixed64.Zero : y,
474+
z.Abs() < effectiveThreshold ? Fixed64.Zero : z
475+
);
453476
}
454477

455478
/// <summary>
@@ -622,7 +645,7 @@ public static Vector3d GetNormalized(Vector3d value)
622645
return new Vector3d(Fixed64.Zero, Fixed64.Zero, Fixed64.Zero);
623646

624647
// If already normalized, return as-is
625-
if (mag == Fixed64.One)
648+
if (FixedMath.Abs(mag - Fixed64.One) <= Fixed64.Epsilon)
626649
return value;
627650

628651
// Normalize it exactly
@@ -643,8 +666,8 @@ public static Fixed64 GetMagnitude(Vector3d vector)
643666
{
644667
Fixed64 mag = (vector.x * vector.x) + (vector.y * vector.y) + (vector.z * vector.z);
645668

646-
// If rounding error pushed magnitude slightly above 1, clamp it
647-
if (mag > Fixed64.One && mag <= Fixed64.One + Fixed64.Epsilon)
669+
// Clamp tiny drift around 1 in either direction.
670+
if (FixedMath.Abs(mag - Fixed64.One) <= Fixed64.Epsilon)
648671
return Fixed64.One;
649672

650673
return mag != Fixed64.Zero ? FixedMath.Sqrt(mag) : Fixed64.Zero;
@@ -817,9 +840,15 @@ public static (Vector3d, Vector3d) ClosestPointsOnTwoLines(Vector3d line1Start,
817840
}
818841

819842
[MethodImpl(MethodImplOptions.AggressiveInlining)]
820-
private static (Fixed64 sc, Fixed64 tc) SolveClosestLineParameters(Fixed64 a, Fixed64 b, Fixed64 c, Fixed64 d, Fixed64 e, Fixed64 determinant)
843+
private static (Fixed64 sc, Fixed64 tc) SolveClosestLineParameters(
844+
Fixed64 a,
845+
Fixed64 b,
846+
Fixed64 c,
847+
Fixed64 d,
848+
Fixed64 e,
849+
Fixed64 determinant)
821850
{
822-
if (determinant < Fixed64.Epsilon)
851+
if (determinant.Abs() < Fixed64.Epsilon)
823852
return (Fixed64.Zero, b > c ? d / b : e / c);
824853

825854
return ((b * e - c * d) / determinant, (a * e - b * d) / determinant);
@@ -903,7 +932,7 @@ public static Fixed64 CrossProduct(Vector3d lhs, Vector3d rhs)
903932
public static Vector3d Project(Vector3d vector, Vector3d onNormal)
904933
{
905934
Fixed64 sqrMag = Dot(onNormal, onNormal);
906-
if (sqrMag < Fixed64.Epsilon)
935+
if (sqrMag.Abs() < Fixed64.Epsilon)
907936
return Zero;
908937
else
909938
{
@@ -923,7 +952,7 @@ public static Vector3d Project(Vector3d vector, Vector3d onNormal)
923952
public static Vector3d ProjectOnPlane(Vector3d vector, Vector3d planeNormal)
924953
{
925954
Fixed64 sqrMag = Dot(planeNormal, planeNormal);
926-
if (sqrMag < Fixed64.Epsilon)
955+
if (sqrMag.Abs() < Fixed64.Epsilon)
927956
return vector;
928957
else
929958
{

tests/FixedMathSharp.Tests/Vector3d.Tests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,32 @@ public void AllComponentsGreaterThanEpsilon_ReturnsFalse_WhenAComponentIsAtOrBel
111111
Assert.False(vector.AllComponentsGreaterThanEpsilon());
112112
}
113113

114+
[Fact]
115+
public void SnapSmallComponentsToZero_UsesDefaultEpsilonThreshold()
116+
{
117+
var halfEpsilon = Fixed64.FromRaw(Fixed64.Epsilon.m_rawValue / 2);
118+
var vector = new Vector3d(halfEpsilon, -halfEpsilon, Fixed64.Epsilon);
119+
120+
var result = vector.SnapSmallComponentsToZero();
121+
122+
Assert.Equal(Fixed64.Zero, result.x);
123+
Assert.Equal(Fixed64.Zero, result.y);
124+
Assert.Equal(Fixed64.Epsilon, result.z); // Boundary: abs(z) == threshold is retained
125+
}
126+
127+
[Fact]
128+
public void SnapSmallComponentsToZero_UsesCustomThreshold_AndKeepsBoundaryValues()
129+
{
130+
var threshold = new Fixed64(0.1);
131+
var vector = new Vector3d(new Fixed64(0.05), new Fixed64(-0.1), new Fixed64(0.2));
132+
133+
var result = vector.SnapSmallComponentsToZero(threshold);
134+
135+
Assert.Equal(Fixed64.Zero, result.x); // abs(x) < threshold -> snapped
136+
Assert.Equal(new Fixed64(-0.1), result.y); // abs(y) == threshold -> retained
137+
Assert.Equal(new Fixed64(0.2), result.z); // abs(z) > threshold -> retained
138+
}
139+
114140
#endregion
115141

116142
#region Test: Arithmetic

0 commit comments

Comments
 (0)