using System; using System.Collections.Generic; using System.Linq; using System.Text; using Azimuth; using Annulus.CSG; namespace Annulus.UnitTests.CSG { public class PolygonUnionTests : UnitTestSharp.TestFixture { public delegate void CheckEqualDelegate(object lhs, object rhs); public static void Test(PolygonPolygon.UnionStatus expectedStatus, PolygonPolygon.UnionStatus actualStatus, IList expectedSilhouette, IList actualSilhouette, IList> expectedHoles, IList> actualHoles, CheckEqualDelegate CheckEqual) { CheckEqual(expectedStatus, actualStatus); if(actualStatus != PolygonPolygon.UnionStatus.Success) { CheckEqual(null, expectedSilhouette); CheckEqual(null, expectedHoles); CheckEqual(null, actualSilhouette); CheckEqual(null, actualHoles); return; } expectedSilhouette = expectedSilhouette ?? new List(); expectedHoles = expectedHoles ?? new List>(); CheckEqual(expectedSilhouette.Count(), actualSilhouette.Count()); CheckEqual(expectedHoles.Count(), actualHoles.Count()); if(expectedSilhouette.Count() == actualSilhouette.Count()) { for (int i = 0; i < expectedSilhouette.Count(); ++i) { CheckEqual(expectedSilhouette[i], actualSilhouette[i]); } } if(expectedHoles.Count() == actualHoles.Count()) { for (int i = 0; i < expectedHoles.Count(); ++i) { CheckEqual(expectedHoles[i].Count(), actualHoles[i].Count()); if (expectedHoles[i].Count() != actualHoles[i].Count()) { continue; } for (int j = 0; j < expectedHoles[i].Count(); ++j) { CheckEqual(expectedHoles[i][j], actualHoles[i][j]); } } } } static IList unitBox = new Vector[] { new Vector(1,1), new Vector(1,-1), new Vector(-1,-1), new Vector(-1,1), }; static IList shiftedUnitBox = new Vector[] { new Vector(0,0), new Vector(2,0), new Vector(2,-2), new Vector(0,-2), }; static IList cShape = new Vector[] { new Vector(-2, 3), new Vector(0, 3), new Vector(0, 2), new Vector(-1, 2), new Vector(-1, -2), new Vector(0, -2), new Vector(0, -3), new Vector(-2, -3), }; static IList eShape = new Vector[] { new Vector(-2, 3), new Vector(0, 3), new Vector(0, 2), new Vector(-1, 2), new Vector(-1, 1), new Vector(0, 1), new Vector(0, -1), new Vector(-1, -1), new Vector(-1, -2), new Vector(0, -2), new Vector(0, -3), new Vector(-2, -3), }; static IList backwardsCShape = null; static IList backwardsEShape = null; public class UnionTests : UnitTestSharp.TestFixture { static UnionTests() { backwardsCShape = new List(cShape.Count()); for (int i = 0; i < cShape.Count(); ++i) { var vertex = new Vector(-1 * cShape[i].X, cShape[i].Y); backwardsCShape.Add(vertex); }; backwardsEShape = new List(eShape.Count()); for (int i = 0; i < eShape.Count(); ++i) { var vertex = new Vector(-1 * eShape[i].X, eShape[i].Y); backwardsEShape.Add(vertex); }; } IList boxBoxExpected = new Vector[] { new Vector(-1,-1), new Vector(-1,1), new Vector(1,1), new Vector(1,0), new Vector(2,0), new Vector(2,-2), new Vector(0,-2), new Vector(0,-1), }; public void BoxBox() { IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(unitBox, shiftedUnitBox, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, boxBoxExpected, silhouette, null, holes, CheckEqual); } } public void BoxBox_BadWinding() { IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(unitBox, new List(shiftedUnitBox.Reverse()), out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, boxBoxExpected, silhouette, null, holes, CheckEqual); } } public void SameInstance() { var expected = new List() { new Vector(1, 1), new Vector(1, -1), new Vector(-1, -1), new Vector(-1, 1), }; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(unitBox, unitBox, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expected, silhouette, null, holes, CheckEqual); } } public void Tris_Identical() { var tris = new Vector[] { new Vector(0, 0), new Vector(1, 0), new Vector(0, 1), }; var expected = tris; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(new List(tris), tris, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expected, silhouette, null, holes, CheckEqual); } } public void Tris_Identical_OppositeWinding() { var tris = new Vector[] { new Vector(0, 0), new Vector(0, 1), new Vector(1, 0), }; var expected = tris; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(new List(tris), tris, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expected, silhouette, null, holes, CheckEqual); } } public void Quad_Identical() { var expected = new List() { new Vector(1, 1), new Vector(1, -1), new Vector(-1, -1), new Vector(-1, 1), }; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(new List(unitBox), unitBox, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expected, silhouette, null, holes, CheckEqual); } } public void Box_Flush() { IList farAway = new Vector[] { unitBox[0] + new Vector(2, 0), unitBox[1] + new Vector(2, 0), unitBox[2] + new Vector(2, 0), unitBox[3] + new Vector(2, 0), }; IList expected = new Vector[] { new Vector(-1,-1), new Vector(-1,1), new Vector(3,1), new Vector(3,-1), }; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(unitBox, farAway, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expected, silhouette, null, holes, CheckEqual); } } public void Disjoint_A() { IList farAway = new Vector[] { unitBox[0] + new Vector(20, 0), unitBox[1] + new Vector(20, 0), unitBox[2] + new Vector(20, 0), unitBox[3] + new Vector(20, 0), }; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(unitBox, farAway, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Disjoint, status, null, silhouette, null, holes, CheckEqual); } } public void Disjoint_B() { IList farAway = new Vector[] { unitBox[0] + new Vector(20, 0), unitBox[1] + new Vector(20, 0), unitBox[2] + new Vector(20, 0), unitBox[3] + new Vector(20, 0), }; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(farAway, unitBox, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Disjoint, status, null, silhouette, null, holes, CheckEqual); } } public void Holes_Single() { IList expectedSilhouette = new Vector[] { new Vector(-2, 3), new Vector(2, 3), new Vector(2, -3), new Vector(-2, -3), }; IList expectedHole = new Vector[] { new Vector(1, -2), new Vector(1, 2), new Vector(-1, 2), new Vector(-1, -2), }; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(cShape, backwardsCShape, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, new IList[] { expectedHole }, holes, CheckEqual); } } public void Holes_Double() { IList expectedSilhouette = new Vector[] { new Vector(-2, 3), new Vector(2, 3), new Vector(2, -3), new Vector(-2, -3), }; IList expectedUpperHole = new Vector[] { new Vector(1, 1), new Vector(1, 2), new Vector(-1, 2), new Vector(-1, 1), }; IList expectedLowerHole = new Vector[] { new Vector(1, -2), new Vector(1, -1), new Vector(-1, -1), new Vector(-1, -2), }; var expectedHole = new IList[] { expectedUpperHole, expectedLowerHole }; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(eShape, backwardsEShape, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, expectedHole, holes, CheckEqual); } } public void Degenerate_One() { var degenerate = new Vector[] { new Vector(0, 0), new Vector(1, 0), new Vector(1, 0), new Vector(2, 0), new Vector(-1, 0), }; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(unitBox, degenerate, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Degenerate, status, null, silhouette, null, holes, CheckEqual); } { var status = Annulus.CSG.PolygonPolygon.Union(degenerate, unitBox, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Degenerate, status, null, silhouette, null, holes, CheckEqual); } } public void SmallerFlushed() { IList farAway = new Vector[] { new Vector(1, 2), new Vector(2, 2), new Vector(2, -2), new Vector(1, -2), }; IList expected = new Vector[] { new Vector(-1,-1), new Vector(-1,1), new Vector(1,1), new Vector(1,2), new Vector(2,2), new Vector(2,-2), new Vector(1,-2), new Vector(1,-1), }; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(unitBox, farAway, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expected, silhouette, null, holes, CheckEqual); } { var status = Annulus.CSG.PolygonPolygon.Union(farAway, unitBox, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expected, silhouette, null, holes, CheckEqual); } } #region OldHoles static IList hollowBox_outer = new Vector[] { new Vector(-2,-2), new Vector(-2,2), new Vector(2,2), new Vector(2,-2), }; static IList hollowBox_inner = new Vector[] { new Vector(-1.5, -1.5), new Vector(-1.5, 1.5), new Vector(1.5, 1.5), new Vector(1.5, -1.5), }; static IList hollowBox_offset_outer = new Vector[] { hollowBox_outer[0] + new Vector(2, 2), hollowBox_outer[1] + new Vector(2, 2), hollowBox_outer[2] + new Vector(2, 2), hollowBox_outer[3] + new Vector(2, 2), }; static IList hollowBox_offset_inner = new Vector[] { hollowBox_inner[0] + new Vector(2, 2), hollowBox_inner[1] + new Vector(2, 2), hollowBox_inner[2] + new Vector(2, 2), hollowBox_inner[3] + new Vector(2, 2), }; public void PreexistingHoles() { var expectedSilhouette = new Vector[] { new Vector(-2,-2), new Vector(-2,2), new Vector(0,2), new Vector(0,4), new Vector(4,4), new Vector(4,0), new Vector(2,0), new Vector(2,-2), }; var expectedLower = new Vector[] { new Vector(0,1.5), new Vector(-1.5,1.5), new Vector(-1.5,-1.5), new Vector(1.5,-1.5), new Vector(1.5,0), new Vector(0,0), }; var expectedUpper = new Vector[] { new Vector(.5,2), new Vector(2,2), new Vector(2,.5), new Vector(3.5,.5), new Vector(3.5,3.5), new Vector(.5,3.5), }; var expectedMid = new Vector[] { new Vector(.5, 1.5), new Vector(.5, .5), new Vector(1.5, .5), new Vector(1.5, 1.5), }; var expectedHoles = new IList[] { expectedUpper, expectedLower, expectedMid }; // Union of two overlapping hollow boxes (produces 3 holes) IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union( hollowBox_outer, hollowBox_offset_outer, new IList[] { hollowBox_inner }, new IList[] { hollowBox_offset_inner }, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, expectedHoles, holes, CheckEqual); } } public void PlugTheHole() { IList expected = new Vector[] { new Vector(-2,-2), new Vector(-2,2), new Vector(2,2), new Vector(2,-2), }; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union( hollowBox_outer, hollowBox_inner, new IList[] { hollowBox_inner }, null, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expected, silhouette, null, holes, CheckEqual); } } #endregion public void FlushOnX() { var box = new Vector[] { new Vector(-1, 10), new Vector(-.5, 10), new Vector(-.5, -10), new Vector(-1, -10), }; var expectedSilhouette = new Vector[] { new Vector(-1, 10), new Vector(-.5, 10), new Vector(-.5, 1), new Vector(1, 1), new Vector(1, -1), new Vector(-.5, -1), new Vector(-.5, -10), new Vector(-1, -10), }; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(unitBox, box, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, null, holes, CheckEqual); } { var status = Annulus.CSG.PolygonPolygon.Union(box, unitBox, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, null, holes, CheckEqual); } } public void EntirelyInside_Solid() { var box = new Vector[] { new Vector(-.25, .25), new Vector(-.25, -.25), new Vector(.25, -.25), new Vector(.25, .25), }; var expectedSilhouette = unitBox; // Union of two overlapping hollow boxes (produces 3 holes IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(unitBox, box, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, null, holes, CheckEqual); } } public void CraftyIntersection_SegementVertex() { var box = new Vector[] { new Vector(-2, 2), new Vector(0, 4), new Vector(2, 2), new Vector(0, 0), }; var expectedSilhouette = new Vector[] { new Vector(-2, 2), new Vector(0, 4), new Vector(2, 2), new Vector(1, 1), new Vector(1, -1), new Vector(-1, -1), new Vector(-1, 1), }; // Union of two overlapping hollow boxes (produces 3 holes IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(unitBox, box, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, null, holes, CheckEqual); } } public void CraftyIntersection_VertexVertex() { var box = new Vector[] { new Vector(-1, 1), new Vector(0, 4), new Vector(1, 1), new Vector(0, 0), }; var expectedSilhouette = new Vector[] { new Vector(-1, -1), new Vector(-1, 1), new Vector(0, 4), new Vector(1, 1), new Vector(1, -1), }; // Union of two overlapping hollow boxes (produces 3 holes IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(unitBox, box, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, null, holes, CheckEqual); } } public void EntirelyInside_Hollow() { var box = new Vector[] { new Vector(-.25, .25), new Vector(-.25, -.25), new Vector(.25, -.25), new Vector(.25, .25), }; var inner = new Vector[] { new Vector(-.5, .5), new Vector(-.5, -.5), new Vector(.5, -.5), new Vector(.5, .5), }; // Union of two overlapping hollow boxes (produces 3 holes IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(unitBox, box, new Vector[][] { inner }, null, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Disjoint, status, null, silhouette, null, holes, CheckEqual); } } public void CombAndSquare() { IList box = new Vector[] { new Vector(-.5,10), new Vector(-.25,10), new Vector(-.25,-10), new Vector(-.5,-10), }; var expectedSilhouette = new Vector[] { new Vector(-2, 3), new Vector(-.5, 3), new Vector(-.5, 10), new Vector(-.25, 10), new Vector(-.25, 3), new Vector(0, 3), new Vector(0, 2), new Vector(-.25, 2), new Vector(-.25, 1), new Vector(0, 1), new Vector(0, -1), new Vector(-.25, -1), new Vector(-.25, -2), new Vector(0, -2), new Vector(0, -3), new Vector(-.25, -3), new Vector(-.25, -10), new Vector(-.5, -10), new Vector(-.5, -3), new Vector(-2, -3), }; var topHole = new Vector[] { new Vector(-.5, 1), new Vector(-.5, 2), new Vector(-1, 2), new Vector(-1, 1), }; var bottomHole = new Vector[] { new Vector(-.5, -2), new Vector(-.5, -1), new Vector(-1, -1), new Vector(-1, -2), }; // Union of two overlapping hollow boxes (produces 3 holes IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(eShape, box, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, new IList[] { bottomHole, topHole }, holes, CheckEqual); } } public void TwoTriangles_NormalWinding() { var triangle1 = new Vector[] { new Vector(0,0), new Vector(1,1), new Vector(1,0), }; var triangle2 = new Vector[] { new Vector(1,0), new Vector(.5,.5), new Vector(5,0), }; var expectedSilhouette = new Vector[] { new Vector(0,0), new Vector(1,1), new Vector(1, 1/2.25), //.4 something new Vector(5, 0), }; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(triangle1, triangle2, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, null, holes, CheckEqual); } { var status = Annulus.CSG.PolygonPolygon.Union(triangle2, triangle1, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, null, holes, CheckEqual); } } public void TwoTriangles_ReverseWinding() { var triangle1 = new Vector[] { new Vector(1,0), new Vector(1,1), new Vector(0,0), }; var triangle2 = new Vector[] { new Vector(5,0), new Vector(.5,.5), new Vector(1,0), }; var expectedSilhouette = new Vector[] { new Vector(0,0), new Vector(5, 0), new Vector(1, 1/2.25), //.4 something new Vector(1,1), }; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(triangle1, triangle2, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, null, holes, CheckEqual); } { var status = Annulus.CSG.PolygonPolygon.Union(triangle2, triangle1, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, null, holes, CheckEqual); } } public void ThreeVertexMeeting() { var cave = new Vector[] { new Vector(-2, 2), new Vector(2, 2), new Vector(2, 1), new Vector(1.5, 0), new Vector(1, 1), new Vector(-1, 1), new Vector(-1, -1), new Vector(1, -1), new Vector(1.5, 0), new Vector(2, -1), new Vector(2, -2), new Vector(-2, -2), }; var box = new Vector[] { new Vector(1.5, 0), new Vector(1.5, 4), new Vector(4, 4), new Vector(4, 0), }; var expectedSilhouette = new Vector[] { new Vector(-2, 2), new Vector(1.5, 2), new Vector(1.5, 4), new Vector(4, 4), new Vector(4, 0), new Vector(1.5, 0), new Vector(1, 1), new Vector(-1, 1), new Vector(-1, -1), new Vector(1, -1), new Vector(1.5, 0), new Vector(2, -1), new Vector(2, -2), new Vector(-2, -2), }; IList silhouette; IList> holes; var status = Annulus.CSG.PolygonPolygon.Union(cave, box, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, null, holes, CheckEqual); } public void IdenticalAABBNoIntersection() { var diamond = new Vector[] { new Vector(1, 0), new Vector(0, -1), new Vector(-1, 0), new Vector(0, 1), }; var pushBox = new Vector[] { new Vector(1, 0), new Vector(0.8, -0.8), new Vector(0, -1), new Vector(-0.8, -0.8), new Vector(-1, 0), new Vector(-0.8, 0.8), new Vector(0, 1), new Vector(0.8, 0.8), }; var expectedSilhouette = pushBox; IList silhouette; IList> holes; { var status = Annulus.CSG.PolygonPolygon.Union(diamond, pushBox, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, null, holes, CheckEqual); } { var status = Annulus.CSG.PolygonPolygon.Union(pushBox, diamond, out silhouette, out holes); Test(PolygonPolygon.UnionStatus.Success, status, expectedSilhouette, silhouette, null, holes, CheckEqual); } } } } }