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:
@@ -35,35 +35,11 @@ namespace OpenNest.Geometry
|
||||
// Step 2: Quick intersection test for crossing points
|
||||
var intersectionPoints = FindCrossingPoints(a, b);
|
||||
|
||||
// Step 3: Convex decomposition
|
||||
var trisA = TriangulateWithBounds(a);
|
||||
var trisB = TriangulateWithBounds(b);
|
||||
|
||||
// Step 4: Clip all triangle pairs
|
||||
var regions = new List<Polygon>();
|
||||
|
||||
foreach (var triA in trisA)
|
||||
{
|
||||
foreach (var triB in trisB)
|
||||
{
|
||||
if (!BoundingBoxesOverlap(triA.BoundingBox, triB.BoundingBox))
|
||||
continue;
|
||||
|
||||
var clipped = ClipConvex(triA, triB);
|
||||
if (clipped != null)
|
||||
regions.Add(clipped);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Hole subtraction
|
||||
if (regions.Count > 0)
|
||||
regions = SubtractHoles(regions, holesA, holesB);
|
||||
|
||||
if (regions.Count == 0)
|
||||
return new CollisionResult(false, regions, intersectionPoints);
|
||||
// Steps 3-5: Convex decomposition, triangle-pair clipping, hole subtraction
|
||||
var regions = OverlapRegions(a, b, holesA, holesB);
|
||||
|
||||
// Step 6: Build result
|
||||
return new CollisionResult(true, regions, intersectionPoints);
|
||||
return new CollisionResult(regions.Count > 0, regions, intersectionPoints);
|
||||
}
|
||||
|
||||
public static bool HasOverlap(
|
||||
@@ -76,9 +52,10 @@ namespace OpenNest.Geometry
|
||||
if (!BoundingBoxesOverlap(a.BoundingBox, b.BoundingBox))
|
||||
return false;
|
||||
|
||||
// Full check is needed: crossing points alone miss containment cases
|
||||
// (one polygon entirely inside another has zero edge crossings).
|
||||
return Check(a, b, holesA, holesB).Overlaps;
|
||||
// Clipping decides the verdict, including containment (one polygon entirely
|
||||
// inside another has zero edge crossings). Crossing points never affect it,
|
||||
// so this overlap-only path skips them.
|
||||
return OverlapRegions(a, b, holesA, holesB).Count > 0;
|
||||
}
|
||||
|
||||
public static List<CollisionResult> CheckAll(
|
||||
@@ -121,6 +98,42 @@ namespace OpenNest.Geometry
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Positive-area overlap regions left after hole subtraction: the verdict shared by
|
||||
/// <see cref="Check"/> and <see cref="HasOverlap"/>. Callers apply the polygon-level
|
||||
/// bounding-box pre-filter first.
|
||||
/// </summary>
|
||||
private static List<Polygon> OverlapRegions(
|
||||
Polygon a,
|
||||
Polygon b,
|
||||
List<Polygon> holesA,
|
||||
List<Polygon> holesB
|
||||
)
|
||||
{
|
||||
var trisA = TriangulateWithBounds(a);
|
||||
var trisB = TriangulateWithBounds(b);
|
||||
|
||||
var regions = new List<Polygon>();
|
||||
|
||||
foreach (var triA in trisA)
|
||||
{
|
||||
foreach (var triB in trisB)
|
||||
{
|
||||
if (!BoundingBoxesOverlap(triA.BoundingBox, triB.BoundingBox))
|
||||
continue;
|
||||
|
||||
var clipped = ClipConvex(triA, triB);
|
||||
if (clipped != null)
|
||||
regions.Add(clipped);
|
||||
}
|
||||
}
|
||||
|
||||
if (regions.Count > 0)
|
||||
regions = SubtractHoles(regions, holesA, holesB);
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
private static bool BoundingBoxesOverlap(Box a, Box b)
|
||||
{
|
||||
var overlapX = System.Math.Min(a.Right, b.Right) - System.Math.Max(a.Left, b.Left);
|
||||
@@ -131,6 +144,7 @@ namespace OpenNest.Geometry
|
||||
|
||||
private static List<Vector> FindCrossingPoints(Polygon a, Polygon b)
|
||||
{
|
||||
PerfCounters.CountCrossingPointScan();
|
||||
if (!Intersect.Intersects(a, b, out var rawPts))
|
||||
return new List<Vector>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user