fix(geometry): stabilize triangulation winding at large coordinates

This commit is contained in:
aj
2026-09-28 20:37:38 -04:00
parent 1f637ca43c
commit fb32d508f8
2 changed files with 75 additions and 7 deletions
@@ -148,14 +148,12 @@ namespace OpenNest.Geometry
/// </summary>
private static double SignedArea(List<Vector> verts)
{
// World-coordinate products can erase the sign of a small polygon's area
// far from the origin, leaving CW outlines/holes untriangulated. Measure
// relative to a vertex, just as the clipping kernel measures its fragments.
var area = 0.0;
for (var i = 0; i < verts.Count; i++)
{
var j = (i + 1) % verts.Count;
area += verts[i].X * verts[j].Y;
area -= verts[j].X * verts[i].Y;
}
for (var i = 1; i + 1 < verts.Count; i++)
area += Cross(verts[0], verts[i], verts[i + 1]);
return area * 0.5;
}
@@ -0,0 +1,70 @@
using OpenNest.Geometry;
namespace OpenNest.Tests.Geometry;
public class CollisionTranslationTests
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Check_ContainedClockwisePolygonFarFromOriginDoesNotDependOnOperandOrder(bool swap)
{
var outer = Square(0, 0, 1e9);
var small = Square(999999998, 999999998, 1);
small.Reverse();
var result = swap ? Collision.Check(small, outer) : Collision.Check(outer, small);
Assert.True(result.Overlaps);
// Do not use Polygon.Area here: this test isolates triangulation, not that legacy API.
Assert.Equal(1, result.OverlapRegions.Sum(StableArea), 8);
}
[Fact]
public void Check_ClockwiseHoleFarFromOriginIsSubtracted()
{
var outer = Square(0, 0, 1e9);
var hole = Square(999999998, 999999998, 1);
hole.Reverse();
var insert = Square(999999998.25, 999999998.25, 0.5);
var result = Collision.Check(outer, insert, new List<Polygon> { hole });
Assert.False(result.Overlaps);
Assert.Empty(result.OverlapRegions);
}
[Theory]
[InlineData(1000000000)]
[InlineData(-1000000000)]
public void Triangulate_ClockwiseTranslatedUnitSquareHasFullArea(double offset)
{
var polygon = Square(offset, offset, 1);
polygon.Reverse();
var triangles = ConvexDecomposition.Triangulate(polygon);
Assert.Equal(2, triangles.Count);
Assert.Equal(1, triangles.Sum(StableArea), 8);
}
private static Polygon Square(double x, double y, double size)
{
var polygon = new Polygon();
polygon.Vertices.AddRange(new[] { new Vector(x, y), new Vector(x + size, y),
new Vector(x + size, y + size), new Vector(x, y + size), new Vector(x, y) });
polygon.UpdateBounds();
return polygon;
}
private static double StableArea(Polygon polygon)
{
var area = 0.0;
var origin = polygon.Vertices[0];
for (var i = 1; i + 1 < polygon.Vertices.Count; i++)
{
var a = polygon.Vertices[i] - origin;
var b = polygon.Vertices[i + 1] - origin;
area += a.X * b.Y - a.Y * b.X;
}
return System.Math.Abs(area) / 2;
}
}