using OpenNest.Geometry; using OpenNest.Math; using System.Collections.Generic; using System.Linq; namespace OpenNest.Engine.BestFit { public class CpuDistanceComputer : IDistanceComputer { public double[] ComputeDistances( List stationaryLines, List movingTemplateLines, SlideOffset[] offsets) { var count = offsets.Length; var results = new double[count]; var allMovingVerts = ExtractUniqueVertices(movingTemplateLines); var allStationaryVerts = ExtractUniqueVertices(stationaryLines); 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; 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; } } } } 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; } public double[] ComputeDistances( List stationaryEntities, List movingEntities, SlideOffset[] offsets) { var count = offsets.Length; var results = new double[count]; var allMovingVerts = ExtractVerticesFromEntities(movingEntities); var allStationaryVerts = ExtractVerticesFromEntities(stationaryEntities); 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 entities 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 < stationaryEntities.Count; j++) { var d = RayEntityDistance(vx, vy, stationaryEntities[j], 0, 0, dirX, dirY); if (d < minDist) { minDist = d; if (d <= 0) { results[i] = 0; return; } } } } // Case 2: Facing stationary vertices → moving entities (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 < movingEntities.Count; j++) { var d = RayEntityDistance(svx, svy, movingEntities[j], offset.Dx, offset.Dy, oppX, oppY); if (d < minDist) { minDist = d; if (d <= 0) { results[i] = 0; return; } } } } results[i] = minDist; }); return results; } private static double RayEntityDistance( double vx, double vy, Entity entity, double entityOffsetX, double entityOffsetY, double dirX, double dirY) { if (entity is Line line) { return SpatialQuery.RayEdgeDistance( vx, vy, line.StartPoint.X + entityOffsetX, line.StartPoint.Y + entityOffsetY, line.EndPoint.X + entityOffsetX, line.EndPoint.Y + entityOffsetY, dirX, dirY); } if (entity is Arc arc) { return SpatialQuery.RayArcDistance( vx, vy, arc.Center.X + entityOffsetX, arc.Center.Y + entityOffsetY, arc.Radius, arc.StartAngle, arc.EndAngle, arc.IsReversed, dirX, dirY); } if (entity is Circle circle) { return SpatialQuery.RayCircleDistance( vx, vy, circle.Center.X + entityOffsetX, circle.Center.Y + entityOffsetY, circle.Radius, dirX, dirY); } return double.MaxValue; } private static Vector[] ExtractVerticesFromEntities(List entities) { var vertices = new HashSet(); for (var i = 0; i < entities.Count; i++) { var entity = entities[i]; if (entity is Line line) { vertices.Add(line.StartPoint); vertices.Add(line.EndPoint); } else if (entity is Arc arc) { vertices.Add(arc.StartPoint()); vertices.Add(arc.EndPoint()); AddArcExtremes(vertices, arc); } else if (entity is Circle circle) { // Four cardinal points vertices.Add(new Vector(circle.Center.X + circle.Radius, circle.Center.Y)); vertices.Add(new Vector(circle.Center.X - circle.Radius, circle.Center.Y)); vertices.Add(new Vector(circle.Center.X, circle.Center.Y + circle.Radius)); vertices.Add(new Vector(circle.Center.X, circle.Center.Y - circle.Radius)); } } return vertices.ToArray(); } private static void AddArcExtremes(HashSet points, Arc arc) { var a1 = arc.StartAngle; var a2 = arc.EndAngle; var reversed = arc.IsReversed; if (reversed) 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)); } private static Vector[] ExtractUniqueVertices(List lines) { var vertices = new HashSet(); for (var i = 0; i < lines.Count; i++) { vertices.Add(lines[i].StartPoint); vertices.Add(lines[i].EndPoint); } return vertices.ToArray(); } 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; } } }