Files
OpenNest/OpenNest.Tests/Diagnostics/OverlapMarkerTests.cs
T
aj 80d01839bd feat(diagnostics): place overlap markers on the shared material
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.
2026-10-15 23:31:14 -04:00

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();
}