mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-03 20:31:35 -05:00
The overlap crosshair sat on the pair's area centroid. For a split,
holed or concave overlap, that point can be off the red material:
between two patches, in a hole, or inside a curve.
PlateOverlapPair.Marker is a point inside the pair's largest island.
It is the island's pole of inaccessibility (PolyLabel), searched to a
tenth of the island's area/perimeter within a 4096-cell budget. If
that bounded search does not land inside (a long thin sliver), the
marker falls back to the midpoint of the widest interior run of a
horizontal scan line placed between vertex heights.
The overlay crosshair, marker hit testing, hover coordinates
("Marker:") and the cutting-inspection ring now use the marker.
Centroid stays the true area centroid. View > Overlap Check > Display
> Centroids is now labelled Markers.
Before the marker existed, its call sites used the centroid. Two
patches, a covered frame, a half annulus and a thin crescent then put
it off the material; now all four land on it. Disabling the scan-line
fallback fails its test. On the 389-plate corpus, markers add about
0.4 ms per overlapping pair; pair and area results are unchanged.
49 lines
1.8 KiB
C#
49 lines
1.8 KiB
C#
using OpenNest.Diagnostics;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Tests.Diagnostics;
|
|
|
|
public class OverlapMarkerTests
|
|
{
|
|
// 4x4 frame with a 2x2 hole: the area centroid (2, 2) is in the hole.
|
|
private static readonly List<Vector> Frame = Closed(new Vector(0, 0), new Vector(4, 0), new Vector(4, 4), new Vector(0, 4));
|
|
private static readonly List<List<Vector>> FrameHole =
|
|
new() { Closed(new Vector(1, 1), new Vector(1, 3), new Vector(3, 3), new Vector(3, 1)) };
|
|
|
|
[Fact]
|
|
public void PoleIsDeepInsideTheMaterial()
|
|
{
|
|
var marker = OverlapMarker.Find(Frame, FrameHole, 12, new Vector(2, 2));
|
|
|
|
// The frame's bars are 1 wide: the pole sits on a bar's centre line.
|
|
Assert.True(Inside(marker), $"{marker.X:R}, {marker.Y:R}");
|
|
var depth = new[] { marker.X, 4 - marker.X, marker.Y, 4 - marker.Y }.Min();
|
|
Assert.InRange(depth, 0.4, 0.6);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExhaustedSearchFallsBackToAnInteriorScanLinePoint()
|
|
{
|
|
// One cell of budget: the search cannot leave the hole-centred bounding-box cell.
|
|
var marker = OverlapMarker.Find(Frame, FrameHole, 12, new Vector(2, 2), maxCells: 1);
|
|
|
|
Assert.True(Inside(marker), $"{marker.X:R}, {marker.Y:R}");
|
|
}
|
|
|
|
[Fact]
|
|
public void NoInteriorRunReturnsTheFallback()
|
|
{
|
|
var fallback = new Vector(7, 7);
|
|
var marker = OverlapMarker.Find(Closed(new Vector(0, 0), new Vector(1, 0), new Vector(2, 0)),
|
|
new List<List<Vector>>(), 0, fallback, maxCells: 1);
|
|
|
|
Assert.Equal(fallback, marker);
|
|
}
|
|
|
|
private static bool Inside(Vector point) =>
|
|
point.X > 0 && point.X < 4 && point.Y > 0 && point.Y < 4
|
|
&& !(point.X >= 1 && point.X <= 3 && point.Y >= 1 && point.Y <= 3);
|
|
|
|
private static List<Vector> Closed(params Vector[] points) => points.Append(points[0]).ToList();
|
|
}
|