fix(geometry): stabilize triangulation winding at large coordinates
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user