using System; using System.Collections.Generic; using Azimuth; using UnitTestSharp; namespace Blacklight.Bezier.UnitTests { public class CubicFeaturesTests : TestFixture { public static readonly CubicPiece UpwardParabola = CubicPiece.FromQuadratic(new Vector(-1, 0.5), new Vector(0, -0.5), new Vector(1, 0.5)); public static readonly CubicPiece DownwardParabola = CubicPiece.FromQuadratic(new Vector(-1, -0.5), new Vector(0, 0.5), new Vector(1, -0.5)); public static readonly CubicPiece Arch = new CubicPiece(new Vector(0, 0), new Vector(0, 1), new Vector(1, 1), new Vector(1, 0)); public static readonly CubicPiece SCurve = new CubicPiece(new Vector(0, 0), new Vector(0.3, 1), new Vector(0.7, -1), new Vector(1, 0)); public static readonly CubicPiece Serpentine = new CubicPiece(new Vector(0, 0), new Vector(0.8, 1), new Vector(0.2, 1), new Vector(1, 0)); public class PowerBasisTests : TestFixture { public void ReproducesThePiece() { CubicFeatures.PowerBasis(CubicPieceTests.Wave, out Vector cubic, out Vector quadratic, out Vector linear); for (double t = 0; t <= 1; t += 0.125) { Vector expected = CubicPieceTests.Wave.PointAt(t); Vector actual = ((cubic * t + quadratic) * t + linear) * t + CubicPieceTests.Wave.P0; CheckGreater(1e-14, (expected - actual).Length().Value); } } } public class CurvatureTests : TestFixture { public void TurningCounterclockwise_IsPositive() { CheckGreater(1e-14, Math.Abs(CubicFeatures.Curvature(UpwardParabola, 0.5) - 1)); } public void TurningClockwise_IsNegative() { CheckGreater(1e-14, Math.Abs(CubicFeatures.Curvature(DownwardParabola, 0.5) + 1)); } } public class CentreOfCurvatureTests : TestFixture { public void OnTheInsideOfTheTurn() { CheckGreater(1e-14, (CubicFeatures.CentreOfCurvature(UpwardParabola, 0.5) - new Vector(0, 1)).Length() .Value); CheckGreater(1e-14, (CubicFeatures.CentreOfCurvature(DownwardParabola, 0.5) - new Vector(0, -1)) .Length().Value); } } public class InflectionsTests : TestFixture { public void Arch_HasNone() { CheckEqual(0, CubicFeatures.Inflections(Arch).Count); } public void Parabola_HasNone() { CheckEqual(0, CubicFeatures.Inflections(UpwardParabola).Count); } public void SCurve_HasOneAtItsCentreOfSymmetry() { List inflections = CubicFeatures.Inflections(SCurve); CheckEqual(1, inflections.Count); CheckGreater(1e-14, Math.Abs(inflections[0] - 0.5)); } public void Serpentine_HasTwoInIncreasingOrder() { List inflections = CubicFeatures.Inflections(Serpentine); double spread = Math.Sqrt(1 - 4 * 5.4 / 25.2) / 2; CheckEqual(2, inflections.Count); CheckGreater(1e-14, Math.Abs(inflections[0] - (0.5 - spread))); CheckGreater(1e-14, Math.Abs(inflections[1] - (0.5 + spread))); } public void InflectionsBeyondThePiece_AreLeftOut() { CheckEqual(0, CubicFeatures.Inflections(Serpentine.Segment(0.4, 0.6)).Count); } } public class SelfIntersectionTests : TestFixture { public void Loop_CrossesWhereBothParametersMeet() { CubicPiece loop = CubicReferenceDistanceTests.Loop; CheckTrue(CubicFeatures.SelfIntersection(loop, out double first, out double second)); CheckGreater(second, first); CheckGreater(1e-12, (loop.PointAt(first) - loop.PointAt(second)).Length().Value); } public void CrossingPartlyOffThePiece_DoesNotCount() { CheckFalse(CubicFeatures.SelfIntersection(CubicReferenceDistanceTests.Loop.Segment(0.2, 1), out _, out _)); } public void Serpentine_NeverCrossesItself() { CheckFalse(CubicFeatures.SelfIntersection(Serpentine, out _, out _)); } public void Parabola_NeverCrossesItself() { CheckFalse(CubicFeatures.SelfIntersection(UpwardParabola, out _, out _)); } } } }