From 633229891285203cc97a1d3cc0561edaa8b4f517 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Fri, 6 Mar 2026 18:27:32 -0500 Subject: [PATCH] feat: add Helper.DirectionalDistance for polygon-based push Co-Authored-By: Claude Opus 4.6 --- OpenNest.Core/Helper.cs | 124 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/OpenNest.Core/Helper.cs b/OpenNest.Core/Helper.cs index 1372bff..d4ab5a9 100644 --- a/OpenNest.Core/Helper.cs +++ b/OpenNest.Core/Helper.cs @@ -776,6 +776,130 @@ namespace OpenNest return lines; } + /// + /// Finds the distance from a vertex to a line segment along a push axis. + /// Returns double.MaxValue if the ray does not hit the segment. + /// + private static double RayEdgeDistance(Vector vertex, Line edge, PushDirection direction) + { + var p1 = edge.StartPoint; + var p2 = edge.EndPoint; + + switch (direction) + { + case PushDirection.Left: + { + // Ray goes in -X direction. Need non-horizontal edge. + if (p1.Y.IsEqualTo(p2.Y)) + return double.MaxValue; // horizontal edge, parallel to ray + + var t = (vertex.Y - p1.Y) / (p2.Y - p1.Y); + if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon) + return double.MaxValue; + + var ix = p1.X + t * (p2.X - p1.X); + var dist = vertex.X - ix; // positive if edge is to the left + return dist > Tolerance.Epsilon ? dist : double.MaxValue; + } + + case PushDirection.Right: + { + if (p1.Y.IsEqualTo(p2.Y)) + return double.MaxValue; + + var t = (vertex.Y - p1.Y) / (p2.Y - p1.Y); + if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon) + return double.MaxValue; + + var ix = p1.X + t * (p2.X - p1.X); + var dist = ix - vertex.X; + return dist > Tolerance.Epsilon ? dist : double.MaxValue; + } + + case PushDirection.Down: + { + // Ray goes in -Y direction. Need non-vertical edge. + if (p1.X.IsEqualTo(p2.X)) + return double.MaxValue; // vertical edge, parallel to ray + + var t = (vertex.X - p1.X) / (p2.X - p1.X); + if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon) + return double.MaxValue; + + var iy = p1.Y + t * (p2.Y - p1.Y); + var dist = vertex.Y - iy; + return dist > Tolerance.Epsilon ? dist : double.MaxValue; + } + + case PushDirection.Up: + { + if (p1.X.IsEqualTo(p2.X)) + return double.MaxValue; + + var t = (vertex.X - p1.X) / (p2.X - p1.X); + if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon) + return double.MaxValue; + + var iy = p1.Y + t * (p2.Y - p1.Y); + var dist = iy - vertex.Y; + return dist > Tolerance.Epsilon ? dist : double.MaxValue; + } + + default: + return double.MaxValue; + } + } + + /// + /// Computes the minimum translation distance along a push direction before + /// any edge of movingLines contacts any edge of stationaryLines. + /// Returns double.MaxValue if no collision path exists. + /// + public static double DirectionalDistance(List movingLines, List stationaryLines, PushDirection direction) + { + var minDist = double.MaxValue; + + // Case 1: Each moving vertex → each stationary edge + for (int i = 0; i < movingLines.Count; i++) + { + var movingLine = movingLines[i]; + + for (int j = 0; j < stationaryLines.Count; j++) + { + var d = RayEdgeDistance(movingLine.StartPoint, stationaryLines[j], direction); + if (d < minDist) minDist = d; + } + } + + // Case 2: Each stationary vertex → each moving edge (opposite direction) + var opposite = OppositeDirection(direction); + + for (int i = 0; i < stationaryLines.Count; i++) + { + var stationaryLine = stationaryLines[i]; + + for (int j = 0; j < movingLines.Count; j++) + { + var d = RayEdgeDistance(stationaryLine.StartPoint, movingLines[j], opposite); + if (d < minDist) minDist = d; + } + } + + return minDist; + } + + private static PushDirection OppositeDirection(PushDirection direction) + { + switch (direction) + { + case PushDirection.Left: return PushDirection.Right; + case PushDirection.Right: return PushDirection.Left; + case PushDirection.Up: return PushDirection.Down; + case PushDirection.Down: return PushDirection.Up; + default: return direction; + } + } + public static double ClosestDistanceLeft(Box box, List boxes) { var closestDistance = double.MaxValue;