using System; using UnitTestSharp; namespace Blacklight.Bezier.UnitTests { public class ReferenceResultTests : TestFixture { public class CtorTests : TestFixture { public void AssignsBothFields() { var result = new ReferenceResult(distance: 1, closestX: 2); CheckEqual(1.0, result.Distance); CheckEqual(2.0, result.ClosestX); } } } public class ReferenceDistanceTests : TestFixture { public static double BruteForceDistance(ShellSample sample) { const int gridSteps = 2000; int best = 0; double bestSquared = double.PositiveInfinity; for (int i = 0; i <= gridSteps; i++) { double foot = sample.End * i / gridSteps; double squared = ReferenceDistance.SquaredDistanceAtOffset(sample, foot - sample.X); if (squared < bestSquared) { bestSquared = squared; best = i; } } double low = sample.End * Math.Max(best - 1, 0) / gridSteps; double high = sample.End * Math.Min(best + 1, gridSteps) / gridSteps; double inverseGolden = (Math.Sqrt(5) - 1) / 2; for (int i = 0; i < 100; i++) { double left = high - inverseGolden * (high - low); double right = low + inverseGolden * (high - low); if (ReferenceDistance.SquaredDistanceAtOffset(sample, left - sample.X) < ReferenceDistance.SquaredDistanceAtOffset(sample, right - sample.X)) { high = right; } else { low = left; } } return Math.Sqrt(Math.Min(bestSquared, ReferenceDistance.SquaredDistanceAtOffset(sample, (low + high) / 2 - sample.X))); } public class CubeRootTests : TestFixture { public void PositiveValue_ReturnsItsCubeRoot() { CheckGreater(1e-12, Math.Abs(ReferenceDistance.CubeRoot(27) - 3)); } public void NegativeValue_ReturnsTheNegatedCubeRoot() { CheckGreater(1e-12, Math.Abs(ReferenceDistance.CubeRoot(-8) + 2)); } public void Zero_ReturnsZero() { CheckEqual(0.0, ReferenceDistance.CubeRoot(0)); } } public class LargestRootTests : TestFixture { public void OneRealRoot_ReturnsIt() { CheckGreater(1e-12, Math.Abs(ReferenceDistance.LargestRoot(p: 1, q: -2) - 1)); } public void ThreeRealRoots_ReturnsTheLargest() { CheckGreater(1e-12, Math.Abs(ReferenceDistance.LargestRoot(p: -7, q: 6) - 2)); } public void DoubleRoot_StaysOnIt() { CheckGreater(1e-12, Math.Abs(ReferenceDistance.LargestRoot(p: -3, q: 2) - 1)); } public void ZeroCoefficients_ReturnZero() { CheckEqual(0.0, ReferenceDistance.LargestRoot(p: 0, q: 0)); } public void NoLinearTerm_ReturnsTheCubeRoot() { CheckGreater(1e-12, Math.Abs(ReferenceDistance.LargestRoot(p: 0, q: -8) - 2)); } public void CardanoCancellation_IsRepaired() { double root = ReferenceDistance.LargestRoot(p: 1e6, q: -1); CheckGreater(1e-12, Math.Abs(root - 1e-6) / 1e-6); } } public class SquaredDistanceAtOffsetTests : TestFixture { public void BackToTheVertex_IsTheSquaredDistanceToTheOrigin() { CheckEqual(25.0, ReferenceDistance.SquaredDistanceAtOffset(ShellSample.FromPosition(3, 4, 10), -3)); } public void ToAnotherCurvePoint_IsTheSquaredDistanceToIt() { CheckEqual(5.0, ReferenceDistance.SquaredDistanceAtOffset(ShellSample.FromPosition(3, 4, 10), -1)); } } public class ShiftedResidualTests : TestFixture { public void MatchesTheFootCubicAtTheShiftedPoint() { CheckEqual(-10.0, ReferenceDistance.ShiftedResidual(ShellSample.FromPosition(3, 4, 10), -1)); } public void VanishesAtTheFoot() { ShellSample sample = SampleSets.AlongNormal(foot: 2, offset: 0.5, end: 10); CheckGreater(1e-12, Math.Abs(ReferenceDistance.ShiftedResidual(sample, 2 - sample.X))); } } public class PolishOffsetTests : TestFixture { public void FarOutNearTheCurve_RecoversTheOffsetFromAnInexactRoot() { ShellSample sample = SampleSets.AlongNormal(foot: 1e4, offset: 1e-9, end: 1e7); double root = ReferenceDistance.LargestRoot(2 * (1 - sample.Y), -2 * sample.X); double offset = ReferenceDistance.PolishOffset(sample, root); double distance = Math.Sqrt(ReferenceDistance.SquaredDistanceAtOffset(sample, offset)); CheckGreater(1e-6, Math.Abs(distance - 1e-9) / 1e-9); } public void AtADoubleRoot_StaysPut() { ShellSample sample = ShellSample.FromPosition(-1, 2.5, 10); CheckGreater(1e-6, Math.Abs(ReferenceDistance.PolishOffset(sample, 1) - 2)); } } public class SolveTests : TestFixture { public void PointOnTheCurve_IsAtDistanceZero() { ReferenceResult result = ReferenceDistance.Solve(ShellSample.FromPosition(2, 2, 10)); CheckGreater(1e-12, result.Distance); CheckGreater(1e-9, Math.Abs(result.ClosestX - 2)); } public void BelowTheVertex_TheVertexIsClosest() { ReferenceResult result = ReferenceDistance.Solve(ShellSample.FromPosition(0, -2, 10)); CheckEqual(2.0, result.Distance); CheckEqual(0.0, result.ClosestX); } public void AboveTheCentreOfCurvature_TheClosestPointIsOffTheAxis() { ReferenceResult result = ReferenceDistance.Solve(ShellSample.FromPosition(0, 3, 10)); CheckGreater(1e-12, Math.Abs(result.Distance - Math.Sqrt(5))); CheckGreater(1e-12, Math.Abs(result.ClosestX - 2)); } public void PastTheEnd_TheEndIsClosest() { ReferenceResult result = ReferenceDistance.Solve(ShellSample.FromPosition(5, 0, 1)); CheckGreater(1e-12, Math.Abs(result.Distance - Math.Sqrt(16.25))); CheckEqual(1.0, result.ClosestX); } public void AcrossTheAxis_TheVertexIsClosest() { ReferenceResult result = ReferenceDistance.Solve(ShellSample.FromPosition(-1, 0, 10)); CheckEqual(1.0, result.Distance); CheckEqual(0.0, result.ClosestX); } public void FarOutNearTheCurve_RecoversTheOffset() { ReferenceResult result = ReferenceDistance.Solve(SampleSets.AlongNormal(foot: 1e4, offset: 1e-9, end: 1e7)); CheckGreater(1e-6, Math.Abs(result.Distance - 1e-9) / 1e-9); CheckGreater(1e-6, Math.Abs(result.ClosestX - 1e4)); } public void MatchesBruteForce() { var random = new Random(5); for (int i = 0; i < 200; i++) { ShellSample sample = ShellSample.FromPosition(SampleSets.Uniform(random, -5, 5), SampleSets.Uniform(random, -3, 8), SampleSets.Uniform(random, 0.1, 10)); CheckGreater(1e-9, Math.Abs(ReferenceDistance.Solve(sample).Distance - BruteForceDistance(sample))); } } } } }