From b509a4139dc1c1ab0384e5b2c3d7bcbf5a46bc59 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Fri, 13 Mar 2026 20:29:36 -0400 Subject: [PATCH] refactor: optimize directional distance for GPU-friendly batching Add primitive-parameter RayEdgeDistance overload with AggressiveInlining, merge Left/Right and Up/Down cases. Add DirectionalDistance overload that accepts (dx, dy) translation without creating Line objects, and FlattenLines for packing segments into flat arrays for GPU transfer. Co-Authored-By: Claude Opus 4.6 --- OpenNest.Core/Helper.cs | 142 +++++++++++++++++++++++++++------------- 1 file changed, 96 insertions(+), 46 deletions(-) diff --git a/OpenNest.Core/Helper.cs b/OpenNest.Core/Helper.cs index 5f0da8d..c2c23af 100644 --- a/OpenNest.Core/Helper.cs +++ b/OpenNest.Core/Helper.cs @@ -863,81 +863,55 @@ namespace OpenNest /// private static double RayEdgeDistance(Vector vertex, Line edge, PushDirection direction) { - var p1x = edge.pt1.X; - var p1y = edge.pt1.Y; - var p2x = edge.pt2.X; - var p2y = edge.pt2.Y; + return RayEdgeDistance( + vertex.X, vertex.Y, + edge.pt1.X, edge.pt1.Y, edge.pt2.X, edge.pt2.Y, + direction); + } + [System.Runtime.CompilerServices.MethodImpl( + System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + private static double RayEdgeDistance( + double vx, double vy, + double p1x, double p1y, double p2x, double p2y, + PushDirection direction) + { switch (direction) { case PushDirection.Left: - { - // Ray goes in -X direction. Need non-horizontal edge. - var dy = p2y - p1y; - if (dy > -Tolerance.Epsilon && dy < Tolerance.Epsilon) - return double.MaxValue; // horizontal edge, parallel to ray - - var t = (vertex.Y - p1y) / dy; - if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon) - return double.MaxValue; - - var ix = p1x + t * (p2x - p1x); - var dist = vertex.X - ix; // positive if edge is to the left - if (dist > Tolerance.Epsilon) return dist; - if (dist >= -Tolerance.Epsilon) return 0; // touching - return double.MaxValue; // edge is behind vertex - } - case PushDirection.Right: { var dy = p2y - p1y; if (dy > -Tolerance.Epsilon && dy < Tolerance.Epsilon) return double.MaxValue; - var t = (vertex.Y - p1y) / dy; + var t = (vy - p1y) / dy; if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon) return double.MaxValue; var ix = p1x + t * (p2x - p1x); - var dist = ix - vertex.X; + var dist = direction == PushDirection.Left ? vx - ix : ix - vx; if (dist > Tolerance.Epsilon) return dist; - if (dist >= -Tolerance.Epsilon) return 0; // touching - return double.MaxValue; // edge is behind vertex + if (dist >= -Tolerance.Epsilon) return 0; + return double.MaxValue; } case PushDirection.Down: - { - // Ray goes in -Y direction. Need non-vertical edge. - var dx = p2x - p1x; - if (dx > -Tolerance.Epsilon && dx < Tolerance.Epsilon) - return double.MaxValue; // vertical edge, parallel to ray - - var t = (vertex.X - p1x) / dx; - if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon) - return double.MaxValue; - - var iy = p1y + t * (p2y - p1y); - var dist = vertex.Y - iy; - if (dist > Tolerance.Epsilon) return dist; - if (dist >= -Tolerance.Epsilon) return 0; // touching - return double.MaxValue; // edge is behind vertex - } - case PushDirection.Up: { var dx = p2x - p1x; if (dx > -Tolerance.Epsilon && dx < Tolerance.Epsilon) return double.MaxValue; - var t = (vertex.X - p1x) / dx; + var t = (vx - p1x) / dx; if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon) return double.MaxValue; var iy = p1y + t * (p2y - p1y); - var dist = iy - vertex.Y; + var dist = direction == PushDirection.Down ? vy - iy : iy - vy; if (dist > Tolerance.Epsilon) return dist; - if (dist >= -Tolerance.Epsilon) return 0; // touching - return double.MaxValue; // edge is behind vertex + if (dist >= -Tolerance.Epsilon) return 0; + return double.MaxValue; } default: @@ -991,6 +965,82 @@ namespace OpenNest return minDist; } + /// + /// Computes the minimum directional distance with the moving lines translated + /// by (movingDx, movingDy) without creating new Line objects. + /// + public static double DirectionalDistance( + List movingLines, double movingDx, double movingDy, + 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 ml = movingLines[i]; + var mx1 = ml.pt1.X + movingDx; + var my1 = ml.pt1.Y + movingDy; + var mx2 = ml.pt2.X + movingDx; + var my2 = ml.pt2.Y + movingDy; + + for (int j = 0; j < stationaryLines.Count; j++) + { + var se = stationaryLines[j]; + var d = RayEdgeDistance(mx1, my1, se.pt1.X, se.pt1.Y, se.pt2.X, se.pt2.Y, direction); + if (d < minDist) minDist = d; + + d = RayEdgeDistance(mx2, my2, se.pt1.X, se.pt1.Y, se.pt2.X, se.pt2.Y, 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 sl = stationaryLines[i]; + + for (int j = 0; j < movingLines.Count; j++) + { + var me = movingLines[j]; + var d = RayEdgeDistance( + sl.pt1.X, sl.pt1.Y, + me.pt1.X + movingDx, me.pt1.Y + movingDy, + me.pt2.X + movingDx, me.pt2.Y + movingDy, + opposite); + if (d < minDist) minDist = d; + + d = RayEdgeDistance( + sl.pt2.X, sl.pt2.Y, + me.pt1.X + movingDx, me.pt1.Y + movingDy, + me.pt2.X + movingDx, me.pt2.Y + movingDy, + opposite); + if (d < minDist) minDist = d; + } + } + + return minDist; + } + + /// + /// Packs line segments into a flat double array [x1,y1,x2,y2, ...] for GPU transfer. + /// + public static double[] FlattenLines(List lines) + { + var result = new double[lines.Count * 4]; + for (int i = 0; i < lines.Count; i++) + { + var line = lines[i]; + result[i * 4] = line.pt1.X; + result[i * 4 + 1] = line.pt1.Y; + result[i * 4 + 2] = line.pt2.X; + result[i * 4 + 3] = line.pt2.Y; + } + return result; + } + public static PushDirection OppositeDirection(PushDirection direction) { switch (direction)