refactor: extract SpatialQuery from Helper
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
614
OpenNest.Core/Geometry/SpatialQuery.cs
Normal file
614
OpenNest.Core/Geometry/SpatialQuery.cs
Normal file
@@ -0,0 +1,614 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using OpenNest.Math;
|
||||||
|
|
||||||
|
namespace OpenNest.Geometry
|
||||||
|
{
|
||||||
|
public static class SpatialQuery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
public static double DirectionalDistance(List<Line> movingLines, List<Line> stationaryLines, PushDirection direction)
|
||||||
|
{
|
||||||
|
var minDist = double.MaxValue;
|
||||||
|
|
||||||
|
// Case 1: Each moving vertex -> each stationary edge
|
||||||
|
var movingVertices = new HashSet<Vector>();
|
||||||
|
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<Vector>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the minimum directional distance with the moving lines translated
|
||||||
|
/// by (movingDx, movingDy) without creating new Line objects.
|
||||||
|
/// </summary>
|
||||||
|
public static double DirectionalDistance(
|
||||||
|
List<Line> movingLines, double movingDx, double movingDy,
|
||||||
|
List<Line> 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<Vector>();
|
||||||
|
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<Vector>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Packs line segments into a flat double array [x1,y1,x2,y2, ...] for GPU transfer.
|
||||||
|
/// </summary>
|
||||||
|
public static double[] FlattenLines(List<Line> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the minimum directional distance using raw edge arrays and location offsets
|
||||||
|
/// to avoid all intermediate object allocations.
|
||||||
|
/// </summary>
|
||||||
|
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<Vector>();
|
||||||
|
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<Vector>();
|
||||||
|
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<Box> 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<Box> 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<Box> 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<Box> 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<Box> 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<Box> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
namespace OpenNest
|
||||||
{
|
{
|
||||||
public static class Helper
|
public static class Helper
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
public static double DirectionalDistance(List<Line> movingLines, List<Line> stationaryLines, PushDirection direction)
|
|
||||||
{
|
|
||||||
var minDist = double.MaxValue;
|
|
||||||
|
|
||||||
// Case 1: Each moving vertex -> each stationary edge
|
|
||||||
var movingVertices = new HashSet<Vector>();
|
|
||||||
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<Vector>();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Computes the minimum directional distance with the moving lines translated
|
|
||||||
/// by (movingDx, movingDy) without creating new Line objects.
|
|
||||||
/// </summary>
|
|
||||||
public static double DirectionalDistance(
|
|
||||||
List<Line> movingLines, double movingDx, double movingDy,
|
|
||||||
List<Line> 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<Vector>();
|
|
||||||
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<Vector>();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Packs line segments into a flat double array [x1,y1,x2,y2, ...] for GPU transfer.
|
|
||||||
/// </summary>
|
|
||||||
public static double[] FlattenLines(List<Line> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Computes the minimum directional distance using raw edge arrays and location offsets
|
|
||||||
/// to avoid all intermediate object allocations.
|
|
||||||
/// </summary>
|
|
||||||
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<Vector>();
|
|
||||||
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<Vector>();
|
|
||||||
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<Box> 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<Box> 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<Box> 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<Box> 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<Box> 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<Box> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,8 +128,8 @@ namespace OpenNest.Engine.BestFit
|
|||||||
|
|
||||||
if (_slideComputer != null)
|
if (_slideComputer != null)
|
||||||
{
|
{
|
||||||
var stationarySegments = Helper.FlattenLines(part1Lines);
|
var stationarySegments = SpatialQuery.FlattenLines(part1Lines);
|
||||||
var movingSegments = Helper.FlattenLines(part2TemplateLines);
|
var movingSegments = SpatialQuery.FlattenLines(part2TemplateLines);
|
||||||
var offsets = new double[count * 2];
|
var offsets = new double[count * 2];
|
||||||
var directions = new int[count];
|
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();
|
sEdges = sEdges.OrderBy(e => System.Math.Min(e.start.X, e.end.X)).ToArray();
|
||||||
stationaryEdgesByDir[dir] = sEdges;
|
stationaryEdgesByDir[dir] = sEdges;
|
||||||
|
|
||||||
var opposite = Helper.OppositeDirection(dir);
|
var opposite = SpatialQuery.OppositeDirection(dir);
|
||||||
var mEdges = new (Vector start, Vector end)[part2TemplateLines.Count];
|
var mEdges = new (Vector start, Vector end)[part2TemplateLines.Count];
|
||||||
for (var i = 0; i < part2TemplateLines.Count; i++)
|
for (var i = 0; i < part2TemplateLines.Count; i++)
|
||||||
mEdges[i] = (part2TemplateLines[i].StartPoint, part2TemplateLines[i].EndPoint);
|
mEdges[i] = (part2TemplateLines[i].StartPoint, part2TemplateLines[i].EndPoint);
|
||||||
@@ -204,21 +204,21 @@ namespace OpenNest.Engine.BestFit
|
|||||||
|
|
||||||
var sEdges = stationaryEdgesByDir[dir];
|
var sEdges = stationaryEdgesByDir[dir];
|
||||||
var mEdges = movingEdgesByDir[dir];
|
var mEdges = movingEdgesByDir[dir];
|
||||||
var opposite = Helper.OppositeDirection(dir);
|
var opposite = SpatialQuery.OppositeDirection(dir);
|
||||||
|
|
||||||
var minDist = double.MaxValue;
|
var minDist = double.MaxValue;
|
||||||
|
|
||||||
// Case 1: Moving vertices -> Stationary edges
|
// Case 1: Moving vertices -> Stationary edges
|
||||||
foreach (var mv in movingVerticesArray)
|
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;
|
if (d < minDist) minDist = d;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 2: Stationary vertices -> Moving edges (translated)
|
// Case 2: Stationary vertices -> Moving edges (translated)
|
||||||
foreach (var sv in stationaryVerticesArray)
|
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;
|
if (d < minDist) minDist = d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ namespace OpenNest
|
|||||||
for (var i = 0; i < obstacleParts.Count; i++)
|
for (var i = 0; i < obstacleParts.Count; i++)
|
||||||
obstacleBoxes[i] = obstacleParts[i].BoundingBox;
|
obstacleBoxes[i] = obstacleParts[i].BoundingBox;
|
||||||
|
|
||||||
var opposite = Helper.OppositeDirection(direction);
|
var opposite = SpatialQuery.OppositeDirection(direction);
|
||||||
var halfSpacing = plate.PartSpacing / 2;
|
var halfSpacing = plate.PartSpacing / 2;
|
||||||
var isHorizontal = Helper.IsHorizontalDirection(direction);
|
var isHorizontal = SpatialQuery.IsHorizontalDirection(direction);
|
||||||
var workArea = plate.WorkArea();
|
var workArea = plate.WorkArea();
|
||||||
var distance = double.MaxValue;
|
var distance = double.MaxValue;
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ namespace OpenNest
|
|||||||
|
|
||||||
foreach (var moving in movingParts)
|
foreach (var moving in movingParts)
|
||||||
{
|
{
|
||||||
var edgeDist = Helper.EdgeDistance(moving.BoundingBox, workArea, direction);
|
var edgeDist = SpatialQuery.EdgeDistance(moving.BoundingBox, workArea, direction);
|
||||||
if (edgeDist <= 0)
|
if (edgeDist <= 0)
|
||||||
distance = 0;
|
distance = 0;
|
||||||
else if (edgeDist < distance)
|
else if (edgeDist < distance)
|
||||||
@@ -113,11 +113,11 @@ namespace OpenNest
|
|||||||
// behind the moving part. The forward gap (gap < 0) is unreliable for
|
// behind the moving part. The forward gap (gap < 0) is unreliable for
|
||||||
// irregular shapes whose bounding boxes overlap even when the actual
|
// irregular shapes whose bounding boxes overlap even when the actual
|
||||||
// geometry still has a valid contact in the push direction.
|
// 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)
|
if (reverseGap > 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var gap = Helper.DirectionalGap(movingBox, obstacleBoxes[i], direction);
|
var gap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], direction);
|
||||||
if (gap >= distance)
|
if (gap >= distance)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ namespace OpenNest
|
|||||||
? PartGeometry.GetOffsetPartLines(obstacleParts[i], halfSpacing, opposite, ChordTolerance)
|
? PartGeometry.GetOffsetPartLines(obstacleParts[i], halfSpacing, opposite, ChordTolerance)
|
||||||
: PartGeometry.GetPartLines(obstacleParts[i], 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)
|
if (d < distance)
|
||||||
distance = d;
|
distance = d;
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@ namespace OpenNest
|
|||||||
|
|
||||||
if (distance < double.MaxValue && distance > 0)
|
if (distance < double.MaxValue && distance > 0)
|
||||||
{
|
{
|
||||||
var offset = Helper.DirectionToOffset(direction, distance);
|
var offset = SpatialQuery.DirectionToOffset(direction, distance);
|
||||||
foreach (var moving in movingParts)
|
foreach (var moving in movingParts)
|
||||||
moving.Offset(offset);
|
moving.Offset(offset);
|
||||||
return distance;
|
return distance;
|
||||||
|
|||||||
@@ -82,9 +82,9 @@ namespace OpenNest
|
|||||||
var locationBOffset = MakeOffset(direction, bboxDim);
|
var locationBOffset = MakeOffset(direction, bboxDim);
|
||||||
|
|
||||||
// Use the most efficient array-based overload to avoid all allocations.
|
// 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(pushDir), partA.Location + locationBOffset,
|
||||||
boundary.GetEdges(Helper.OppositeDirection(pushDir)), partA.Location,
|
boundary.GetEdges(SpatialQuery.OppositeDirection(pushDir)), partA.Location,
|
||||||
pushDir);
|
pushDir);
|
||||||
|
|
||||||
return ComputeCopyDistance(bboxDim, slideDistance);
|
return ComputeCopyDistance(bboxDim, slideDistance);
|
||||||
@@ -103,7 +103,7 @@ namespace OpenNest
|
|||||||
|
|
||||||
var bboxDim = GetDimension(patternA.BoundingBox, direction);
|
var bboxDim = GetDimension(patternA.BoundingBox, direction);
|
||||||
var pushDir = GetPushDirection(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
|
// Compute a starting offset large enough that every part-pair in
|
||||||
// patternB has its offset geometry beyond patternA's offset geometry.
|
// 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++)
|
for (var i = 0; i < patternA.Parts.Count; i++)
|
||||||
{
|
{
|
||||||
var slideDistance = Helper.DirectionalDistance(
|
var slideDistance = SpatialQuery.DirectionalDistance(
|
||||||
movingEdges[j], locationB,
|
movingEdges[j], locationB,
|
||||||
stationaryEdges[i], patternA.Parts[i].Location,
|
stationaryEdges[i], patternA.Parts[i].Location,
|
||||||
pushDir);
|
pushDir);
|
||||||
|
|||||||
@@ -186,8 +186,8 @@ namespace OpenNest.Actions
|
|||||||
boxes.Add(part.BoundingBox.Offset(plate.PartSpacing));
|
boxes.Add(part.BoundingBox.Offset(plate.PartSpacing));
|
||||||
|
|
||||||
var pt = plateView.CurrentPoint;
|
var pt = plateView.CurrentPoint;
|
||||||
var vertical = Helper.GetLargestBoxVertically(pt, bounds, boxes);
|
var vertical = SpatialQuery.GetLargestBoxVertically(pt, bounds, boxes);
|
||||||
var horizontal = Helper.GetLargestBoxHorizontally(pt, bounds, boxes);
|
var horizontal = SpatialQuery.GetLargestBoxHorizontally(pt, bounds, boxes);
|
||||||
|
|
||||||
var bestArea = vertical;
|
var bestArea = vertical;
|
||||||
if (horizontal.Area() > vertical.Area())
|
if (horizontal.Area() > vertical.Area())
|
||||||
|
|||||||
@@ -150,8 +150,8 @@ namespace OpenNest.Actions
|
|||||||
private void UpdateSelectedArea()
|
private void UpdateSelectedArea()
|
||||||
{
|
{
|
||||||
SelectedArea = altSelect
|
SelectedArea = altSelect
|
||||||
? Helper.GetLargestBoxHorizontally(plateView.CurrentPoint, Bounds, boxes)
|
? SpatialQuery.GetLargestBoxHorizontally(plateView.CurrentPoint, Bounds, boxes)
|
||||||
: Helper.GetLargestBoxVertically(plateView.CurrentPoint, Bounds, boxes);
|
: SpatialQuery.GetLargestBoxVertically(plateView.CurrentPoint, Bounds, boxes);
|
||||||
|
|
||||||
plateView.Invalidate();
|
plateView.Invalidate();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user