perf(core): skip unused crossing points in overlap-only checks

Collision.HasOverlap only needs the verdict, but it went through Check,
which also collected crossing points. Triangulation, clipping and hole
subtraction now live in one private OverlapRegions method shared by Check
and HasOverlap, so verdict arithmetic stays single-sourced; Check output is
unchanged.

Tests: a frozen copy of the previous Collision is the oracle. 50,000 seeded
HasOverlap verdicts and 2,400 bitwise Check results match it, plus
containment, contact, hole and input-immutability cases. A Debug-only
PerfCounters.CrossingPointScans counter proves HasOverlap no longer scans.
Malformed polygons with null outer vertices still throw when the bounding
boxes overlap (now ArgumentNullException from triangulation rather than
NullReferenceException from ToLines).

Measured (Release, same harness in both trees): about 44% less time per
overlap-only polygon check, allocations 10.0 -> 7.9 MB per 155-pair sweep.
The 169-part serialized corpus layout is byte-identical.
This commit is contained in:
aj
2026-09-27 12:54:20 -04:00
parent a98a49c00a
commit 2b5485f6cf
4 changed files with 679 additions and 30 deletions
+6
View File
@@ -16,6 +16,7 @@ namespace OpenNest
private static long partBoundaryPreparations;
private static long partBoundsUpdates;
private static long featureBitmaskCells;
private static long crossingPointScans;
public static long FindBestFits => Interlocked.Read(ref findBestFits);
public static long OffsetPerimeterEntities => Interlocked.Read(ref offsetPerimeterEntities);
@@ -24,6 +25,7 @@ namespace OpenNest
public static long PartBoundaryPreparations => Interlocked.Read(ref partBoundaryPreparations);
public static long PartBoundsUpdates => Interlocked.Read(ref partBoundsUpdates);
public static long FeatureBitmaskCells => Interlocked.Read(ref featureBitmaskCells);
public static long CrossingPointScans => Interlocked.Read(ref crossingPointScans);
[Conditional("DEBUG")]
public static void CountFindBestFits() => Interlocked.Increment(ref findBestFits);
@@ -47,6 +49,9 @@ namespace OpenNest
[Conditional("DEBUG")]
public static void CountFeatureBitmaskCell() => Interlocked.Increment(ref featureBitmaskCells);
[Conditional("DEBUG")]
public static void CountCrossingPointScan() => Interlocked.Increment(ref crossingPointScans);
public static void Reset()
{
Interlocked.Exchange(ref findBestFits, 0);
@@ -56,6 +61,7 @@ namespace OpenNest
Interlocked.Exchange(ref partBoundaryPreparations, 0);
Interlocked.Exchange(ref partBoundsUpdates, 0);
Interlocked.Exchange(ref featureBitmaskCells, 0);
Interlocked.Exchange(ref crossingPointScans, 0);
}
}
}