diff --git a/OpenNest.Core/Geometry/SpatialQuery.cs b/OpenNest.Core/Geometry/SpatialQuery.cs
new file mode 100644
index 0000000..dca54cf
--- /dev/null
+++ b/OpenNest.Core/Geometry/SpatialQuery.cs
@@ -0,0 +1,614 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using OpenNest.Math;
+
+namespace OpenNest.Geometry
+{
+ public static class SpatialQuery
+ {
+ ///
+ /// 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)
+ {
+ 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:
+ case PushDirection.Right:
+ {
+ var dy = p2y - p1y;
+ if (System.Math.Abs(dy) < Tolerance.Epsilon)
+ return double.MaxValue;
+
+ 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 = direction == PushDirection.Left ? vx - ix : ix - vx;
+
+ if (dist > Tolerance.Epsilon) return dist;
+ if (dist >= -Tolerance.Epsilon) return 0;
+ return double.MaxValue;
+ }
+
+ case PushDirection.Down:
+ case PushDirection.Up:
+ {
+ var dx = p2x - p1x;
+ if (System.Math.Abs(dx) < Tolerance.Epsilon)
+ return double.MaxValue;
+
+ 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 = direction == PushDirection.Down ? vy - iy : iy - vy;
+
+ if (dist > Tolerance.Epsilon) return dist;
+ if (dist >= -Tolerance.Epsilon) return 0;
+ return 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
+ var movingVertices = new HashSet();
+ for (int i = 0; i < movingLines.Count; i++)
+ {
+ movingVertices.Add(movingLines[i].pt1);
+ movingVertices.Add(movingLines[i].pt2);
+ }
+
+ var stationaryEdges = new (Vector start, Vector end)[stationaryLines.Count];
+ for (int i = 0; i < stationaryLines.Count; i++)
+ stationaryEdges[i] = (stationaryLines[i].pt1, stationaryLines[i].pt2);
+
+ // Sort edges for pruning if not already sorted (usually they aren't here)
+ if (direction == PushDirection.Left || direction == PushDirection.Right)
+ stationaryEdges = stationaryEdges.OrderBy(e => System.Math.Min(e.start.Y, e.end.Y)).ToArray();
+ else
+ stationaryEdges = stationaryEdges.OrderBy(e => System.Math.Min(e.start.X, e.end.X)).ToArray();
+
+ foreach (var mv in movingVertices)
+ {
+ var d = OneWayDistance(mv, stationaryEdges, Vector.Zero, direction);
+ if (d < minDist) minDist = d;
+ }
+
+ // Case 2: Each stationary vertex -> each moving edge (opposite direction)
+ var opposite = OppositeDirection(direction);
+ var stationaryVertices = new HashSet();
+ for (int i = 0; i < stationaryLines.Count; i++)
+ {
+ stationaryVertices.Add(stationaryLines[i].pt1);
+ stationaryVertices.Add(stationaryLines[i].pt2);
+ }
+
+ var movingEdges = new (Vector start, Vector end)[movingLines.Count];
+ for (int i = 0; i < movingLines.Count; i++)
+ movingEdges[i] = (movingLines[i].pt1, movingLines[i].pt2);
+
+ if (opposite == PushDirection.Left || opposite == PushDirection.Right)
+ movingEdges = movingEdges.OrderBy(e => System.Math.Min(e.start.Y, e.end.Y)).ToArray();
+ else
+ movingEdges = movingEdges.OrderBy(e => System.Math.Min(e.start.X, e.end.X)).ToArray();
+
+ foreach (var sv in stationaryVertices)
+ {
+ var d = OneWayDistance(sv, movingEdges, Vector.Zero, opposite);
+ if (d < minDist) minDist = d;
+ }
+
+ 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;
+ var movingOffset = new Vector(movingDx, movingDy);
+
+ // Case 1: Each moving vertex -> each stationary edge
+ var movingVertices = new HashSet();
+ for (int i = 0; i < movingLines.Count; i++)
+ {
+ movingVertices.Add(movingLines[i].pt1 + movingOffset);
+ movingVertices.Add(movingLines[i].pt2 + movingOffset);
+ }
+
+ var stationaryEdges = new (Vector start, Vector end)[stationaryLines.Count];
+ for (int i = 0; i < stationaryLines.Count; i++)
+ stationaryEdges[i] = (stationaryLines[i].pt1, stationaryLines[i].pt2);
+
+ if (direction == PushDirection.Left || direction == PushDirection.Right)
+ stationaryEdges = stationaryEdges.OrderBy(e => System.Math.Min(e.start.Y, e.end.Y)).ToArray();
+ else
+ stationaryEdges = stationaryEdges.OrderBy(e => System.Math.Min(e.start.X, e.end.X)).ToArray();
+
+ foreach (var mv in movingVertices)
+ {
+ var d = OneWayDistance(mv, stationaryEdges, Vector.Zero, direction);
+ if (d < minDist) minDist = d;
+ }
+
+ // Case 2: Each stationary vertex -> each moving edge (opposite direction)
+ var opposite = OppositeDirection(direction);
+ var stationaryVertices = new HashSet();
+ for (int i = 0; i < stationaryLines.Count; i++)
+ {
+ stationaryVertices.Add(stationaryLines[i].pt1);
+ stationaryVertices.Add(stationaryLines[i].pt2);
+ }
+
+ var movingEdges = new (Vector start, Vector end)[movingLines.Count];
+ for (int i = 0; i < movingLines.Count; i++)
+ movingEdges[i] = (movingLines[i].pt1, movingLines[i].pt2);
+
+ if (opposite == PushDirection.Left || opposite == PushDirection.Right)
+ movingEdges = movingEdges.OrderBy(e => System.Math.Min(e.start.Y, e.end.Y)).ToArray();
+ else
+ movingEdges = movingEdges.OrderBy(e => System.Math.Min(e.start.X, e.end.X)).ToArray();
+
+ foreach (var sv in stationaryVertices)
+ {
+ var d = OneWayDistance(sv, movingEdges, movingOffset, 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;
+ }
+
+ ///
+ /// Computes the minimum directional distance using raw edge arrays and location offsets
+ /// to avoid all intermediate object allocations.
+ ///
+ public static double DirectionalDistance(
+ (Vector start, Vector end)[] movingEdges, Vector movingOffset,
+ (Vector start, Vector end)[] stationaryEdges, Vector stationaryOffset,
+ PushDirection direction)
+ {
+ var minDist = double.MaxValue;
+
+ // Extract unique vertices from moving edges.
+ var movingVertices = new HashSet();
+ for (var i = 0; i < movingEdges.Length; i++)
+ {
+ movingVertices.Add(movingEdges[i].start + movingOffset);
+ movingVertices.Add(movingEdges[i].end + movingOffset);
+ }
+
+ // Case 1: Each moving vertex -> each stationary edge
+ foreach (var mv in movingVertices)
+ {
+ var d = OneWayDistance(mv, stationaryEdges, stationaryOffset, direction);
+ if (d < minDist) minDist = d;
+ }
+
+ // Case 2: Each stationary vertex -> each moving edge (opposite direction)
+ var opposite = OppositeDirection(direction);
+ var stationaryVertices = new HashSet();
+ for (var i = 0; i < stationaryEdges.Length; i++)
+ {
+ stationaryVertices.Add(stationaryEdges[i].start + stationaryOffset);
+ stationaryVertices.Add(stationaryEdges[i].end + stationaryOffset);
+ }
+
+ foreach (var sv in stationaryVertices)
+ {
+ var d = OneWayDistance(sv, movingEdges, movingOffset, opposite);
+ if (d < minDist) minDist = d;
+ }
+
+ return minDist;
+ }
+
+ public static double OneWayDistance(
+ Vector vertex, (Vector start, Vector end)[] edges, Vector edgeOffset,
+ PushDirection direction)
+ {
+ var minDist = double.MaxValue;
+ var vx = vertex.X;
+ var vy = vertex.Y;
+
+ // Pruning: edges are sorted by their perpendicular min-coordinate in PartBoundary.
+ if (direction == PushDirection.Left || direction == PushDirection.Right)
+ {
+ for (var i = 0; i < edges.Length; i++)
+ {
+ var e1 = edges[i].start + edgeOffset;
+ var e2 = edges[i].end + edgeOffset;
+
+ var minY = e1.Y < e2.Y ? e1.Y : e2.Y;
+ var maxY = e1.Y > e2.Y ? e1.Y : e2.Y;
+
+ // Since edges are sorted by minY, if vy < minY, then vy < all subsequent minY.
+ if (vy < minY - Tolerance.Epsilon)
+ break;
+
+ if (vy > maxY + Tolerance.Epsilon)
+ continue;
+
+ var d = RayEdgeDistance(vx, vy, e1.X, e1.Y, e2.X, e2.Y, direction);
+ if (d < minDist) minDist = d;
+ }
+ }
+ else // Up/Down
+ {
+ for (var i = 0; i < edges.Length; i++)
+ {
+ var e1 = edges[i].start + edgeOffset;
+ var e2 = edges[i].end + edgeOffset;
+
+ var minX = e1.X < e2.X ? e1.X : e2.X;
+ var maxX = e1.X > e2.X ? e1.X : e2.X;
+
+ // Since edges are sorted by minX, if vx < minX, then vx < all subsequent minX.
+ if (vx < minX - Tolerance.Epsilon)
+ break;
+
+ if (vx > maxX + Tolerance.Epsilon)
+ continue;
+
+ var d = RayEdgeDistance(vx, vy, e1.X, e1.Y, e2.X, e2.Y, direction);
+ if (d < minDist) minDist = d;
+ }
+ }
+
+ return minDist;
+ }
+
+ public 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 bool IsHorizontalDirection(PushDirection direction)
+ {
+ return direction is PushDirection.Left or PushDirection.Right;
+ }
+
+ public static double EdgeDistance(Box box, Box boundary, PushDirection direction)
+ {
+ switch (direction)
+ {
+ case PushDirection.Left: return box.Left - boundary.Left;
+ case PushDirection.Right: return boundary.Right - box.Right;
+ case PushDirection.Up: return boundary.Top - box.Top;
+ case PushDirection.Down: return box.Bottom - boundary.Bottom;
+ default: return double.MaxValue;
+ }
+ }
+
+ public static Vector DirectionToOffset(PushDirection direction, double distance)
+ {
+ switch (direction)
+ {
+ case PushDirection.Left: return new Vector(-distance, 0);
+ case PushDirection.Right: return new Vector(distance, 0);
+ case PushDirection.Up: return new Vector(0, distance);
+ case PushDirection.Down: return new Vector(0, -distance);
+ default: return new Vector();
+ }
+ }
+
+ public static double DirectionalGap(Box from, Box to, PushDirection direction)
+ {
+ switch (direction)
+ {
+ case PushDirection.Left: return from.Left - to.Right;
+ case PushDirection.Right: return to.Left - from.Right;
+ case PushDirection.Up: return to.Bottom - from.Top;
+ case PushDirection.Down: return from.Bottom - to.Top;
+ default: return double.MaxValue;
+ }
+ }
+
+ public static double ClosestDistanceLeft(Box box, List boxes)
+ {
+ var closestDistance = double.MaxValue;
+
+ for (int i = 0; i < boxes.Count; i++)
+ {
+ var compareBox = boxes[i];
+
+ RelativePosition pos;
+
+ if (!box.IsHorizontalTo(compareBox, out pos))
+ continue;
+
+ if (pos != RelativePosition.Right)
+ continue;
+
+ var distance = box.Left - compareBox.Right;
+
+ if (distance < closestDistance)
+ closestDistance = distance;
+ }
+
+ return closestDistance == double.MaxValue ? double.NaN : closestDistance;
+ }
+
+ public static double ClosestDistanceRight(Box box, List boxes)
+ {
+ var closestDistance = double.MaxValue;
+
+ for (int i = 0; i < boxes.Count; i++)
+ {
+ var compareBox = boxes[i];
+
+ RelativePosition pos;
+
+ if (!box.IsHorizontalTo(compareBox, out pos))
+ continue;
+
+ if (pos != RelativePosition.Left)
+ continue;
+
+ var distance = compareBox.Left - box.Right;
+
+ if (distance < closestDistance)
+ closestDistance = distance;
+ }
+
+ return closestDistance == double.MaxValue ? double.NaN : closestDistance;
+ }
+
+ public static double ClosestDistanceUp(Box box, List boxes)
+ {
+ var closestDistance = double.MaxValue;
+
+ for (int i = 0; i < boxes.Count; i++)
+ {
+ var compareBox = boxes[i];
+
+ RelativePosition pos;
+
+ if (!box.IsVerticalTo(compareBox, out pos))
+ continue;
+
+ if (pos != RelativePosition.Bottom)
+ continue;
+
+ var distance = compareBox.Bottom - box.Top;
+
+ if (distance < closestDistance)
+ closestDistance = distance;
+ }
+
+ return closestDistance == double.MaxValue ? double.NaN : closestDistance;
+ }
+
+ public static double ClosestDistanceDown(Box box, List boxes)
+ {
+ var closestDistance = double.MaxValue;
+
+ for (int i = 0; i < boxes.Count; i++)
+ {
+ var compareBox = boxes[i];
+
+ RelativePosition pos;
+
+ if (!box.IsVerticalTo(compareBox, out pos))
+ continue;
+
+ if (pos != RelativePosition.Top)
+ continue;
+
+ var distance = box.Bottom - compareBox.Top;
+
+ if (distance < closestDistance)
+ closestDistance = distance;
+ }
+
+ return closestDistance == double.MaxValue ? double.NaN : closestDistance;
+ }
+
+ public static Box GetLargestBoxVertically(Vector pt, Box bounds, IEnumerable boxes)
+ {
+ var verticalBoxes = boxes.Where(b => !(b.Left > pt.X || b.Right < pt.X)).ToList();
+
+ #region Find Top/Bottom Limits
+
+ var top = double.MaxValue;
+ var btm = double.MinValue;
+
+ foreach (var box in verticalBoxes)
+ {
+ var boxBtm = box.Bottom;
+ var boxTop = box.Top;
+
+ if (boxBtm > pt.Y && boxBtm < top)
+ top = boxBtm;
+
+ else if (box.Top < pt.Y && boxTop > btm)
+ btm = boxTop;
+ }
+
+ if (top == double.MaxValue)
+ {
+ if (bounds.Top > pt.Y)
+ top = bounds.Top;
+ else return Box.Empty;
+ }
+
+ if (btm == double.MinValue)
+ {
+ if (bounds.Bottom < pt.Y)
+ btm = bounds.Bottom;
+ else return Box.Empty;
+ }
+
+ #endregion
+
+ var horizontalBoxes = boxes.Where(b => !(b.Bottom >= top || b.Top <= btm)).ToList();
+
+ #region Find Left/Right Limits
+
+ var lft = double.MinValue;
+ var rgt = double.MaxValue;
+
+ foreach (var box in horizontalBoxes)
+ {
+ var boxLft = box.Left;
+ var boxRgt = box.Right;
+
+ if (boxLft > pt.X && boxLft < rgt)
+ rgt = boxLft;
+
+ else if (boxRgt < pt.X && boxRgt > lft)
+ lft = boxRgt;
+ }
+
+ if (rgt == double.MaxValue)
+ {
+ if (bounds.Right > pt.X)
+ rgt = bounds.Right;
+ else return Box.Empty;
+ }
+
+ if (lft == double.MinValue)
+ {
+ if (bounds.Left < pt.X)
+ lft = bounds.Left;
+ else return Box.Empty;
+ }
+
+ #endregion
+
+ return new Box(lft, btm, rgt - lft, top - btm);
+ }
+
+ public static Box GetLargestBoxHorizontally(Vector pt, Box bounds, IEnumerable boxes)
+ {
+ var horizontalBoxes = boxes.Where(b => !(b.Bottom > pt.Y || b.Top < pt.Y)).ToList();
+
+ #region Find Left/Right Limits
+
+ var lft = double.MinValue;
+ var rgt = double.MaxValue;
+
+ foreach (var box in horizontalBoxes)
+ {
+ var boxLft = box.Left;
+ var boxRgt = box.Right;
+
+ if (boxLft > pt.X && boxLft < rgt)
+ rgt = boxLft;
+
+ else if (boxRgt < pt.X && boxRgt > lft)
+ lft = boxRgt;
+ }
+
+ if (rgt == double.MaxValue)
+ {
+ if (bounds.Right > pt.X)
+ rgt = bounds.Right;
+ else return Box.Empty;
+ }
+
+ if (lft == double.MinValue)
+ {
+ if (bounds.Left < pt.X)
+ lft = bounds.Left;
+ else return Box.Empty;
+ }
+
+ #endregion
+
+ var verticalBoxes = boxes.Where(b => !(b.Left >= rgt || b.Right <= lft)).ToList();
+
+ #region Find Top/Bottom Limits
+
+ var top = double.MaxValue;
+ var btm = double.MinValue;
+
+ foreach (var box in verticalBoxes)
+ {
+ var boxBtm = box.Bottom;
+ var boxTop = box.Top;
+
+ if (boxBtm > pt.Y && boxBtm < top)
+ top = boxBtm;
+
+ else if (box.Top < pt.Y && boxTop > btm)
+ btm = boxTop;
+ }
+
+ if (top == double.MaxValue)
+ {
+ if (bounds.Top > pt.Y)
+ top = bounds.Top;
+ else return Box.Empty;
+ }
+
+ if (btm == double.MinValue)
+ {
+ if (bounds.Bottom < pt.Y)
+ btm = bounds.Bottom;
+ else return Box.Empty;
+ }
+
+ #endregion
+
+ return new Box(lft, btm, rgt - lft, top - btm);
+ }
+ }
+}
diff --git a/OpenNest.Core/Helper.cs b/OpenNest.Core/Helper.cs
index a41b50f..fd09a71 100644
--- a/OpenNest.Core/Helper.cs
+++ b/OpenNest.Core/Helper.cs
@@ -1,618 +1,6 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Threading.Tasks;
-using OpenNest.Converters;
-using OpenNest.Geometry;
-using OpenNest.Math;
-
namespace OpenNest
{
public static class Helper
{
- ///
- /// 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)
- {
- 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:
- case PushDirection.Right:
- {
- var dy = p2y - p1y;
- if (System.Math.Abs(dy) < Tolerance.Epsilon)
- return double.MaxValue;
-
- 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 = direction == PushDirection.Left ? vx - ix : ix - vx;
-
- if (dist > Tolerance.Epsilon) return dist;
- if (dist >= -Tolerance.Epsilon) return 0;
- return double.MaxValue;
- }
-
- case PushDirection.Down:
- case PushDirection.Up:
- {
- var dx = p2x - p1x;
- if (System.Math.Abs(dx) < Tolerance.Epsilon)
- return double.MaxValue;
-
- 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 = direction == PushDirection.Down ? vy - iy : iy - vy;
-
- if (dist > Tolerance.Epsilon) return dist;
- if (dist >= -Tolerance.Epsilon) return 0;
- return 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
- var movingVertices = new HashSet();
- for (int i = 0; i < movingLines.Count; i++)
- {
- movingVertices.Add(movingLines[i].pt1);
- movingVertices.Add(movingLines[i].pt2);
- }
-
- var stationaryEdges = new (Vector start, Vector end)[stationaryLines.Count];
- for (int i = 0; i < stationaryLines.Count; i++)
- stationaryEdges[i] = (stationaryLines[i].pt1, stationaryLines[i].pt2);
-
- // Sort edges for pruning if not already sorted (usually they aren't here)
- if (direction == PushDirection.Left || direction == PushDirection.Right)
- stationaryEdges = stationaryEdges.OrderBy(e => System.Math.Min(e.start.Y, e.end.Y)).ToArray();
- else
- stationaryEdges = stationaryEdges.OrderBy(e => System.Math.Min(e.start.X, e.end.X)).ToArray();
-
- foreach (var mv in movingVertices)
- {
- var d = OneWayDistance(mv, stationaryEdges, Vector.Zero, direction);
- if (d < minDist) minDist = d;
- }
-
- // Case 2: Each stationary vertex -> each moving edge (opposite direction)
- var opposite = OppositeDirection(direction);
- var stationaryVertices = new HashSet();
- for (int i = 0; i < stationaryLines.Count; i++)
- {
- stationaryVertices.Add(stationaryLines[i].pt1);
- stationaryVertices.Add(stationaryLines[i].pt2);
- }
-
- var movingEdges = new (Vector start, Vector end)[movingLines.Count];
- for (int i = 0; i < movingLines.Count; i++)
- movingEdges[i] = (movingLines[i].pt1, movingLines[i].pt2);
-
- if (opposite == PushDirection.Left || opposite == PushDirection.Right)
- movingEdges = movingEdges.OrderBy(e => System.Math.Min(e.start.Y, e.end.Y)).ToArray();
- else
- movingEdges = movingEdges.OrderBy(e => System.Math.Min(e.start.X, e.end.X)).ToArray();
-
- foreach (var sv in stationaryVertices)
- {
- var d = OneWayDistance(sv, movingEdges, Vector.Zero, opposite);
- if (d < minDist) minDist = d;
- }
-
- 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;
- var movingOffset = new Vector(movingDx, movingDy);
-
- // Case 1: Each moving vertex -> each stationary edge
- var movingVertices = new HashSet();
- for (int i = 0; i < movingLines.Count; i++)
- {
- movingVertices.Add(movingLines[i].pt1 + movingOffset);
- movingVertices.Add(movingLines[i].pt2 + movingOffset);
- }
-
- var stationaryEdges = new (Vector start, Vector end)[stationaryLines.Count];
- for (int i = 0; i < stationaryLines.Count; i++)
- stationaryEdges[i] = (stationaryLines[i].pt1, stationaryLines[i].pt2);
-
- if (direction == PushDirection.Left || direction == PushDirection.Right)
- stationaryEdges = stationaryEdges.OrderBy(e => System.Math.Min(e.start.Y, e.end.Y)).ToArray();
- else
- stationaryEdges = stationaryEdges.OrderBy(e => System.Math.Min(e.start.X, e.end.X)).ToArray();
-
- foreach (var mv in movingVertices)
- {
- var d = OneWayDistance(mv, stationaryEdges, Vector.Zero, direction);
- if (d < minDist) minDist = d;
- }
-
- // Case 2: Each stationary vertex -> each moving edge (opposite direction)
- var opposite = OppositeDirection(direction);
- var stationaryVertices = new HashSet();
- for (int i = 0; i < stationaryLines.Count; i++)
- {
- stationaryVertices.Add(stationaryLines[i].pt1);
- stationaryVertices.Add(stationaryLines[i].pt2);
- }
-
- var movingEdges = new (Vector start, Vector end)[movingLines.Count];
- for (int i = 0; i < movingLines.Count; i++)
- movingEdges[i] = (movingLines[i].pt1, movingLines[i].pt2);
-
- if (opposite == PushDirection.Left || opposite == PushDirection.Right)
- movingEdges = movingEdges.OrderBy(e => System.Math.Min(e.start.Y, e.end.Y)).ToArray();
- else
- movingEdges = movingEdges.OrderBy(e => System.Math.Min(e.start.X, e.end.X)).ToArray();
-
- foreach (var sv in stationaryVertices)
- {
- var d = OneWayDistance(sv, movingEdges, movingOffset, 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;
- }
-
- ///
- /// Computes the minimum directional distance using raw edge arrays and location offsets
- /// to avoid all intermediate object allocations.
- ///
- public static double DirectionalDistance(
- (Vector start, Vector end)[] movingEdges, Vector movingOffset,
- (Vector start, Vector end)[] stationaryEdges, Vector stationaryOffset,
- PushDirection direction)
- {
- var minDist = double.MaxValue;
-
- // Extract unique vertices from moving edges.
- var movingVertices = new HashSet();
- for (var i = 0; i < movingEdges.Length; i++)
- {
- movingVertices.Add(movingEdges[i].start + movingOffset);
- movingVertices.Add(movingEdges[i].end + movingOffset);
- }
-
- // Case 1: Each moving vertex -> each stationary edge
- foreach (var mv in movingVertices)
- {
- var d = OneWayDistance(mv, stationaryEdges, stationaryOffset, direction);
- if (d < minDist) minDist = d;
- }
-
- // Case 2: Each stationary vertex -> each moving edge (opposite direction)
- var opposite = OppositeDirection(direction);
- var stationaryVertices = new HashSet();
- for (var i = 0; i < stationaryEdges.Length; i++)
- {
- stationaryVertices.Add(stationaryEdges[i].start + stationaryOffset);
- stationaryVertices.Add(stationaryEdges[i].end + stationaryOffset);
- }
-
- foreach (var sv in stationaryVertices)
- {
- var d = OneWayDistance(sv, movingEdges, movingOffset, opposite);
- if (d < minDist) minDist = d;
- }
-
- return minDist;
- }
-
- public static double OneWayDistance(
- Vector vertex, (Vector start, Vector end)[] edges, Vector edgeOffset,
- PushDirection direction)
- {
- var minDist = double.MaxValue;
- var vx = vertex.X;
- var vy = vertex.Y;
-
- // Pruning: edges are sorted by their perpendicular min-coordinate in PartBoundary.
- if (direction == PushDirection.Left || direction == PushDirection.Right)
- {
- for (var i = 0; i < edges.Length; i++)
- {
- var e1 = edges[i].start + edgeOffset;
- var e2 = edges[i].end + edgeOffset;
-
- var minY = e1.Y < e2.Y ? e1.Y : e2.Y;
- var maxY = e1.Y > e2.Y ? e1.Y : e2.Y;
-
- // Since edges are sorted by minY, if vy < minY, then vy < all subsequent minY.
- if (vy < minY - Tolerance.Epsilon)
- break;
-
- if (vy > maxY + Tolerance.Epsilon)
- continue;
-
- var d = RayEdgeDistance(vx, vy, e1.X, e1.Y, e2.X, e2.Y, direction);
- if (d < minDist) minDist = d;
- }
- }
- else // Up/Down
- {
- for (var i = 0; i < edges.Length; i++)
- {
- var e1 = edges[i].start + edgeOffset;
- var e2 = edges[i].end + edgeOffset;
-
- var minX = e1.X < e2.X ? e1.X : e2.X;
- var maxX = e1.X > e2.X ? e1.X : e2.X;
-
- // Since edges are sorted by minX, if vx < minX, then vx < all subsequent minX.
- if (vx < minX - Tolerance.Epsilon)
- break;
-
- if (vx > maxX + Tolerance.Epsilon)
- continue;
-
- var d = RayEdgeDistance(vx, vy, e1.X, e1.Y, e2.X, e2.Y, direction);
- if (d < minDist) minDist = d;
- }
- }
-
- return minDist;
- }
-
- public 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 bool IsHorizontalDirection(PushDirection direction)
- {
- return direction is PushDirection.Left or PushDirection.Right;
- }
-
- public static double EdgeDistance(Box box, Box boundary, PushDirection direction)
- {
- switch (direction)
- {
- case PushDirection.Left: return box.Left - boundary.Left;
- case PushDirection.Right: return boundary.Right - box.Right;
- case PushDirection.Up: return boundary.Top - box.Top;
- case PushDirection.Down: return box.Bottom - boundary.Bottom;
- default: return double.MaxValue;
- }
- }
-
- public static Vector DirectionToOffset(PushDirection direction, double distance)
- {
- switch (direction)
- {
- case PushDirection.Left: return new Vector(-distance, 0);
- case PushDirection.Right: return new Vector(distance, 0);
- case PushDirection.Up: return new Vector(0, distance);
- case PushDirection.Down: return new Vector(0, -distance);
- default: return new Vector();
- }
- }
-
- public static double DirectionalGap(Box from, Box to, PushDirection direction)
- {
- switch (direction)
- {
- case PushDirection.Left: return from.Left - to.Right;
- case PushDirection.Right: return to.Left - from.Right;
- case PushDirection.Up: return to.Bottom - from.Top;
- case PushDirection.Down: return from.Bottom - to.Top;
- default: return double.MaxValue;
- }
- }
-
- public static double ClosestDistanceLeft(Box box, List boxes)
- {
- var closestDistance = double.MaxValue;
-
- for (int i = 0; i < boxes.Count; i++)
- {
- var compareBox = boxes[i];
-
- RelativePosition pos;
-
- if (!box.IsHorizontalTo(compareBox, out pos))
- continue;
-
- if (pos != RelativePosition.Right)
- continue;
-
- var distance = box.Left - compareBox.Right;
-
- if (distance < closestDistance)
- closestDistance = distance;
- }
-
- return closestDistance == double.MaxValue ? double.NaN : closestDistance;
- }
-
- public static double ClosestDistanceRight(Box box, List boxes)
- {
- var closestDistance = double.MaxValue;
-
- for (int i = 0; i < boxes.Count; i++)
- {
- var compareBox = boxes[i];
-
- RelativePosition pos;
-
- if (!box.IsHorizontalTo(compareBox, out pos))
- continue;
-
- if (pos != RelativePosition.Left)
- continue;
-
- var distance = compareBox.Left - box.Right;
-
- if (distance < closestDistance)
- closestDistance = distance;
- }
-
- return closestDistance == double.MaxValue ? double.NaN : closestDistance;
- }
-
- public static double ClosestDistanceUp(Box box, List boxes)
- {
- var closestDistance = double.MaxValue;
-
- for (int i = 0; i < boxes.Count; i++)
- {
- var compareBox = boxes[i];
-
- RelativePosition pos;
-
- if (!box.IsVerticalTo(compareBox, out pos))
- continue;
-
- if (pos != RelativePosition.Bottom)
- continue;
-
- var distance = compareBox.Bottom - box.Top;
-
- if (distance < closestDistance)
- closestDistance = distance;
- }
-
- return closestDistance == double.MaxValue ? double.NaN : closestDistance;
- }
-
- public static double ClosestDistanceDown(Box box, List boxes)
- {
- var closestDistance = double.MaxValue;
-
- for (int i = 0; i < boxes.Count; i++)
- {
- var compareBox = boxes[i];
-
- RelativePosition pos;
-
- if (!box.IsVerticalTo(compareBox, out pos))
- continue;
-
- if (pos != RelativePosition.Top)
- continue;
-
- var distance = box.Bottom - compareBox.Top;
-
- if (distance < closestDistance)
- closestDistance = distance;
- }
-
- return closestDistance == double.MaxValue ? double.NaN : closestDistance;
- }
-
- public static Box GetLargestBoxVertically(Vector pt, Box bounds, IEnumerable boxes)
- {
- var verticalBoxes = boxes.Where(b => !(b.Left > pt.X || b.Right < pt.X)).ToList();
-
- #region Find Top/Bottom Limits
-
- var top = double.MaxValue;
- var btm = double.MinValue;
-
- foreach (var box in verticalBoxes)
- {
- var boxBtm = box.Bottom;
- var boxTop = box.Top;
-
- if (boxBtm > pt.Y && boxBtm < top)
- top = boxBtm;
-
- else if (box.Top < pt.Y && boxTop > btm)
- btm = boxTop;
- }
-
- if (top == double.MaxValue)
- {
- if (bounds.Top > pt.Y)
- top = bounds.Top;
- else return Box.Empty;
- }
-
- if (btm == double.MinValue)
- {
- if (bounds.Bottom < pt.Y)
- btm = bounds.Bottom;
- else return Box.Empty;
- }
-
- #endregion
-
- var horizontalBoxes = boxes.Where(b => !(b.Bottom >= top || b.Top <= btm)).ToList();
-
- #region Find Left/Right Limits
-
- var lft = double.MinValue;
- var rgt = double.MaxValue;
-
- foreach (var box in horizontalBoxes)
- {
- var boxLft = box.Left;
- var boxRgt = box.Right;
-
- if (boxLft > pt.X && boxLft < rgt)
- rgt = boxLft;
-
- else if (boxRgt < pt.X && boxRgt > lft)
- lft = boxRgt;
- }
-
- if (rgt == double.MaxValue)
- {
- if (bounds.Right > pt.X)
- rgt = bounds.Right;
- else return Box.Empty;
- }
-
- if (lft == double.MinValue)
- {
- if (bounds.Left < pt.X)
- lft = bounds.Left;
- else return Box.Empty;
- }
-
- #endregion
-
- return new Box(lft, btm, rgt - lft, top - btm);
- }
-
- public static Box GetLargestBoxHorizontally(Vector pt, Box bounds, IEnumerable boxes)
- {
- var horizontalBoxes = boxes.Where(b => !(b.Bottom > pt.Y || b.Top < pt.Y)).ToList();
-
- #region Find Left/Right Limits
-
- var lft = double.MinValue;
- var rgt = double.MaxValue;
-
- foreach (var box in horizontalBoxes)
- {
- var boxLft = box.Left;
- var boxRgt = box.Right;
-
- if (boxLft > pt.X && boxLft < rgt)
- rgt = boxLft;
-
- else if (boxRgt < pt.X && boxRgt > lft)
- lft = boxRgt;
- }
-
- if (rgt == double.MaxValue)
- {
- if (bounds.Right > pt.X)
- rgt = bounds.Right;
- else return Box.Empty;
- }
-
- if (lft == double.MinValue)
- {
- if (bounds.Left < pt.X)
- lft = bounds.Left;
- else return Box.Empty;
- }
-
- #endregion
-
- var verticalBoxes = boxes.Where(b => !(b.Left >= rgt || b.Right <= lft)).ToList();
-
- #region Find Top/Bottom Limits
-
- var top = double.MaxValue;
- var btm = double.MinValue;
-
- foreach (var box in verticalBoxes)
- {
- var boxBtm = box.Bottom;
- var boxTop = box.Top;
-
- if (boxBtm > pt.Y && boxBtm < top)
- top = boxBtm;
-
- else if (box.Top < pt.Y && boxTop > btm)
- btm = boxTop;
- }
-
- if (top == double.MaxValue)
- {
- if (bounds.Top > pt.Y)
- top = bounds.Top;
- else return Box.Empty;
- }
-
- if (btm == double.MinValue)
- {
- if (bounds.Bottom < pt.Y)
- btm = bounds.Bottom;
- else return Box.Empty;
- }
-
- #endregion
-
- return new Box(lft, btm, rgt - lft, top - btm);
- }
}
}
diff --git a/OpenNest.Engine/BestFit/RotationSlideStrategy.cs b/OpenNest.Engine/BestFit/RotationSlideStrategy.cs
index 26a6db4..9308ec3 100644
--- a/OpenNest.Engine/BestFit/RotationSlideStrategy.cs
+++ b/OpenNest.Engine/BestFit/RotationSlideStrategy.cs
@@ -128,8 +128,8 @@ namespace OpenNest.Engine.BestFit
if (_slideComputer != null)
{
- var stationarySegments = Helper.FlattenLines(part1Lines);
- var movingSegments = Helper.FlattenLines(part2TemplateLines);
+ var stationarySegments = SpatialQuery.FlattenLines(part1Lines);
+ var movingSegments = SpatialQuery.FlattenLines(part2TemplateLines);
var offsets = new double[count * 2];
var directions = new int[count];
@@ -182,7 +182,7 @@ namespace OpenNest.Engine.BestFit
sEdges = sEdges.OrderBy(e => System.Math.Min(e.start.X, e.end.X)).ToArray();
stationaryEdgesByDir[dir] = sEdges;
- var opposite = Helper.OppositeDirection(dir);
+ var opposite = SpatialQuery.OppositeDirection(dir);
var mEdges = new (Vector start, Vector end)[part2TemplateLines.Count];
for (var i = 0; i < part2TemplateLines.Count; i++)
mEdges[i] = (part2TemplateLines[i].StartPoint, part2TemplateLines[i].EndPoint);
@@ -204,21 +204,21 @@ namespace OpenNest.Engine.BestFit
var sEdges = stationaryEdgesByDir[dir];
var mEdges = movingEdgesByDir[dir];
- var opposite = Helper.OppositeDirection(dir);
+ var opposite = SpatialQuery.OppositeDirection(dir);
var minDist = double.MaxValue;
// Case 1: Moving vertices -> Stationary edges
foreach (var mv in movingVerticesArray)
{
- var d = Helper.OneWayDistance(mv + movingOffset, sEdges, Vector.Zero, dir);
+ var d = SpatialQuery.OneWayDistance(mv + movingOffset, sEdges, Vector.Zero, dir);
if (d < minDist) minDist = d;
}
// Case 2: Stationary vertices -> Moving edges (translated)
foreach (var sv in stationaryVerticesArray)
{
- var d = Helper.OneWayDistance(sv, mEdges, movingOffset, opposite);
+ var d = SpatialQuery.OneWayDistance(sv, mEdges, movingOffset, opposite);
if (d < minDist) minDist = d;
}
diff --git a/OpenNest.Engine/Compactor.cs b/OpenNest.Engine/Compactor.cs
index 364fb26..f7bc551 100644
--- a/OpenNest.Engine/Compactor.cs
+++ b/OpenNest.Engine/Compactor.cs
@@ -87,9 +87,9 @@ namespace OpenNest
for (var i = 0; i < obstacleParts.Count; i++)
obstacleBoxes[i] = obstacleParts[i].BoundingBox;
- var opposite = Helper.OppositeDirection(direction);
+ var opposite = SpatialQuery.OppositeDirection(direction);
var halfSpacing = plate.PartSpacing / 2;
- var isHorizontal = Helper.IsHorizontalDirection(direction);
+ var isHorizontal = SpatialQuery.IsHorizontalDirection(direction);
var workArea = plate.WorkArea();
var distance = double.MaxValue;
@@ -98,7 +98,7 @@ namespace OpenNest
foreach (var moving in movingParts)
{
- var edgeDist = Helper.EdgeDistance(moving.BoundingBox, workArea, direction);
+ var edgeDist = SpatialQuery.EdgeDistance(moving.BoundingBox, workArea, direction);
if (edgeDist <= 0)
distance = 0;
else if (edgeDist < distance)
@@ -113,11 +113,11 @@ namespace OpenNest
// behind the moving part. The forward gap (gap < 0) is unreliable for
// irregular shapes whose bounding boxes overlap even when the actual
// geometry still has a valid contact in the push direction.
- var reverseGap = Helper.DirectionalGap(movingBox, obstacleBoxes[i], opposite);
+ var reverseGap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], opposite);
if (reverseGap > 0)
continue;
- var gap = Helper.DirectionalGap(movingBox, obstacleBoxes[i], direction);
+ var gap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], direction);
if (gap >= distance)
continue;
@@ -136,7 +136,7 @@ namespace OpenNest
? PartGeometry.GetOffsetPartLines(obstacleParts[i], halfSpacing, opposite, ChordTolerance)
: PartGeometry.GetPartLines(obstacleParts[i], opposite, ChordTolerance);
- var d = Helper.DirectionalDistance(movingLines, obstacleLines[i], direction);
+ var d = SpatialQuery.DirectionalDistance(movingLines, obstacleLines[i], direction);
if (d < distance)
distance = d;
}
@@ -144,7 +144,7 @@ namespace OpenNest
if (distance < double.MaxValue && distance > 0)
{
- var offset = Helper.DirectionToOffset(direction, distance);
+ var offset = SpatialQuery.DirectionToOffset(direction, distance);
foreach (var moving in movingParts)
moving.Offset(offset);
return distance;
diff --git a/OpenNest.Engine/FillLinear.cs b/OpenNest.Engine/FillLinear.cs
index 09c0ead..92e6415 100644
--- a/OpenNest.Engine/FillLinear.cs
+++ b/OpenNest.Engine/FillLinear.cs
@@ -82,9 +82,9 @@ namespace OpenNest
var locationBOffset = MakeOffset(direction, bboxDim);
// Use the most efficient array-based overload to avoid all allocations.
- var slideDistance = Helper.DirectionalDistance(
+ var slideDistance = SpatialQuery.DirectionalDistance(
boundary.GetEdges(pushDir), partA.Location + locationBOffset,
- boundary.GetEdges(Helper.OppositeDirection(pushDir)), partA.Location,
+ boundary.GetEdges(SpatialQuery.OppositeDirection(pushDir)), partA.Location,
pushDir);
return ComputeCopyDistance(bboxDim, slideDistance);
@@ -103,7 +103,7 @@ namespace OpenNest
var bboxDim = GetDimension(patternA.BoundingBox, direction);
var pushDir = GetPushDirection(direction);
- var opposite = Helper.OppositeDirection(pushDir);
+ var opposite = SpatialQuery.OppositeDirection(pushDir);
// Compute a starting offset large enough that every part-pair in
// patternB has its offset geometry beyond patternA's offset geometry.
@@ -143,7 +143,7 @@ namespace OpenNest
for (var i = 0; i < patternA.Parts.Count; i++)
{
- var slideDistance = Helper.DirectionalDistance(
+ var slideDistance = SpatialQuery.DirectionalDistance(
movingEdges[j], locationB,
stationaryEdges[i], patternA.Parts[i].Location,
pushDir);
diff --git a/OpenNest/Actions/ActionClone.cs b/OpenNest/Actions/ActionClone.cs
index 9abb583..793c6eb 100644
--- a/OpenNest/Actions/ActionClone.cs
+++ b/OpenNest/Actions/ActionClone.cs
@@ -186,8 +186,8 @@ namespace OpenNest.Actions
boxes.Add(part.BoundingBox.Offset(plate.PartSpacing));
var pt = plateView.CurrentPoint;
- var vertical = Helper.GetLargestBoxVertically(pt, bounds, boxes);
- var horizontal = Helper.GetLargestBoxHorizontally(pt, bounds, boxes);
+ var vertical = SpatialQuery.GetLargestBoxVertically(pt, bounds, boxes);
+ var horizontal = SpatialQuery.GetLargestBoxHorizontally(pt, bounds, boxes);
var bestArea = vertical;
if (horizontal.Area() > vertical.Area())
diff --git a/OpenNest/Actions/ActionSelectArea.cs b/OpenNest/Actions/ActionSelectArea.cs
index d65c46b..77b3ff3 100644
--- a/OpenNest/Actions/ActionSelectArea.cs
+++ b/OpenNest/Actions/ActionSelectArea.cs
@@ -150,8 +150,8 @@ namespace OpenNest.Actions
private void UpdateSelectedArea()
{
SelectedArea = altSelect
- ? Helper.GetLargestBoxHorizontally(plateView.CurrentPoint, Bounds, boxes)
- : Helper.GetLargestBoxVertically(plateView.CurrentPoint, Bounds, boxes);
+ ? SpatialQuery.GetLargestBoxHorizontally(plateView.CurrentPoint, Bounds, boxes)
+ : SpatialQuery.GetLargestBoxVertically(plateView.CurrentPoint, Bounds, boxes);
plateView.Invalidate();
}