feat: integrate GPU slide computation into best-fit pipeline

Thread ISlideComputer through BestFitCache → BestFitFinder →
RotationSlideStrategy. RotationSlideStrategy now collects all offsets
across 4 push directions and dispatches them in a single batch (GPU or
CPU fallback). Also improves rotation angle extraction: uses raw geometry
(line endpoints + arc cardinal extremes) instead of tessellation to avoid
flooding the hull with near-duplicate edge angles, and adds a 5-degree
deduplication threshold.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 20:29:51 -04:00
parent 97dfe27953
commit 183d169cc1
4 changed files with 175 additions and 74 deletions
+61 -5
View File
@@ -12,11 +12,14 @@ namespace OpenNest.Engine.BestFit
public class BestFitFinder
{
private readonly IPairEvaluator _evaluator;
private readonly ISlideComputer _slideComputer;
private readonly BestFitFilter _filter;
public BestFitFinder(double maxPlateWidth, double maxPlateHeight, IPairEvaluator evaluator = null)
public BestFitFinder(double maxPlateWidth, double maxPlateHeight,
IPairEvaluator evaluator = null, ISlideComputer slideComputer = null)
{
_evaluator = evaluator ?? new PairEvaluator();
_slideComputer = slideComputer;
_filter = new BestFitFilter
{
MaxPlateWidth = maxPlateWidth,
@@ -78,7 +81,7 @@ namespace OpenNest.Engine.BestFit
foreach (var angle in angles)
{
var desc = string.Format("{0:F1} deg rotated, offset slide", Angle.ToDegrees(angle));
strategies.Add(new RotationSlideStrategy(angle, type++, desc));
strategies.Add(new RotationSlideStrategy(angle, type++, desc, _slideComputer));
}
return strategies;
@@ -102,6 +105,7 @@ namespace OpenNest.Engine.BestFit
AddUniqueAngle(angles, Angle.NormalizeRad(hullAngle + System.Math.PI));
}
angles.Sort();
return angles;
}
@@ -115,8 +119,24 @@ namespace OpenNest.Engine.BestFit
foreach (var shape in shapes)
{
var polygon = shape.ToPolygonWithTolerance(0.01);
points.AddRange(polygon.Vertices);
// Extract key points from original geometry — line endpoints
// plus arc endpoints and cardinal extreme points. This avoids
// tessellating arcs into many chords that flood the hull with
// near-duplicate edge angles.
foreach (var entity in shape.Entities)
{
if (entity is Line line)
{
points.Add(line.StartPoint);
points.Add(line.EndPoint);
}
else if (entity is Arc arc)
{
points.Add(arc.StartPoint());
points.Add(arc.EndPoint());
AddArcExtremes(points, arc);
}
}
}
if (points.Count < 3)
@@ -143,13 +163,49 @@ namespace OpenNest.Engine.BestFit
return hullAngles;
}
/// <summary>
/// Adds the cardinal extreme points of an arc (0°, 90°, 180°, 270°)
/// if they fall within the arc's angular span.
/// </summary>
private static void AddArcExtremes(List<Vector> points, Arc arc)
{
var a1 = arc.StartAngle;
var a2 = arc.EndAngle;
if (arc.IsReversed)
Generic.Swap(ref a1, ref a2);
// Right (0°)
if (Angle.IsBetweenRad(Angle.TwoPI, a1, a2))
points.Add(new Vector(arc.Center.X + arc.Radius, arc.Center.Y));
// Top (90°)
if (Angle.IsBetweenRad(Angle.HalfPI, a1, a2))
points.Add(new Vector(arc.Center.X, arc.Center.Y + arc.Radius));
// Left (180°)
if (Angle.IsBetweenRad(System.Math.PI, a1, a2))
points.Add(new Vector(arc.Center.X - arc.Radius, arc.Center.Y));
// Bottom (270°)
if (Angle.IsBetweenRad(System.Math.PI * 1.5, a1, a2))
points.Add(new Vector(arc.Center.X, arc.Center.Y - arc.Radius));
}
/// <summary>
/// Minimum angular separation (radians) between hull-derived rotation candidates.
/// Tessellated arcs produce many hull edges with nearly identical angles;
/// a 1° threshold collapses those into a single representative.
/// </summary>
private const double AngleTolerance = System.Math.PI / 36; // 5 degrees
private static void AddUniqueAngle(List<double> angles, double angle)
{
angle = Angle.NormalizeRad(angle);
foreach (var existing in angles)
{
if (existing.IsEqualTo(angle))
if (existing.IsEqualTo(angle, AngleTolerance))
return;
}