diff --git a/OpenNest.Engine/BestFit/PairEvaluator.cs b/OpenNest.Engine/BestFit/PairEvaluator.cs index c54ec5e..cdc07a3 100644 --- a/OpenNest.Engine/BestFit/PairEvaluator.cs +++ b/OpenNest.Engine/BestFit/PairEvaluator.cs @@ -58,19 +58,6 @@ namespace OpenNest.Engine.BestFit part2.Location = candidate.Part2Offset; part2.UpdateBounds(); - // Overlap check — perimeter vs perimeter. Uses Collision.HasOverlap (full polygon - // clip) rather than Shape.Intersects (edge-crossing only), which misses containment- - // style overlaps where one perimeter's boundary never crosses the other's. - var shape1 = GetPerimeterShape(part1); - var shape2 = GetPerimeterShape(part2); - var overlaps = - shape1 != null - && shape2 != null - && Collision.HasOverlap( - shape1.ToPolygonWithTolerance(OverlapChordTolerance), - shape2.ToPolygonWithTolerance(OverlapChordTolerance) - ); - // Convex hull vertices from perimeter polygons only var allPoints = GetPartVertices(part1); allPoints.AddRange(GetPartVertices(part2)); @@ -106,7 +93,9 @@ namespace OpenNest.Engine.BestFit var trueArea = candidate.Drawing.Area * 2; - // Normalize to landscape (width >= height) for consistent display. + // Normalize to landscape (width >= height) for consistent display. Do this before + // the overlap check so bestRotation already matches the final OptimalRotation that + // BuildParts will apply. if (bestHeight > bestWidth) { var tmp = bestWidth; @@ -115,6 +104,33 @@ namespace OpenNest.Engine.BestFit bestRotation += Angle.HalfPI; } + // Overlap check — perimeter vs perimeter, in the same final orientation BuildParts + // uses downstream. Uses Collision.HasOverlap (full polygon clip) rather than + // Shape.Intersects (edge-crossing only), which misses containment-style overlaps + // where one perimeter's boundary never crosses the other's. Checking pre-rotation + // geometry here (rather than rotating part1/part2 first, matching BuildParts) would + // tessellate arcs at a different orientation than the geometry actually gets placed + // with, letting tangent-corner slivers slip through in one frame but not the other. + if (!bestRotation.IsEqualTo(0)) + { + var pairBounds = ( + (IEnumerable)new IBoundable[] { part1, part2 } + ).GetBoundingBox(); + var center = pairBounds.Center; + part1.Rotate(-bestRotation, center); + part2.Rotate(-bestRotation, center); + } + + var shape1 = GetPerimeterShape(part1); + var shape2 = GetPerimeterShape(part2); + var overlaps = + shape1 != null + && shape2 != null + && Collision.HasOverlap( + shape1.ToPolygonWithTolerance(OverlapChordTolerance), + shape2.ToPolygonWithTolerance(OverlapChordTolerance) + ); + return new BestFitResult { Candidate = candidate,