diff --git a/OpenNest.Engine/BestFit/BestFitFinder.cs b/OpenNest.Engine/BestFit/BestFitFinder.cs index be5a26d..90bf2da 100644 --- a/OpenNest.Engine/BestFit/BestFitFinder.cs +++ b/OpenNest.Engine/BestFit/BestFitFinder.cs @@ -12,14 +12,16 @@ namespace OpenNest.Engine.BestFit public class BestFitFinder { private readonly IPairEvaluator _evaluator; - private readonly ISlideComputer _slideComputer; + private readonly IDistanceComputer _distanceComputer; private readonly BestFitFilter _filter; public BestFitFinder(double maxPlateWidth, double maxPlateHeight, IPairEvaluator evaluator = null, ISlideComputer slideComputer = null) { _evaluator = evaluator ?? new PairEvaluator(); - _slideComputer = slideComputer; + _distanceComputer = slideComputer != null + ? (IDistanceComputer)new GpuDistanceComputer(slideComputer) + : new CpuDistanceComputer(); var plateAspect = System.Math.Max(maxPlateWidth, maxPlateHeight) / System.Math.Max(System.Math.Min(maxPlateWidth, maxPlateHeight), 0.001); _filter = new BestFitFilter @@ -79,12 +81,12 @@ namespace OpenNest.Engine.BestFit { var angles = GetRotationAngles(drawing); var strategies = new List(); - var type = 1; + var index = 1; 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, _slideComputer)); + strategies.Add(new RotationSlideStrategy(angle, index++, desc, _distanceComputer)); } return strategies; @@ -226,7 +228,7 @@ namespace OpenNest.Engine.BestFit case BestFitSortField.ShortestSide: return results.OrderBy(r => r.ShortestSide).ToList(); case BestFitSortField.Type: - return results.OrderBy(r => r.Candidate.StrategyType) + return results.OrderBy(r => r.Candidate.StrategyIndex) .ThenBy(r => r.Candidate.TestNumber).ToList(); case BestFitSortField.OriginalSequence: return results.OrderBy(r => r.Candidate.TestNumber).ToList(); diff --git a/OpenNest.Engine/BestFit/IBestFitStrategy.cs b/OpenNest.Engine/BestFit/IBestFitStrategy.cs index f386c28..56bee87 100644 --- a/OpenNest.Engine/BestFit/IBestFitStrategy.cs +++ b/OpenNest.Engine/BestFit/IBestFitStrategy.cs @@ -4,7 +4,7 @@ namespace OpenNest.Engine.BestFit { public interface IBestFitStrategy { - int Type { get; } + int StrategyIndex { get; } string Description { get; } List GenerateCandidates(Drawing drawing, double spacing, double stepSize); } diff --git a/OpenNest.Engine/BestFit/NfpSlideStrategy.cs b/OpenNest.Engine/BestFit/NfpSlideStrategy.cs index 70f18e4..c2373a5 100644 --- a/OpenNest.Engine/BestFit/NfpSlideStrategy.cs +++ b/OpenNest.Engine/BestFit/NfpSlideStrategy.cs @@ -22,14 +22,14 @@ namespace OpenNest.Engine.BestFit Polygon stationaryPerimeter, Polygon stationaryHull, Vector correction) { _part2Rotation = part2Rotation; - Type = type; + StrategyIndex = type; Description = description; _stationaryPerimeter = stationaryPerimeter; _stationaryHull = stationaryHull; _correction = correction; } - public int Type { get; } + public int StrategyIndex { get; } public string Description { get; } /// @@ -155,7 +155,7 @@ namespace OpenNest.Engine.BestFit Part1Rotation = 0, Part2Rotation = _part2Rotation, Part2Offset = offset, - StrategyType = Type, + StrategyIndex = StrategyIndex, TestNumber = testNumber, Spacing = spacing }; diff --git a/OpenNest.Engine/BestFit/PairCandidate.cs b/OpenNest.Engine/BestFit/PairCandidate.cs index 523d922..d7e7459 100644 --- a/OpenNest.Engine/BestFit/PairCandidate.cs +++ b/OpenNest.Engine/BestFit/PairCandidate.cs @@ -8,7 +8,7 @@ namespace OpenNest.Engine.BestFit public double Part1Rotation { get; set; } public double Part2Rotation { get; set; } public Vector Part2Offset { get; set; } - public int StrategyType { get; set; } + public int StrategyIndex { get; set; } public int TestNumber { get; set; } public double Spacing { get; set; } } diff --git a/OpenNest.Engine/BestFit/RotationSlideStrategy.cs b/OpenNest.Engine/BestFit/RotationSlideStrategy.cs index 7ad834d..a5706a7 100644 --- a/OpenNest.Engine/BestFit/RotationSlideStrategy.cs +++ b/OpenNest.Engine/BestFit/RotationSlideStrategy.cs @@ -1,29 +1,31 @@ using OpenNest.Geometry; using System.Collections.Generic; -using System.Linq; namespace OpenNest.Engine.BestFit { public class RotationSlideStrategy : IBestFitStrategy { - private readonly ISlideComputer _slideComputer; + private readonly IDistanceComputer _distanceComputer; - private static readonly PushDirection[] AllDirections = + private static readonly (double DirX, double DirY)[] PushDirections = { - PushDirection.Left, PushDirection.Down, PushDirection.Right, PushDirection.Up + (-1, 0), // Left + (0, -1), // Down + (1, 0), // Right + (0, 1) // Up }; - public RotationSlideStrategy(double part2Rotation, int type, string description, - ISlideComputer slideComputer = null) + public RotationSlideStrategy(double part2Rotation, int strategyIndex, string description, + IDistanceComputer distanceComputer) { Part2Rotation = part2Rotation; - Type = type; + StrategyIndex = strategyIndex; Description = description; - _slideComputer = slideComputer; + _distanceComputer = distanceComputer; } public double Part2Rotation { get; } - public int Type { get; } + public int StrategyIndex { get; } public string Description { get; } public List GenerateCandidates(Drawing drawing, double spacing, double stepSize) @@ -40,36 +42,25 @@ namespace OpenNest.Engine.BestFit var bbox1 = part1.BoundingBox; var bbox2 = part2Template.BoundingBox; - // Collect offsets and directions across all 4 axes - var allDx = new List(); - var allDy = new List(); - var allDirs = new List(); + var offsets = BuildOffsets(bbox1, bbox2, spacing, stepSize); - foreach (var pushDir in AllDirections) - BuildOffsets(bbox1, bbox2, spacing, stepSize, pushDir, allDx, allDy, allDirs); - - if (allDx.Count == 0) + if (offsets.Length == 0) return candidates; - // Compute all distances — single GPU dispatch or CPU loop - var distances = ComputeAllDistances( - part1Lines, part2TemplateLines, allDx, allDy, allDirs); + var distances = _distanceComputer.ComputeDistances( + part1Lines, part2TemplateLines, offsets); - // Create candidates from valid results var testNumber = 0; - for (var i = 0; i < allDx.Count; i++) + for (var i = 0; i < offsets.Length; i++) { var slideDist = distances[i]; if (slideDist >= double.MaxValue || slideDist < 0) continue; - var dx = allDx[i]; - var dy = allDy[i]; - var pushVector = GetPushVector(allDirs[i], slideDist); var finalPosition = new Vector( - part2Template.Location.X + dx + pushVector.X, - part2Template.Location.Y + dy + pushVector.Y); + part2Template.Location.X + offsets[i].Dx + offsets[i].DirX * slideDist, + part2Template.Location.Y + offsets[i].Dy + offsets[i].DirY * slideDist); candidates.Add(new PairCandidate { @@ -77,7 +68,7 @@ namespace OpenNest.Engine.BestFit Part1Rotation = 0, Part2Rotation = Part2Rotation, Part2Offset = finalPosition, - StrategyType = Type, + StrategyIndex = StrategyIndex, TestNumber = testNumber++, Spacing = spacing }); @@ -86,158 +77,44 @@ namespace OpenNest.Engine.BestFit return candidates; } - private static void BuildOffsets( - Box bbox1, Box bbox2, double spacing, double stepSize, - PushDirection pushDir, List allDx, List allDy, - List allDirs) + private static SlideOffset[] BuildOffsets(Box bbox1, Box bbox2, double spacing, double stepSize) { - var isHorizontalPush = pushDir == PushDirection.Left || pushDir == PushDirection.Right; + var offsets = new List(); - double perpMin, perpMax, pushStartOffset; - - if (isHorizontalPush) + foreach (var (dirX, dirY) in PushDirections) { - perpMin = -(bbox2.Length + spacing); - perpMax = bbox1.Length + bbox2.Length + spacing; - pushStartOffset = bbox1.Width + bbox2.Width + spacing * 2; - } - else - { - perpMin = -(bbox2.Width + spacing); - perpMax = bbox1.Width + bbox2.Width + spacing; - pushStartOffset = bbox1.Length + bbox2.Length + spacing * 2; - } + var isHorizontalPush = System.Math.Abs(dirX) > System.Math.Abs(dirY); - var alignedStart = System.Math.Ceiling(perpMin / stepSize) * stepSize; - var isPositiveStart = pushDir == PushDirection.Left || pushDir == PushDirection.Down; - var startPos = isPositiveStart ? pushStartOffset : -pushStartOffset; + double perpMin, perpMax, pushStartOffset; - for (var offset = alignedStart; offset <= perpMax; offset += stepSize) - { - allDx.Add(isHorizontalPush ? startPos : offset); - allDy.Add(isHorizontalPush ? offset : startPos); - allDirs.Add(pushDir); - } - } - - private double[] ComputeAllDistances( - List part1Lines, List part2TemplateLines, - List allDx, List allDy, List allDirs) - { - var count = allDx.Count; - - if (_slideComputer != null) - { - var stationarySegments = SpatialQuery.FlattenLines(part1Lines); - var movingSegments = SpatialQuery.FlattenLines(part2TemplateLines); - var offsets = new double[count * 2]; - var directions = new int[count]; - - for (var i = 0; i < count; i++) + if (isHorizontalPush) { - offsets[i * 2] = allDx[i]; - offsets[i * 2 + 1] = allDy[i]; - directions[i] = (int)allDirs[i]; + perpMin = -(bbox2.Length + spacing); + perpMax = bbox1.Length + bbox2.Length + spacing; + pushStartOffset = bbox1.Width + bbox2.Width + spacing * 2; } - - return _slideComputer.ComputeBatchMultiDir( - stationarySegments, part1Lines.Count, - movingSegments, part2TemplateLines.Count, - offsets, count, directions); - } - - var results = new double[count]; - - // Pre-calculate moving vertices in local space. - var movingVerticesLocal = new HashSet(); - for (var i = 0; i < part2TemplateLines.Count; i++) - { - movingVerticesLocal.Add(part2TemplateLines[i].StartPoint); - movingVerticesLocal.Add(part2TemplateLines[i].EndPoint); - } - var movingVerticesArray = movingVerticesLocal.ToArray(); - - // Pre-calculate stationary vertices in local space. - var stationaryVerticesLocal = new HashSet(); - for (var i = 0; i < part1Lines.Count; i++) - { - stationaryVerticesLocal.Add(part1Lines[i].StartPoint); - stationaryVerticesLocal.Add(part1Lines[i].EndPoint); - } - var stationaryVerticesArray = stationaryVerticesLocal.ToArray(); - - // Pre-sort stationary and moving edges for all 4 directions. - var stationaryEdgesByDir = new Dictionary(); - var movingEdgesByDir = new Dictionary(); - - foreach (var dir in AllDirections) - { - var sEdges = new (Vector start, Vector end)[part1Lines.Count]; - for (var i = 0; i < part1Lines.Count; i++) - sEdges[i] = (part1Lines[i].StartPoint, part1Lines[i].EndPoint); - - if (dir == PushDirection.Left || dir == PushDirection.Right) - sEdges = sEdges.OrderBy(e => System.Math.Min(e.start.Y, e.end.Y)).ToArray(); else - sEdges = sEdges.OrderBy(e => System.Math.Min(e.start.X, e.end.X)).ToArray(); - stationaryEdgesByDir[dir] = sEdges; - - var opposite = SpatialQuery.OppositeDirection(dir); - var mEdges = new (Vector start, Vector end)[part2TemplateLines.Count]; - for (var i = 0; i < part2TemplateLines.Count; i++) - mEdges[i] = (part2TemplateLines[i].StartPoint, part2TemplateLines[i].EndPoint); - - if (opposite == PushDirection.Left || opposite == PushDirection.Right) - mEdges = mEdges.OrderBy(e => System.Math.Min(e.start.Y, e.end.Y)).ToArray(); - else - mEdges = mEdges.OrderBy(e => System.Math.Min(e.start.X, e.end.X)).ToArray(); - movingEdgesByDir[dir] = mEdges; - } - - // Use Parallel.For for the heavy lifting. - System.Threading.Tasks.Parallel.For(0, count, i => - { - var dx = allDx[i]; - var dy = allDy[i]; - var dir = allDirs[i]; - var movingOffset = new Vector(dx, dy); - - var sEdges = stationaryEdgesByDir[dir]; - var mEdges = movingEdgesByDir[dir]; - var opposite = SpatialQuery.OppositeDirection(dir); - - var minDist = double.MaxValue; - - // Case 1: Moving vertices -> Stationary edges - foreach (var mv in movingVerticesArray) { - var d = SpatialQuery.OneWayDistance(mv + movingOffset, sEdges, Vector.Zero, dir); - if (d < minDist) minDist = d; + perpMin = -(bbox2.Width + spacing); + perpMax = bbox1.Width + bbox2.Width + spacing; + pushStartOffset = bbox1.Length + bbox2.Length + spacing * 2; } - // Case 2: Stationary vertices -> Moving edges (translated) - foreach (var sv in stationaryVerticesArray) + var alignedStart = System.Math.Ceiling(perpMin / stepSize) * stepSize; + + // Start on the opposite side of the push direction. + var pushComponent = isHorizontalPush ? dirX : dirY; + var startPos = pushComponent < 0 ? pushStartOffset : -pushStartOffset; + + for (var offset = alignedStart; offset <= perpMax; offset += stepSize) { - var d = SpatialQuery.OneWayDistance(sv, mEdges, movingOffset, opposite); - if (d < minDist) minDist = d; + var dx = isHorizontalPush ? startPos : offset; + var dy = isHorizontalPush ? offset : startPos; + offsets.Add(new SlideOffset(dx, dy, dirX, dirY)); } - - results[i] = minDist; - }); - - return results; - } - - private static Vector GetPushVector(PushDirection direction, double distance) - { - switch (direction) - { - case PushDirection.Left: return new Vector(-distance, 0); - case PushDirection.Right: return new Vector(distance, 0); - case PushDirection.Down: return new Vector(0, -distance); - case PushDirection.Up: return new Vector(0, distance); - default: return Vector.Zero; } + + return offsets.ToArray(); } } } diff --git a/OpenNest.IO/NestReader.cs b/OpenNest.IO/NestReader.cs index 99efcec..5936b3b 100644 --- a/OpenNest.IO/NestReader.cs +++ b/OpenNest.IO/NestReader.cs @@ -129,7 +129,7 @@ namespace OpenNest.IO Part1Rotation = r.Part1Rotation, Part2Rotation = r.Part2Rotation, Part2Offset = new Vector(r.Part2OffsetX, r.Part2OffsetY), - StrategyType = r.StrategyType, + StrategyIndex = r.StrategyType, TestNumber = r.TestNumber, Spacing = r.CandidateSpacing }, diff --git a/OpenNest.IO/NestWriter.cs b/OpenNest.IO/NestWriter.cs index 23cf581..ffa5f77 100644 --- a/OpenNest.IO/NestWriter.cs +++ b/OpenNest.IO/NestWriter.cs @@ -214,7 +214,7 @@ namespace OpenNest.IO Part2Rotation = r.Candidate.Part2Rotation, Part2OffsetX = r.Candidate.Part2Offset.X, Part2OffsetY = r.Candidate.Part2Offset.Y, - StrategyType = r.Candidate.StrategyType, + StrategyType = r.Candidate.StrategyIndex, TestNumber = r.Candidate.TestNumber, CandidateSpacing = r.Candidate.Spacing, RotatedArea = r.RotatedArea,