diff --git a/OpenNest.Core/Geometry/SlideCurvePrimitives.cs b/OpenNest.Core/Geometry/SlideCurvePrimitives.cs
new file mode 100644
index 0000000..c8749a9
--- /dev/null
+++ b/OpenNest.Core/Geometry/SlideCurvePrimitives.cs
@@ -0,0 +1,54 @@
+using OpenNest.Math;
+
+namespace OpenNest.Geometry
+{
+ /// Shared curve primitives for raw distance queries and slide contact events.
+ internal static class SlideCurvePrimitives
+ {
+ internal static bool ContainsContactAngle(Arc arc, double radius, double x, double y)
+ {
+ // A zero-radius curve is a point: its angular range has no geometric meaning.
+ if (arc == null || radius == 0)
+ return true;
+ var angle = Angle.NormalizeRad(System.Math.Atan2(y, x));
+ return Angle.IsBetweenRad(angle, arc.StartAngle, arc.EndAngle, arc.IsReversed);
+ }
+
+ /// Returns both ray-circle parameters, before forward filtering or epsilon snapping.
+ [System.Runtime.CompilerServices.MethodImpl(
+ System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining
+ )]
+ internal static bool SolveRayCircle(
+ double vx,
+ double vy,
+ double cx,
+ double cy,
+ double r,
+ double dirX,
+ double dirY,
+ out double t1,
+ out double t2
+ )
+ {
+ var ox = vx - cx;
+ var oy = vy - cy;
+
+ var a = dirX * dirX + dirY * dirY;
+ var b = 2.0 * (ox * dirX + oy * dirY);
+ var c = ox * ox + oy * oy - r * r;
+
+ var discriminant = b * b - 4.0 * a * c;
+ if (discriminant < 0)
+ {
+ t1 = t2 = double.MaxValue;
+ return false;
+ }
+
+ var sqrtD = System.Math.Sqrt(discriminant);
+ var inv2a = 1.0 / (2.0 * a);
+ t1 = (-b - sqrtD) * inv2a;
+ t2 = (-b + sqrtD) * inv2a;
+ return true;
+ }
+ }
+}
diff --git a/OpenNest.Core/Geometry/SlideEvents.cs b/OpenNest.Core/Geometry/SlideEvents.cs
index 509245e..0edbeb5 100644
--- a/OpenNest.Core/Geometry/SlideEvents.cs
+++ b/OpenNest.Core/Geometry/SlideEvents.cs
@@ -415,11 +415,7 @@ namespace OpenNest.Geometry
private static bool ContainsContactAngle(Arc arc, double radius, double x, double y)
{
- // A zero-radius curve is a point: its angular range has no geometric meaning.
- if (arc == null || radius == 0)
- return true;
- var angle = Angle.NormalizeRad(System.Math.Atan2(y, x));
- return Angle.IsBetweenRad(angle, arc.StartAngle, arc.EndAngle, arc.IsReversed);
+ return SlideCurvePrimitives.ContainsContactAngle(arc, radius, x, y);
}
internal static bool SolveRayCircle(
@@ -434,25 +430,7 @@ namespace OpenNest.Geometry
out double t2
)
{
- var ox = vx - cx;
- var oy = vy - cy;
-
- var a = dirX * dirX + dirY * dirY;
- var b = 2.0 * (ox * dirX + oy * dirY);
- var c = ox * ox + oy * oy - r * r;
-
- var discriminant = b * b - 4.0 * a * c;
- if (discriminant < 0)
- {
- t1 = t2 = double.MaxValue;
- return false;
- }
-
- var sqrtD = System.Math.Sqrt(discriminant);
- var inv2a = 1.0 / (2.0 * a);
- t1 = (-b - sqrtD) * inv2a;
- t2 = (-b + sqrtD) * inv2a;
- return true;
+ return SlideCurvePrimitives.SolveRayCircle(vx, vy, cx, cy, r, dirX, dirY, out t1, out t2);
}
}
diff --git a/OpenNest.Core/Geometry/SpatialQuery.cs b/OpenNest.Core/Geometry/SpatialQuery.cs
index 27d22b2..ed9921e 100644
--- a/OpenNest.Core/Geometry/SpatialQuery.cs
+++ b/OpenNest.Core/Geometry/SpatialQuery.cs
@@ -147,25 +147,7 @@ namespace OpenNest.Geometry
out double t2
)
{
- var ox = vx - cx;
- var oy = vy - cy;
-
- var a = dirX * dirX + dirY * dirY;
- var b = 2.0 * (ox * dirX + oy * dirY);
- var c = ox * ox + oy * oy - r * r;
-
- var discriminant = b * b - 4.0 * a * c;
- if (discriminant < 0)
- {
- t1 = t2 = double.MaxValue;
- return false;
- }
-
- var sqrtD = System.Math.Sqrt(discriminant);
- var inv2a = 1.0 / (2.0 * a);
- t1 = (-b - sqrtD) * inv2a;
- t2 = (-b + sqrtD) * inv2a;
- return true;
+ return SlideCurvePrimitives.SolveRayCircle(vx, vy, cx, cy, r, dirX, dirY, out t1, out t2);
}
///
@@ -312,11 +294,7 @@ namespace OpenNest.Geometry
private static bool ContainsContactAngle(Arc arc, double radius, double x, double y)
{
- // A zero-radius curve is a point: its angular range has no geometric meaning.
- if (arc == null || radius == 0)
- return true;
- var angle = Angle.NormalizeRad(System.Math.Atan2(y, x));
- return Angle.IsBetweenRad(angle, arc.StartAngle, arc.EndAngle, arc.IsReversed);
+ return SlideCurvePrimitives.ContainsContactAngle(arc, radius, x, y);
}
///
diff --git a/OpenNest.Tests/Geometry/RayContactPrimitiveCharacterizationTests.cs b/OpenNest.Tests/Geometry/RayContactPrimitiveCharacterizationTests.cs
new file mode 100644
index 0000000..6de8639
--- /dev/null
+++ b/OpenNest.Tests/Geometry/RayContactPrimitiveCharacterizationTests.cs
@@ -0,0 +1,280 @@
+using System.Reflection;
+using OpenNest.Geometry;
+using OpenNest.Math;
+
+namespace OpenNest.Tests.Geometry;
+
+public class RayContactPrimitiveCharacterizationTests
+{
+ private delegate bool RaySolver(double vx, double vy, double cx, double cy, double radius,
+ double dx, double dy, out double near, out double far);
+
+ private static readonly RaySolver QuerySolver = Bind(typeof(SpatialQuery), "SolveRayCircle");
+ private static readonly RaySolver EventSolver = Bind(typeof(SlideEvents), "SolveRayCircle");
+ private static readonly Func QueryAngle =
+ Bind>(typeof(SpatialQuery), "ContainsContactAngle");
+ private static readonly Func EventAngle =
+ Bind>(typeof(SlideEvents), "ContainsContactAngle");
+
+ public static IEnumerable