refactor: extract IDistanceComputer with CPU and GPU implementations
Extract distance computation from RotationSlideStrategy into a pluggable IDistanceComputer interface. CpuDistanceComputer adds leading-face vertex culling (~50% fewer rays per direction) with early exit on overlap. GpuDistanceComputer wraps ISlideComputer with Line-to-flat-array conversion. SlideOffset struct uses direction vectors (DirX/DirY) instead of PushDirection. SpatialQuery.RayEdgeDistance(dirX,dirY) made public for CPU path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -76,7 +76,7 @@ namespace OpenNest.Geometry
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[System.Runtime.CompilerServices.MethodImpl(
|
[System.Runtime.CompilerServices.MethodImpl(
|
||||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||||
private static double RayEdgeDistance(
|
public static double RayEdgeDistance(
|
||||||
double vx, double vy,
|
double vx, double vy,
|
||||||
double p1x, double p1y, double p2x, double p2y,
|
double p1x, double p1y, double p2x, double p2y,
|
||||||
double dirX, double dirY)
|
double dirX, double dirY)
|
||||||
|
|||||||
152
OpenNest.Engine/BestFit/CpuDistanceComputer.cs
Normal file
152
OpenNest.Engine/BestFit/CpuDistanceComputer.cs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
using OpenNest.Geometry;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace OpenNest.Engine.BestFit
|
||||||
|
{
|
||||||
|
public class CpuDistanceComputer : IDistanceComputer
|
||||||
|
{
|
||||||
|
public double[] ComputeDistances(
|
||||||
|
List<Line> stationaryLines,
|
||||||
|
List<Line> movingTemplateLines,
|
||||||
|
SlideOffset[] offsets)
|
||||||
|
{
|
||||||
|
var count = offsets.Length;
|
||||||
|
var results = new double[count];
|
||||||
|
|
||||||
|
var allMovingVerts = ExtractUniqueVertices(movingTemplateLines);
|
||||||
|
var allStationaryVerts = ExtractUniqueVertices(stationaryLines);
|
||||||
|
|
||||||
|
// Pre-filter vertices per unique direction (typically 4 cardinal directions).
|
||||||
|
var vertexCache = new Dictionary<(double, double), (Vector[] leading, Vector[] facing)>();
|
||||||
|
|
||||||
|
foreach (var offset in offsets)
|
||||||
|
{
|
||||||
|
var key = (offset.DirX, offset.DirY);
|
||||||
|
if (vertexCache.ContainsKey(key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var leading = FilterVerticesByProjection(allMovingVerts, offset.DirX, offset.DirY, keepHigh: true);
|
||||||
|
var facing = FilterVerticesByProjection(allStationaryVerts, offset.DirX, offset.DirY, keepHigh: false);
|
||||||
|
vertexCache[key] = (leading, facing);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Threading.Tasks.Parallel.For(0, count, i =>
|
||||||
|
{
|
||||||
|
var offset = offsets[i];
|
||||||
|
var dirX = offset.DirX;
|
||||||
|
var dirY = offset.DirY;
|
||||||
|
var oppX = -dirX;
|
||||||
|
var oppY = -dirY;
|
||||||
|
|
||||||
|
var (leadingMoving, facingStationary) = vertexCache[(dirX, dirY)];
|
||||||
|
|
||||||
|
var minDist = double.MaxValue;
|
||||||
|
|
||||||
|
// Case 1: Leading moving vertices → stationary edges
|
||||||
|
for (var v = 0; v < leadingMoving.Length; v++)
|
||||||
|
{
|
||||||
|
var vx = leadingMoving[v].X + offset.Dx;
|
||||||
|
var vy = leadingMoving[v].Y + offset.Dy;
|
||||||
|
|
||||||
|
for (var j = 0; j < stationaryLines.Count; j++)
|
||||||
|
{
|
||||||
|
var e = stationaryLines[j];
|
||||||
|
var d = SpatialQuery.RayEdgeDistance(
|
||||||
|
vx, vy,
|
||||||
|
e.StartPoint.X, e.StartPoint.Y,
|
||||||
|
e.EndPoint.X, e.EndPoint.Y,
|
||||||
|
dirX, dirY);
|
||||||
|
|
||||||
|
if (d < minDist)
|
||||||
|
{
|
||||||
|
minDist = d;
|
||||||
|
if (d <= 0) { results[i] = 0; return; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: Facing stationary vertices → moving edges (opposite direction)
|
||||||
|
for (var v = 0; v < facingStationary.Length; v++)
|
||||||
|
{
|
||||||
|
var svx = facingStationary[v].X;
|
||||||
|
var svy = facingStationary[v].Y;
|
||||||
|
|
||||||
|
for (var j = 0; j < movingTemplateLines.Count; j++)
|
||||||
|
{
|
||||||
|
var e = movingTemplateLines[j];
|
||||||
|
var d = SpatialQuery.RayEdgeDistance(
|
||||||
|
svx, svy,
|
||||||
|
e.StartPoint.X + offset.Dx, e.StartPoint.Y + offset.Dy,
|
||||||
|
e.EndPoint.X + offset.Dx, e.EndPoint.Y + offset.Dy,
|
||||||
|
oppX, oppY);
|
||||||
|
|
||||||
|
if (d < minDist)
|
||||||
|
{
|
||||||
|
minDist = d;
|
||||||
|
if (d <= 0) { results[i] = 0; return; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results[i] = minDist;
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector[] ExtractUniqueVertices(List<Line> lines)
|
||||||
|
{
|
||||||
|
var vertices = new HashSet<Vector>();
|
||||||
|
for (var i = 0; i < lines.Count; i++)
|
||||||
|
{
|
||||||
|
vertices.Add(lines[i].StartPoint);
|
||||||
|
vertices.Add(lines[i].EndPoint);
|
||||||
|
}
|
||||||
|
return vertices.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filters vertices by their projection onto the push direction.
|
||||||
|
/// keepHigh=true returns the leading half (front face, closest to target).
|
||||||
|
/// keepHigh=false returns the facing half (side facing the approaching part).
|
||||||
|
/// </summary>
|
||||||
|
private static Vector[] FilterVerticesByProjection(
|
||||||
|
Vector[] vertices, double dirX, double dirY, bool keepHigh)
|
||||||
|
{
|
||||||
|
if (vertices.Length == 0)
|
||||||
|
return vertices;
|
||||||
|
|
||||||
|
var projections = new double[vertices.Length];
|
||||||
|
var min = double.MaxValue;
|
||||||
|
var max = double.MinValue;
|
||||||
|
|
||||||
|
for (var i = 0; i < vertices.Length; i++)
|
||||||
|
{
|
||||||
|
projections[i] = vertices[i].X * dirX + vertices[i].Y * dirY;
|
||||||
|
if (projections[i] < min) min = projections[i];
|
||||||
|
if (projections[i] > max) max = projections[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
var midpoint = (min + max) / 2;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < vertices.Length; i++)
|
||||||
|
{
|
||||||
|
if (keepHigh ? projections[i] >= midpoint : projections[i] <= midpoint)
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new Vector[count];
|
||||||
|
var idx = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < vertices.Length; i++)
|
||||||
|
{
|
||||||
|
if (keepHigh ? projections[i] >= midpoint : projections[i] <= midpoint)
|
||||||
|
result[idx++] = vertices[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
OpenNest.Engine/BestFit/GpuDistanceComputer.cs
Normal file
51
OpenNest.Engine/BestFit/GpuDistanceComputer.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using OpenNest.Geometry;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace OpenNest.Engine.BestFit
|
||||||
|
{
|
||||||
|
public class GpuDistanceComputer : IDistanceComputer
|
||||||
|
{
|
||||||
|
private readonly ISlideComputer _slideComputer;
|
||||||
|
|
||||||
|
public GpuDistanceComputer(ISlideComputer slideComputer)
|
||||||
|
{
|
||||||
|
_slideComputer = slideComputer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double[] ComputeDistances(
|
||||||
|
List<Line> stationaryLines,
|
||||||
|
List<Line> movingTemplateLines,
|
||||||
|
SlideOffset[] offsets)
|
||||||
|
{
|
||||||
|
var stationarySegments = SpatialQuery.FlattenLines(stationaryLines);
|
||||||
|
var movingSegments = SpatialQuery.FlattenLines(movingTemplateLines);
|
||||||
|
var count = offsets.Length;
|
||||||
|
var flatOffsets = new double[count * 2];
|
||||||
|
var directions = new int[count];
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
flatOffsets[i * 2] = offsets[i].Dx;
|
||||||
|
flatOffsets[i * 2 + 1] = offsets[i].Dy;
|
||||||
|
directions[i] = DirectionVectorToInt(offsets[i].DirX, offsets[i].DirY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _slideComputer.ComputeBatchMultiDir(
|
||||||
|
stationarySegments, stationaryLines.Count,
|
||||||
|
movingSegments, movingTemplateLines.Count,
|
||||||
|
flatOffsets, count, directions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps a unit direction vector to a PushDirection int for the GPU interface.
|
||||||
|
/// Left=0, Down=1, Right=2, Up=3.
|
||||||
|
/// </summary>
|
||||||
|
private static int DirectionVectorToInt(double dirX, double dirY)
|
||||||
|
{
|
||||||
|
if (dirX < -0.5) return (int)PushDirection.Left;
|
||||||
|
if (dirX > 0.5) return (int)PushDirection.Right;
|
||||||
|
if (dirY < -0.5) return (int)PushDirection.Down;
|
||||||
|
return (int)PushDirection.Up;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
OpenNest.Engine/BestFit/IDistanceComputer.cs
Normal file
13
OpenNest.Engine/BestFit/IDistanceComputer.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using OpenNest.Geometry;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace OpenNest.Engine.BestFit
|
||||||
|
{
|
||||||
|
public interface IDistanceComputer
|
||||||
|
{
|
||||||
|
double[] ComputeDistances(
|
||||||
|
List<Line> stationaryLines,
|
||||||
|
List<Line> movingTemplateLines,
|
||||||
|
SlideOffset[] offsets);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
OpenNest.Engine/BestFit/SlideOffset.cs
Normal file
18
OpenNest.Engine/BestFit/SlideOffset.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
namespace OpenNest.Engine.BestFit
|
||||||
|
{
|
||||||
|
public readonly struct SlideOffset
|
||||||
|
{
|
||||||
|
public double Dx { get; }
|
||||||
|
public double Dy { get; }
|
||||||
|
public double DirX { get; }
|
||||||
|
public double DirY { get; }
|
||||||
|
|
||||||
|
public SlideOffset(double dx, double dy, double dirX, double dirY)
|
||||||
|
{
|
||||||
|
Dx = dx;
|
||||||
|
Dy = dy;
|
||||||
|
DirX = dirX;
|
||||||
|
DirY = dirY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user