using System; using Annulus.BezierCurves; using Annulus.CSG; using Azimuth; namespace Annulus.UnitTests.CSG { public class BoundaryInvariantsTests : UnitTestSharp.TestFixture { public static BoundaryPart Part(Vector start, Vector control, Vector end) { QuadraticBezierCurve curve = DiskQueryTests.Piece(start, control, end); return new BoundaryPart { Stretch = 5, Region = 2, StartCut = 3, EndCut = 7, Curve = curve, BowsIn = BoundaryParts.BowsIntoTheShape(curve), }; } // Bows out of the shape, with its triangle under the line from (0, 0) to (2, 0). public static BoundaryPart Bulge() { return Part(new Vector(0, 0), new Vector(1, -1), new Vector(2, 0)); } public class FindSplitTests : UnitTestSharp.TestFixture { public void PointPart_NeedsNoSplit() { BoundaryPart part = Part(new Vector(1, 1), new Vector(1, 1), new Vector(1, 1)); part.IsPoint = true; Scalar parameter; CheckFalse(BoundaryInvariants.FindSplit(part, new Vector[] { new Vector(0, 0), new Vector(2, 2) }, out parameter)); } public void StraightPart_NeedsNoSplit() { BoundaryPart part = Part(new Vector(0, 0), new Vector(1, 0), new Vector(2, 0)); Scalar parameter; CheckFalse(BoundaryInvariants.FindSplit(part, new Vector[] { new Vector(1, -1), new Vector(1, 1) }, out parameter)); } public void OutlineThatKeepsAwayFromTheTriangle_NeedsNoSplit() { Vector[] outline = { new Vector(0, 0), new Vector(2, 0), new Vector(1, 2) }; Scalar parameter; CheckFalse(BoundaryInvariants.FindSplit(Bulge(), outline, out parameter)); } public void EdgeCrossingTheStartTangent_SplitsWhereItCrossesIt() { Vector[] outline = { new Vector(0.3, -1), new Vector(0.3, 0.5) }; Scalar parameter; CheckTrue(BoundaryInvariants.FindSplit(Bulge(), outline, out parameter)); CheckEqual((Scalar)0.3, parameter); } public void EdgeCrossingTheEndTangent_SplitsWhereItCrossesIt() { Vector[] outline = { new Vector(1.7, -1), new Vector(1.7, 0.5) }; Scalar parameter; CheckTrue(BoundaryInvariants.FindSplit(Bulge(), outline, out parameter)); CheckEqual((Scalar)0.7, parameter); } public void EdgeCrossingBothTangents_SplitsAtTheStartTangent() { Vector[] outline = { new Vector(0.3, -1), new Vector(0.3, 0.5), new Vector(1.7, 0.5), new Vector(1.7, -1) }; Scalar parameter; CheckTrue(BoundaryInvariants.FindSplit(Bulge(), outline, out parameter)); CheckEqual((Scalar)0.3, parameter); } public void EdgeCrossingOnlyTheChord_SplitsInHalf() { Vector[] outline = { new Vector(1, 0.5), new Vector(1, -0.2) }; Scalar parameter; CheckTrue(BoundaryInvariants.FindSplit(Bulge(), outline, out parameter)); CheckEqual((Scalar)0.5, parameter); } public void CrossingTooCloseToTheStart_SplitsInHalfInstead() { Vector[] outline = { new Vector(1e-8, -1), new Vector(1e-8, 0.5) }; Scalar parameter; CheckTrue(BoundaryInvariants.FindSplit(Bulge(), outline, out parameter)); CheckEqual((Scalar)0.5, parameter); } public void EdgeFromTheStartInsideTheCorner_SplitsWhereTheChordReachesIt() { Vector[] outline = { new Vector(0, 0), new Vector(2, -0.5), new Vector(2, 0) }; Scalar parameter; CheckTrue(BoundaryInvariants.FindSplit(Bulge(), outline, out parameter)); CheckEqual((Scalar)0.75, parameter); } public void EdgeFromTheEndInsideTheCorner_SplitsWhereTheChordReachesIt() { Vector[] outline = { new Vector(2, 0), new Vector(0, -0.5), new Vector(0, 0) }; Scalar parameter; CheckTrue(BoundaryInvariants.FindSplit(Bulge(), outline, out parameter)); CheckEqual((Scalar)0.25, parameter); } public void EdgeFromTheStartOutsideTheCorner_NeedsNoSplit() { Vector[] outline = { new Vector(0, 0), new Vector(0, 2), new Vector(2, 0) }; Scalar parameter; CheckFalse(BoundaryInvariants.FindSplit(Bulge(), outline, out parameter)); } public void BowingInPart_IsTestedAgainstTheEdgesOtherThanItsOwn() { BoundaryPart part = Part(new Vector(0, 0), new Vector(1, 1), new Vector(2, 0)); Vector[] clear = { new Vector(0, 0), new Vector(1, 1), new Vector(2, 0), new Vector(1, -2) }; Vector[] crossed = { new Vector(0, 0), new Vector(1, 1), new Vector(2, 0), new Vector(0.5, 2), new Vector(0.4, -1) }; Scalar parameter; CheckTrue(part.BowsIn); CheckFalse(BoundaryInvariants.FindSplit(part, clear, out parameter)); CheckTrue(BoundaryInvariants.FindSplit(part, crossed, out parameter)); } } public class SplitTests : UnitTestSharp.TestFixture { public void CutsTheCurveAtTheParameter() { BoundaryPart first; BoundaryPart second; BoundaryInvariants.Split(Bulge(), 0.5, out first, out second); CheckEqual(new Vector(0, 0), first.Curve.startPoint); CheckEqual(new Vector(0.5, -0.5), first.Curve.controlPoint); CheckEqual(new Vector(1, -0.5), first.Curve.endPoint); CheckEqual(new Vector(1, -0.5), second.Curve.startPoint); CheckEqual(new Vector(1.5, -0.5), second.Curve.controlPoint); CheckEqual(new Vector(2, 0), second.Curve.endPoint); } public void OnlyTheOuterEndsKeepTheirCuts() { BoundaryPart first; BoundaryPart second; BoundaryInvariants.Split(Bulge(), 0.5, out first, out second); CheckEqual(3, first.StartCut); CheckEqual(-1, first.EndCut); CheckEqual(-1, second.StartCut); CheckEqual(7, second.EndCut); } public void BothHalvesKeepTheStretchAndRegion() { BoundaryPart first; BoundaryPart second; BoundaryInvariants.Split(Bulge(), 0.5, out first, out second); CheckEqual(5, first.Stretch); CheckEqual(2, first.Region); CheckEqual(5, second.Stretch); CheckEqual(2, second.Region); } public void HalvesOfABowingInPartBowInToo() { BoundaryPart first; BoundaryPart second; BoundaryInvariants.Split(Part(new Vector(0, 0), new Vector(1, 1), new Vector(2, 0)), 0.5, out first, out second); CheckTrue(first.BowsIn); CheckTrue(second.BowsIn); } public void HalvesOfABowingOutPartBowOutToo() { BoundaryPart first; BoundaryPart second; BoundaryInvariants.Split(Bulge(), 0.5, out first, out second); CheckFalse(first.BowsIn); CheckFalse(second.BowsIn); } } public class EnforceTests : UnitTestSharp.TestFixture { [UnitTestSharp.IgnoreTest] public BoundaryPart[] Unsplit(BoundaryLabelling labelling) { return BoundaryParts.Build(labelling.Pieces, labelling.Joints, labelling.Stretches, labelling.Cuts, labelling.Intervals); } public void ShapeWhoseTrianglesAreClear_KeepsItsParts() { BoundaryLabelling labelling = new BoundaryLabelling(BoundaryLabellingTests.LShape()); CheckEqual(Unsplit(labelling).Length, labelling.Parts.Length); } public void CrescentsOuterPiece_IsSplitWhereItsTangentsMeetTheOutline() { BoundaryLabelling labelling = new BoundaryLabelling(BoundaryPartsTests.Crescent()); CheckEqual(2, Unsplit(labelling).Length); CheckEqual(4, labelling.Parts.Length); CheckEqual(new Vector(-1.0 / 3, -4.0 / 3), labelling.Parts[0].Curve.endPoint); CheckEqual(new Vector(1.0 / 3, -4.0 / 3), labelling.Parts[1].Curve.endPoint); } public void SplitParts_LeaveNoEdgeInsideAnyTriangle() { BoundaryLabelling labelling = new BoundaryLabelling(BoundaryPartsTests.Crescent()); foreach (BoundaryPart part in labelling.Parts) { Scalar parameter; CheckFalse(BoundaryInvariants.FindSplit(part, labelling.Outlines[part.Region], out parameter)); } } public void SplitParts_ChainContinuouslyAroundTheLoop() { BoundaryLabelling labelling = new BoundaryLabelling(BoundaryPartsTests.Crescent()); for (int i = 0; i < labelling.Parts.Length; ++i) { BoundaryPart next = labelling.Parts[(i + 1) % labelling.Parts.Length]; CheckEqual(next.Curve.startPoint, labelling.Parts[i].Curve.endPoint); } } public void TooFewRounds_Throws() { BoundaryLabelling labelling = new BoundaryLabelling(BoundaryPartsTests.Crescent()); Vector[][] outlines; CheckThrow(typeof(InvalidOperationException)); BoundaryInvariants.Enforce(Unsplit(labelling), labelling.Cuts, labelling.Vertices, labelling.Intervals, labelling.Regions.Length, out outlines, maxRounds: 0); } } } }