using System; using System.Collections.Generic; using System.Linq; using System.Text; using Azimuth; using Annulus.CSG; namespace Annulus.UnitTests.CSG { public class TriangulationTests : UnitTestSharp.TestFixture { public class GlomTests : UnitTestSharp.TestFixture { public void Triangle() { var verts = new Vector[] { new Vector(-1, 1), new Vector(-1,-1), new Vector( 1,-1), }; var glomed = Annulus.CSG.Triangulation.GlomPolygon(new PerforatedPolygon(new SimplePolygon(verts))); CheckEqual(verts.Length, glomed.Count); for (int i = 0; i < Math.Min(verts.Length, glomed.Count); ++i) { CheckEqual(verts[i], glomed[i]); } } public void Donut_1() { var silhouette = new Vector[] { new Vector(-1, 1), new Vector(-1,-1), new Vector( 1,-1), new Vector( 1, 1), }; var hole = new Vector[] { new Vector(-.6, .6), new Vector(-.5,-.5), new Vector( .5,-.5), new Vector( .5, .5), }; var expected = new Vector[] { new Vector(-1, 1), new Vector(-.6, .6), new Vector( .5, .5), new Vector( .5,-.5), new Vector(-.5,-.5), new Vector(-.6, .6), new Vector(-1, 1), new Vector(-1,-1), new Vector( 1,-1), new Vector( 1, 1), }; var glomed = Annulus.CSG.Triangulation.GlomPolygon(new PerforatedPolygon( new SimplePolygon(silhouette), new SimplePolygon[] { new SimplePolygon(hole), } )); CheckEqual(expected.Length, glomed.Count); for (int i = 0; i < Math.Min(expected.Length, glomed.Count); ++i) { CheckEqual(expected[i], glomed[i]); } } public void Donut_2() { var silhouette = new Vector[] { new Vector(-1, 1), new Vector(-1,-1), new Vector( 1,-1), new Vector( 1, 1), }; var hole = new Vector[] { new Vector(-.5,-.5), new Vector( .5,-.5), new Vector( .5, .5), new Vector(-.6, .6), }; var expected = new Vector[] { new Vector(-1, 1), new Vector(-.6, .6), new Vector( .5, .5), new Vector( .5,-.5), new Vector(-.5,-.5), new Vector(-.6, .6), new Vector(-1, 1), new Vector(-1,-1), new Vector( 1,-1), new Vector( 1, 1), }; var glomed = Annulus.CSG.Triangulation.GlomPolygon(new PerforatedPolygon( new SimplePolygon(silhouette), new SimplePolygon[] { new SimplePolygon(hole), } )); CheckEqual(expected.Length, glomed.Count); for (int i = 0; i < Math.Min(expected.Length, glomed.Count); ++i) { CheckEqual(expected[i], glomed[i]); } } public void Donut_Winding() { var silhouette = new Vector[] { new Vector(-1, 1), new Vector(-1,-1), new Vector( 1,-1), new Vector( 1, 1), }; var hole = new Vector[] { new Vector(-.6, .6), new Vector( .5, .5), new Vector( .5,-.5), new Vector(-.5,-.5), }; var expected = new Vector[] { new Vector(-1, 1), new Vector(-.6, .6), new Vector( .5, .5), new Vector( .5,-.5), new Vector(-.5,-.5), new Vector(-.6, .6), new Vector(-1, 1), new Vector(-1,-1), new Vector( 1,-1), new Vector( 1, 1), }; var glomed = Annulus.CSG.Triangulation.GlomPolygon(new PerforatedPolygon( new SimplePolygon(silhouette), new SimplePolygon[] { new SimplePolygon(hole), } )); CheckEqual(expected.Length, glomed.Count); for (int i = 0; i < Math.Min(expected.Length, glomed.Count); ++i) { CheckEqual(expected[i], glomed[i]); } } public void Donut_Silhouette_Winding() { var silhouette = new Vector[] { new Vector( 1, 1), new Vector( 1,-1), new Vector(-1,-1), new Vector(-1, 1), }; var hole = new Vector[] { new Vector(-.6, .6), new Vector( .5, .5), new Vector( .5,-.5), new Vector(-.5,-.5), }; var expected = new Vector[] { new Vector( 1, 1), new Vector( 1,-1), new Vector(-1,-1), new Vector(-1, 1), new Vector(-.6, .6), new Vector(-.5,-.5), new Vector( .5,-.5), new Vector( .5, .5), new Vector(-.6, .6), new Vector(-1, 1), }; var glomed = Annulus.CSG.Triangulation.GlomPolygon(new PerforatedPolygon( new SimplePolygon(silhouette), new SimplePolygon[] { new SimplePolygon(hole), } )); CheckEqual(expected.Length, glomed.Count); for (int i = 0; i < Math.Min(expected.Length, glomed.Count); ++i) { CheckEqual(expected[i], glomed[i]); } } public void Window() { var silhouette = new Vector[] { new Vector( 2, 2), new Vector( 2,-2), new Vector(-2,-2), new Vector(-2, 2), }; var holeNE = new Vector[] { new Vector( .6,1.6), new Vector(1.5,1.5), new Vector(1.5, .5), new Vector( .5, .5), }; var holeNW = new Vector[] { new Vector(-.6,1.6), new Vector(-1.5,1.5), new Vector(-1.5, .5), new Vector(-.5, .5), }; var holeSE = new Vector[] { new Vector( .6,-1.6), new Vector(1.5,-1.5), new Vector(1.5,-.5), new Vector( .5,-.5), }; var holeSW = new Vector[] { new Vector(-.6,-1.6), new Vector(-1.5,-1.5), new Vector(-1.5,-.5), new Vector(-.5,-.5), }; var expected = new Vector[] { new Vector(2, 2), new Vector(1.5, 1.5), new Vector(0.6, 1.6), new Vector(0.5, 0.5), new Vector(1.5, 0.5), new Vector(1.5, 1.5), new Vector(2, 2), new Vector(2, -2), new Vector(1.5, -1.5), new Vector(1.5, -0.5), new Vector(0.5, -0.5), new Vector(0.6, -1.6), new Vector(1.5, -1.5), new Vector(2, -2), new Vector(-2, -2), new Vector(-1.5, -1.5), new Vector(-0.6, -1.6), new Vector(-0.5, -0.5), new Vector(-1.5, -0.5), new Vector(-1.5, -1.5), new Vector(-2, -2), new Vector(-2, 2), new Vector(-1.5, 1.5), new Vector(-1.5, 0.5), new Vector(-0.5, 0.5), new Vector(-0.6, 1.6), new Vector(-1.5, 1.5), new Vector(-2, 2), }; var glomed = Annulus.CSG.Triangulation.GlomPolygon(new PerforatedPolygon( new SimplePolygon(silhouette), new SimplePolygon[] { new SimplePolygon(holeNW), new SimplePolygon(holeSW), new SimplePolygon(holeNE), new SimplePolygon(holeSE), } )); CheckEqual(expected.Length, glomed.Count); for (int i = 0; i < Math.Min(expected.Length, glomed.Count); ++i) { CheckEqual(expected[i], glomed[i]); } } public void InARow() { var silhouette = new Vector[] { new Vector( 0, 2), new Vector( 0,-2), new Vector( 7,-2), new Vector( 7, 2), }; var hole1 = new Vector[] { new Vector( 2, 1), new Vector( 2,-1), new Vector( 1,-1), new Vector( 1, 1), }; var hole2 = new Vector[] { new Vector( 4, 1), new Vector( 4,-1), new Vector( 3,-1), new Vector( 3, 1), }; var hole3 = new Vector[] { new Vector( 6, 1), new Vector( 6,-1), new Vector( 5,-1), new Vector( 5, 1), }; var expected = new Vector[] { new Vector(0, 2), new Vector(1, 1), new Vector(2, 1), new Vector(3, 1), new Vector(4, 1), new Vector(5, 1), new Vector(6, 1), new Vector(6, -1), new Vector(5, -1), new Vector(5, 1), new Vector(4, 1), new Vector(4, -1), new Vector(3, -1), new Vector(3, 1), new Vector(2, 1), new Vector(2, -1), new Vector(1, -1), new Vector(1, 1), new Vector(0, 2), new Vector(0, -2), new Vector(7, -2), new Vector(7, 2), }; var glomed = Annulus.CSG.Triangulation.GlomPolygon(new PerforatedPolygon( new SimplePolygon(silhouette), new SimplePolygon[] { new SimplePolygon(hole1), new SimplePolygon(hole2), new SimplePolygon(hole3), } )); CheckEqual(expected.Length, glomed.Count); for (int i = 0; i < Math.Min(expected.Length, glomed.Count); ++i) { CheckEqual(expected[i], glomed[i]); } } } public class TriangulatorTests: UnitTestSharp.TestFixture { public void Quad() { var silhouette = new SimplePolygon(new Vector[] { new Vector(-1, 1), new Vector(-1,-1), new Vector( 1,-1), new Vector( 1, 1), }); var expected = new Triangulation.Triangle[] { new Triangulation.Triangle { vertices = silhouette, indices = new int[] { 0, 1, 2}, }, new Triangulation.Triangle { vertices = silhouette, indices = new int[] { 2, 3, 0}, }, }; Polygon vertices; List tris; Triangulation.Triangulate(new PerforatedPolygon(silhouette), out vertices, out tris); CheckEqual(2, tris.Count); if (tris.Count == 2) { CheckEqual(expected[0], tris[0]); CheckEqual(expected[1], tris[1]); Check(tris[0].SignedArea * silhouette.SignedArea >= 0); Check(tris[1].SignedArea * silhouette.SignedArea >= 0); } } public void Empty() { var silhouette = new SimplePolygon(new Vector[] { }); Polygon vertices; List tris; Triangulation.Triangulate(new PerforatedPolygon(silhouette), out vertices, out tris); CheckEqual(0, tris.Count); } public void One() { var silhouette = new SimplePolygon(new Vector[] { new Vector(1,1), }); Polygon vertices; List tris; var poly = new PerforatedPolygon(silhouette); Triangulation.Triangulate(poly, out vertices, out tris); CheckEqual(0, tris.Count); CheckEqual(0, vertices.Count); } public void Two() { var silhouette = new SimplePolygon(new Vector[] { new Vector(0,0), new Vector(1,1), }); Polygon vertices; List tris; Triangulation.Triangulate(new PerforatedPolygon(silhouette), out vertices, out tris); CheckEqual(0, tris.Count); CheckEqual(0, vertices.Count); } public void Tris() { var silhouette = new SimplePolygon(new Vector[] { new Vector(1,1), new Vector(0,0), new Vector(1,0), }); Polygon vertices; List tris; Triangulation.Triangulate(new PerforatedPolygon(silhouette), out vertices, out tris); CheckEqual(1, tris.Count); CheckEqual(3, vertices.Count); if (tris.Count == 1) { CheckEqual(new Triangulation.Triangle { vertices = silhouette, indices = new int[] { 0, 1, 2 }, }, tris[0]); } } public void Donut() { var silhouette = new SimplePolygon(new Vector[] { new Vector(-1, 1), new Vector(-1,-1), new Vector( 1,-1), new Vector( 1, 1), }); var hole = new SimplePolygon(new Vector[] { new Vector(-.6, .6), new Vector(-.5,-.5), new Vector( .5,-.5), new Vector( .5, .5), }); var expectedVerts = new Polygon(new Vector[] { new Vector(-1, 1), new Vector(-0.6, 0.6), new Vector(0.5, 0.5), new Vector(0.5, -0.5), new Vector(-0.5, -0.5), new Vector(-0.6, 0.6), new Vector(-1, 1), new Vector(-1, -1), new Vector(1, -1), new Vector(1, 1), }); var expected = new Triangulation.Triangle[] { new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 1, 2, 6}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 4, 5, 0}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 6, 7, 4, }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 7, 8, 4}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 9, 0, 2}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 2, 3, 9}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 3, 4, 8}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 8, 9, 3}, }, }; Polygon vertices; List tris; Triangulation.Triangulate(new PerforatedPolygon(silhouette, new SimplePolygon[] { hole }), out vertices, out tris); CheckEqual(expected.Length, tris.Count); if (tris.Count == expected.Length) { for (int i = 0; i < tris.Count; ++i) { CheckEqual(expected[i], tris[i]); } } } public void InARow() { var silhouette = new SimplePolygon(new Vector[] { new Vector( 0, 2), new Vector( 0,-2), new Vector( 7,-2), new Vector( 7, 2), }); var hole1 = new SimplePolygon(new Vector[] { new Vector( 2, 1), new Vector( 2,-1), new Vector( 1,-1), new Vector( 1, 1), }); var hole2 = new SimplePolygon(new Vector[] { new Vector( 4, 1), new Vector( 4,-1), new Vector( 3,-1), new Vector( 3, 1), }); var hole3 = new SimplePolygon(new Vector[] { new Vector( 6, 1), new Vector( 6,-1), new Vector( 5,-1), new Vector( 5, 1), }); var expectedVerts = new Polygon(new Vector[] { new Vector(0, 2), new Vector(1, 1), new Vector(2, 1), new Vector(3, 1), new Vector(4, 1), new Vector(5, 1), new Vector(6, 1), new Vector(6, -1), new Vector(5, -1), new Vector(5, 1), new Vector(4, 1), new Vector(4, -1), new Vector(3, -1), new Vector(3, 1), new Vector(2, 1), new Vector(2, -1), new Vector(1, -1), new Vector(1, 1), new Vector(0, 2), new Vector(0, -2), new Vector(7, -2), new Vector(7, 2), }); var expected = new Triangulation.Triangle[] { new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 1, 2, 18}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 14, 3, 0}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 3, 4, 0}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 10, 5, 0}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 5, 6, 0}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 8, 9, 4}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 10, 11, 8}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 12, 13, 2}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 14, 15, 12}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 16, 17, 0}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 18, 19, 16}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 19, 20, 16, }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 21, 0, 6 }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 6, 7, 21 }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 15, 16, 20, }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 20, 21, 7, }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 7, 8, 20, }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 20, 8, 11, }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 11, 12, 20, }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 20, 12, 15, }, }, }; Polygon vertices; List tris; Annulus.CSG.Triangulation.Triangulate( new PerforatedPolygon( silhouette, new SimplePolygon[] { hole1, hole2, hole3 } ), out vertices, out tris ); CheckEqual(expectedVerts.Count, vertices.Count); for (int i = 0; i < expectedVerts.Count; ++i) { CheckEqual(expectedVerts[i], vertices[i]); } CheckEqual(expected.Length, tris.Count); if (tris.Count == expected.Length) { for (int i = 0; i < tris.Count; ++i) { CheckEqual(expected[i], tris[i]); } } } public void PreGlomed() { var expectedVerts = new Polygon(new Vector[] { new Vector(0, 2), new Vector(1, 1), new Vector(2, 1), new Vector(3, 1), new Vector(4, 1), new Vector(5, 1), new Vector(6, 1), new Vector(6, -1), new Vector(5, -1), new Vector(5, 1), new Vector(4, 1), new Vector(4, -1), new Vector(3, -1), new Vector(3, 1), new Vector(2, 1), new Vector(2, -1), new Vector(1, -1), new Vector(1, 1), new Vector(0, 2), new Vector(0, -2), new Vector(7, -2), new Vector(7, 2), }); var expected = new Triangulation.Triangle[] { new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 1, 2, 18}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 14, 3, 0}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 3, 4, 0}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 10, 5, 0}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 5, 6, 0}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 8, 9, 4}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 10, 11, 8}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 12, 13, 2}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 14, 15, 12}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 16, 17, 0}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 18, 19, 16}, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 19, 20, 16, }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 21, 0, 6 }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 6, 7, 21 }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 15, 16, 20, }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 20, 21, 7, }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 7, 8, 20, }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 20, 8, 11, }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 11, 12, 20, }, }, new Triangulation.Triangle { vertices = expectedVerts, indices = new int[] { 20, 12, 15, }, }, }; Polygon vertices; List tris; Triangulation.Triangulate(new PerforatedPolygon(new SimplePolygon(expectedVerts.VertexList)), out vertices, out tris); CheckEqual(expectedVerts.Count, vertices.Count); for (int i = 0; i < expectedVerts.Count; ++i) { CheckEqual(expectedVerts[i], vertices[i]); } CheckEqual(expected.Length, tris.Count); if (tris.Count == expected.Length) { for (int i = 0; i < tris.Count; ++i) { CheckEqual(expected[i], tris[i]); } } } } public class CanUseTriangleTests : UnitTestSharp.TestFixture { public void Good() { var vertices = new Vector[] { new Vector(0,0), new Vector(0,1), new Vector(1,0), }; var triangle = new Triangulation.Triangle { vertices = new Polygon(vertices), indices = new int[] { 0, 1, 2, }, }; Scalar winding = 1; var sortNsweep = new Annulus.Broadphase.SortAndSweep( vertices, vertices.Select( x => new Interval(x.X) ).ToList() ); bool canUse = Triangulation.CanUseTriangle(triangle, winding, sortNsweep); CheckTrue(canUse); } public void VertexInside() { var vertices = new Vector[] { new Vector(0,0), new Vector(0,1), new Vector(1,0), new Vector(0.25,0.25), }; var triangle = new Triangulation.Triangle { vertices = new Polygon(vertices), indices = new int[] { 0, 1, 2, }, }; Scalar winding = 1; var sortNsweep = new Annulus.Broadphase.SortAndSweep( vertices, vertices.Select( x => new Interval(x.X) ).ToList() ); bool canUse = Triangulation.CanUseTriangle(triangle, winding, sortNsweep); CheckFalse(canUse); } public void BadWinding() { var vertices = new Vector[] { new Vector(0,0), new Vector(1,0), new Vector(0,1), }; var triangle = new Triangulation.Triangle { vertices = new Polygon(vertices), indices = new int[] { 0, 1, 2, }, }; Scalar winding = 1; var sortNsweep = new Annulus.Broadphase.SortAndSweep( vertices, vertices.Select( x => new Interval(x.X) ).ToList() ); bool canUse = Triangulation.CanUseTriangle(triangle, winding, sortNsweep); CheckFalse(canUse); } public void Degenerate_1() { var vertices = new Vector[] { new Vector(0,0), new Vector(0,0), new Vector(0,1), }; var triangle = new Triangulation.Triangle { vertices = new Polygon(vertices), indices = new int[] { 0, 1, 2, }, }; Scalar winding = 1; var sortNsweep = new Annulus.Broadphase.SortAndSweep( vertices, vertices.Select( x => new Interval(x.X) ).ToList() ); bool canUse = Triangulation.CanUseTriangle(triangle, winding, sortNsweep); CheckFalse(canUse); } public void Degenerate_2() { var vertices = new Vector[] { new Vector(0,0), new Vector(0,0), new Vector(0,1), }; var triangle = new Triangulation.Triangle { vertices = new Polygon(vertices), indices = new int[] { 0, 1, 2, }, }; Scalar winding = -1; var sortNsweep = new Annulus.Broadphase.SortAndSweep( vertices, vertices.Select( x => new Interval(x.X) ).ToList() ); bool canUse = Triangulation.CanUseTriangle(triangle, winding, sortNsweep); CheckFalse(canUse); } public void BadExpectedWinding_1() { var vertices = new Vector[] { new Vector(0,0), new Vector(0,0), new Vector(0,1), }; var triangle = new Triangulation.Triangle { vertices = new Polygon(vertices), indices = new int[] { 0, 1, 2, }, }; Scalar winding = 0; var sortNsweep = new Annulus.Broadphase.SortAndSweep( vertices, vertices.Select( x => new Interval(x.X) ).ToList() ); CheckThrow(typeof(Exception)); bool canUse = Triangulation.CanUseTriangle(triangle, winding, sortNsweep); } } public class FindSharedEdgesTests : UnitTestSharp.TestFixture { public void NoEdgesOverlap() { var edges = new Polygon(new Vector[] { new Vector(-1, 1), new Vector(-1,-1), new Vector( 1,-1), new Vector( 1, 1), }).Edges; var shared = Triangulation.FindSharedEdges(edges); CheckEqual(0, shared.Count); } public void AntiParallel() { var edges = new Polygon(new Vector[] { new Vector(-1, 1), new Vector(-1,-1), new Vector( 1,-1), new Vector( 1, 1), new Vector(-1, 1), new Vector( 1, 1), new Vector( 1,-1), new Vector(-1,-1), new Vector(-1, 1), }).Edges; var shared = Triangulation.FindSharedEdges(edges); CheckEqual(4, shared.Count); } public void Parallel() { var edges = new Polygon(new Vector[] { new Vector(-1, 1), new Vector(-1,-1), new Vector( 1,-1), new Vector( 1, 1), new Vector(-1, 1), new Vector(-1,-1), new Vector( 1,-1), new Vector( 1, 1), }).Edges; // var shared = Triangulation.FindSharedEdges(edges); TODO("The triangulator needs to be able to handle non parallel edges."); } } public class CutEdgesWithNearbyVerticesTests : UnitTestSharp.TestFixture { public void Todo() { TODO("CutEdgesWithNearbyVertices tests."); } } } }