diff --git a/OpenNest.Core/Geometry/SpatialQuery.cs b/OpenNest.Core/Geometry/SpatialQuery.cs index e34d345..f7e398c 100644 --- a/OpenNest.Core/Geometry/SpatialQuery.cs +++ b/OpenNest.Core/Geometry/SpatialQuery.cs @@ -76,7 +76,7 @@ namespace OpenNest.Geometry /// [System.Runtime.CompilerServices.MethodImpl( System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - private static double RayEdgeDistance( + public static double RayEdgeDistance( double vx, double vy, double p1x, double p1y, double p2x, double p2y, double dirX, double dirY) diff --git a/OpenNest.Engine/BestFit/CpuDistanceComputer.cs b/OpenNest.Engine/BestFit/CpuDistanceComputer.cs new file mode 100644 index 0000000..3de81ee --- /dev/null +++ b/OpenNest.Engine/BestFit/CpuDistanceComputer.cs @@ -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 stationaryLines, + List 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 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(); + } + + /// + /// 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). + /// + 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; + } + } +} diff --git a/OpenNest.Engine/BestFit/GpuDistanceComputer.cs b/OpenNest.Engine/BestFit/GpuDistanceComputer.cs new file mode 100644 index 0000000..591bbe2 --- /dev/null +++ b/OpenNest.Engine/BestFit/GpuDistanceComputer.cs @@ -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 stationaryLines, + List 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); + } + + /// + /// Maps a unit direction vector to a PushDirection int for the GPU interface. + /// Left=0, Down=1, Right=2, Up=3. + /// + 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; + } + } +} diff --git a/OpenNest.Engine/BestFit/IDistanceComputer.cs b/OpenNest.Engine/BestFit/IDistanceComputer.cs new file mode 100644 index 0000000..5c208c7 --- /dev/null +++ b/OpenNest.Engine/BestFit/IDistanceComputer.cs @@ -0,0 +1,13 @@ +using OpenNest.Geometry; +using System.Collections.Generic; + +namespace OpenNest.Engine.BestFit +{ + public interface IDistanceComputer + { + double[] ComputeDistances( + List stationaryLines, + List movingTemplateLines, + SlideOffset[] offsets); + } +} diff --git a/OpenNest.Engine/BestFit/SlideOffset.cs b/OpenNest.Engine/BestFit/SlideOffset.cs new file mode 100644 index 0000000..ea046c6 --- /dev/null +++ b/OpenNest.Engine/BestFit/SlideOffset.cs @@ -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; + } + } +}