// Copyright (C) Stichting Deltares 2016. All rights reserved. // // This file is part of Ringtoets. // // Ringtoets is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Core.Common.Base.Geometry; using Core.Common.Base.Properties; using Core.Common.TestUtil; using NUnit.Framework; namespace Core.Common.Base.Test.Geometry { [TestFixture] public class Math2DTest { #region testcases /// /// Test cases for intersecting segments. The contains pairs of , /// which represent the coordinate of a point. Each pair of coordinates form a segment. /// The last 2 double values are the expected intersection points. /// private static IEnumerable IntersectingSegments() { // \/ // /\ var testCaseDatadata1 = new TestCaseData(new[] { new Point2D(0.0, 0.0), new Point2D(1.0, 1.0), new Point2D(1.0, 0.0), new Point2D(0.0, 1.0), new Point2D(0.5, 0.5) }, "IntersectingSegments 1"); yield return testCaseDatadata1; // __ // / // / var testCaseDatadata2 = new TestCaseData(new[] { new Point2D(0.0, 0.0), new Point2D(1.0, 1.0), new Point2D(0.0, 1.0), new Point2D(1.0, 1.0), new Point2D(1.0, 1.0) }, "IntersectingSegments 2"); yield return testCaseDatadata2; // // / // /__ var testCaseDatadata3 = new TestCaseData(new[] { new Point2D(0.0, 0.0), new Point2D(1.0, 0.0), new Point2D(0.0, 0.0), new Point2D(1.0, 1.0), new Point2D(0.0, 0.0) }, "IntersectingSegments 3"); yield return testCaseDatadata3; } /// /// Test cases for parallel segments. The contains pairs of , /// which represent the coordinate of a point. Each pair of coordinates form a segment. /// private static IEnumerable ParallelSegments() { // __ // __ var testCaseDatadata1 = new TestCaseData(new[] { new Point2D(0.0, 0.0), new Point2D(1.0, 0.0), new Point2D(0.0, 1.0), new Point2D(1.0, 1.0) }, "ParallelSegments"); yield return testCaseDatadata1; // ____ (connected in single point) var testCaseDatadata2 = new TestCaseData(new[] { new Point2D(0.0, 0.0), new Point2D(1.0, 0.0), new Point2D(1.0, 0.0), new Point2D(2.0, 0.0) }, "ParallelSegments, connected in single point"); yield return testCaseDatadata2; // __ (overlap) var testCaseDatadata3 = new TestCaseData(new[] { new Point2D(0.0, 0.0), new Point2D(1.0, 0.0), new Point2D(0.5, 0.0), new Point2D(1.5, 0.0) }, "ParallelSegments, overlap"); yield return testCaseDatadata3; } /// /// Test cases for non intersecting segments. The contains pairs of , /// which represent the coordinate of a point. Each pair of coordinates form a segment. /// private static readonly Point2D[][] nonIntersectingSegments = { // | // ___ new[] { new Point2D(0.0, 0.0), new Point2D(1.0, 0.0), new Point2D(0.5, 1.0), new Point2D(0.5, 0.5), new Point2D(0.5, 0.0) } }; #endregion [Test] [TestCase(0, "line1Point1")] [TestCase(1, "line1Point2")] [TestCase(2, "line2Point1")] [TestCase(3, "line2Point2")] public void LineIntersectionWithLine_APointIsNull_ThrowsArgumentNullException(int nullIndex, string expectedParameter) { // Setup var points = new[] { new Point2D(0, 0), new Point2D(1, 0), new Point2D(0, -1), new Point2D(1, 2) }; points[nullIndex] = null; // Call TestDelegate test = () => Math2D.LineIntersectionWithLine(points[0], points[1], points[2], points[3]); // Assert var paramName = Assert.Throws(test).ParamName; Assert.AreEqual(expectedParameter, paramName); } [Test] [TestCaseSource("IntersectingSegments")] public void LineIntersectionWithLine_DifferentLineSegmentsWithIntersections_ReturnsPoint(Point2D[] points, string testname = "") { // Call var result = Math2D.LineIntersectionWithLine(points[0], points[1], points[2], points[3]); // Assert Assert.AreEqual(points[4], result); } [Test] [TestCaseSource("ParallelSegments")] // String testname was added because the Teamcity report only counts the unique signatures that were tested public void LineIntersectionWithLine_DifferentParallelLineSegments_ReturnsNoPoint(Point2D[] points, string testname = "") { // Call var result = Math2D.LineIntersectionWithLine(points[0], points[1], points[2], points[3]); // Assert Assert.IsNull(result); } [Test] [TestCaseSource("nonIntersectingSegments")] public void LineIntersectionWithLine_DifferentLineSegmentsWithNoIntersection_ReturnsPoint(Point2D[] points) { // Call var result = Math2D.LineIntersectionWithLine(points[0], points[1], points[2], points[3]); // Assert Assert.AreEqual(points[4], result); } [Test] public void LineIntersectionWithLine_WithEqualPoints_ThrowsArgumentException() { // Call TestDelegate testA = () => Math2D.LineIntersectionWithLine(new Point2D(0, 0), new Point2D(0, 0), new Point2D(1, 0), new Point2D(0, 1)); TestDelegate testB = () => Math2D.LineIntersectionWithLine(new Point2D(0, 1), new Point2D(0, 0), new Point2D(1, 1), new Point2D(1, 1)); // Assert var exceptionA = Assert.Throws(testA); var exceptionB = Assert.Throws(testB); Assert.AreEqual(Resources.Math2D_LineIntersectionWithLine_Line_points_are_equal, exceptionA.Message); Assert.AreEqual(Resources.Math2D_LineIntersectionWithLine_Line_points_are_equal, exceptionB.Message); } [Test] public void LineIntersectionWithLine_InterSectionsHigherUpX_ReturnsIntersectionWithTolerance() { // Setup var y1 = 5.925; var y2 = 5.890; var start = 133; // Call var result = Math2D.LineIntersectionWithLine(new Point2D(start, y1), new Point2D(start + 1, y2), new Point2D(start + 0.5, 0), new Point2D(start + 0.5, 1)); // Assert Assert.AreEqual((y1 + y2)/2, result.Y, 1e-6); } [Test] public void SegmentsIntersectionWithVerticalLine_SegmentsIsNull_ThrowsArgumentNullException() { // Call TestDelegate test = () => Math2D.SegmentsIntersectionWithVerticalLine(null, 0.0); // Assert var paramName = Assert.Throws(test).ParamName; Assert.AreEqual("segments", paramName); } [Test] [TestCase(2.5, new[] { 3.3 })] [TestCase(1.1, new double[0])] [TestCase(5.5, new double[0])] [TestCase(-1.5, new[] { 1.5, 3.75 })] public void SegmentsIntersectionWithVerticalLine_SegmentsCollectionNotIntersecting_ReturnsEmptyCollection(double x, double[] intersectionHeights) { // Setup var segments = new[] { new Segment2D(new Point2D(2.2, 3.3), new Point2D(3.3, 3.3)), new Segment2D(new Point2D(1.1, 5.0), new Point2D(1.1, 2.0)), // vertical new Segment2D(new Point2D(5.5, 2.0), new Point2D(5.5, 2.0)), // no length new Segment2D(new Point2D(-2.0, 1.0), new Point2D(-1.0, 2.0)), new Segment2D(new Point2D(-1.0, 2.0), new Point2D(-2.0, 5.5)) }; // Call var result = Math2D.SegmentsIntersectionWithVerticalLine(segments, x); // Assert Assert.AreEqual(intersectionHeights.Select(y => new Point2D(x, y)), result); } [Test] [TestCase(0)] [TestCase(1)] public void ConvertLinePointsToLineSegments_TooFewPoints_ReturnEmpty(int pointCount) { // Setup var linePoints = Enumerable.Repeat(new Point2D(0, 0), pointCount); // Call IEnumerable segments = Math2D.ConvertLinePointsToLineSegments(linePoints); // Assert CollectionAssert.IsEmpty(segments); } [Test] public void ConvertLinePointsToLineSegments_TwoPoints_ReturnOneSegmentOfThoseTwoPoints() { // Setup var linePoints = new[] { new Point2D(1.1, 2.2), new Point2D(3.3, 4.4), }; // Call Segment2D[] segments = Math2D.ConvertLinePointsToLineSegments(linePoints).ToArray(); // Assert Assert.AreEqual(1, segments.Length); Assert.AreEqual(linePoints[0], segments[0].FirstPoint); Assert.AreEqual(linePoints[1], segments[0].SecondPoint); } [Test] [TestCase(0)] [TestCase(1)] public void SplitLineAtLengths_TooFewPoints_ThrowArgumentException(int pointCount) { // Setup var originalLine = Enumerable.Repeat(new Point2D(0.0, 0.0), pointCount); var lengths = new[] { 0.0 }; // Call TestDelegate call = () => Math2D.SplitLineAtLengths(originalLine, lengths); // Assert const string expectedMessage = "Er zijn niet genoeg punten beschikbaar om een lijn te definiëren."; TestHelper.AssertThrowsArgumentExceptionAndTestMessage(call, expectedMessage); } [Test] public void SplitLineAtLengths_NegativeLength_ThrowArgumentException() { // Setup var originalLine = new[] { new Point2D(0.0, 0.0), new Point2D(6.0, 0.0) }; var lengths = new[] { 2.0, 6.0, -2.0 }; // Call TestDelegate call = () => Math2D.SplitLineAtLengths(originalLine, lengths); // Assert const string expectedMessage = "Er mogen geen negatieve lengtes worden opgegeven om de lijn mee op te knippen."; TestHelper.AssertThrowsArgumentExceptionAndTestMessage(call, expectedMessage); } [Test] [TestCase(0.0)] [TestCase(2.0 - 1.1e-6)] [TestCase(2.0 + 1.1e-6)] [TestCase(67.8)] public void SplitLineAtLengths_LengthsDoNotFullyCoverLine_ThrowArgumentException(double l) { // Setup var originalLine = new[] { new Point2D(0.0, 0.0), new Point2D(6.0, 0.0) }; var lengths = new[] { 2.0, 2.0, l }; // Call TestDelegate call = () => Math2D.SplitLineAtLengths(originalLine, lengths); // Assert const string expectedMessage = "De som van alle lengtes moet gelijk zijn aan de lengte van de opgegeven lijn."; TestHelper.AssertThrowsArgumentExceptionAndTestMessage(call, expectedMessage); } [Test] public void SplitLineAtLengths_OneLengthsForWholeLine_ReturnAllLinePoints() { // Setup var originalLine = new[] { new Point2D(0.0, 0.0), new Point2D(2.0, 0.0), new Point2D(4.0, 0.0), new Point2D(6.0, 0.0) }; var lengths = new[] { 6.0 }; // Call Point2D[][] lineSplits = Math2D.SplitLineAtLengths(originalLine, lengths); // Assert Assert.AreEqual(1, lineSplits.Length); Assert.AreNotSame(originalLine, lineSplits[0]); CollectionAssert.AreEqual(originalLine, lineSplits[0]); } [Test] public void SplitLineAtLengths_LongLineSplitInFourPieces_ReturnFourSplitResults() { // Setup var originalLine = new[] { new Point2D(0.0, 0.0), new Point2D(20.0, 60.0), }; var lengths = GetLengthsBasedOnReletative(new[] { 0.25, 0.25, 0.15, 0.35 }, originalLine); // Call Point2D[][] lineSplits = Math2D.SplitLineAtLengths(originalLine, lengths); // Assert var doubleToleranceComparer = new Point2DComparerWithTolerance(1e-6); Assert.AreEqual(4, lineSplits.Length); CollectionAssert.AreEqual(new[] { new Point2D(0, 0), new Point2D(5.0, 15.0) }, lineSplits[0], doubleToleranceComparer); CollectionAssert.AreEqual(new[] { new Point2D(5.0, 15.0), new Point2D(10.0, 30) }, lineSplits[1], doubleToleranceComparer); CollectionAssert.AreEqual(new[] { new Point2D(10.0, 30.0), new Point2D(13.0, 39.0) }, lineSplits[2], doubleToleranceComparer); CollectionAssert.AreEqual(new[] { new Point2D(13.0, 39.0), new Point2D(20.0, 60.0) }, lineSplits[3], doubleToleranceComparer); } [Test] [TestCase(0, "segment1")] [TestCase(1, "segment2")] public void GetIntersectionBetweenSegments_ASegmentIsNull_ThrowsArgumentNullException(int nullIndex, string expectedParameter) { // Setup var segments = new[] { new Segment2D(new Point2D(0, 0), new Point2D(0, 1)), new Segment2D(new Point2D(0, 0), new Point2D(0, 1)) }; segments[nullIndex] = null; // Call TestDelegate test = () => Math2D.GetIntersectionBetweenSegments(segments[0], segments[1]); // Assert var paramName = Assert.Throws(test).ParamName; Assert.AreEqual(expectedParameter, paramName); } [Test] [TestCase(1234.56789)] [TestCase(1e-6)] [TestCase(-1e-6)] [TestCase(-98765.4321)] public void GetIntersectionBetweenSegments_TwoHorizontalParallelSegments_ReturnNoIntersection( double dy) { // Setup const double y1 = 2.2; double y2 = y1 + dy; const double x1 = 1.1; const double x2 = 3.3; var horizontalSegment1 = new Segment2D(new Point2D(x1, y1), new Point2D(x2, y1)); var horizontalSegment2 = new Segment2D(new Point2D(x1, y2), new Point2D(x2, y2)); // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(horizontalSegment1, horizontalSegment2); // Assert Assert.AreEqual(Intersection2DType.DoesNotIntersect, result.IntersectionType); CollectionAssert.IsEmpty(result.IntersectionPoints); } [Test] [TestCase(1234.56789)] [TestCase(1e-6)] [TestCase(-1e-6)] [TestCase(-98765.4321)] public void GetIntersectionBetweenSegments_TwoVerticalParallelSegments_ReturnNoIntersection( double dx) { // Setup const double x1 = 1.1; double x2 = x1 + dx; const double y1 = 2.2; const double y2 = 3.3; var horizontalSegment1 = new Segment2D(new Point2D(x1, y1), new Point2D(x1, y2)); var horizontalSegment2 = new Segment2D(new Point2D(x2, y1), new Point2D(x2, y2)); // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(horizontalSegment1, horizontalSegment2); // Assert Assert.AreEqual(Intersection2DType.DoesNotIntersect, result.IntersectionType); CollectionAssert.IsEmpty(result.IntersectionPoints); } [Test] public void GetIntersectionBetweenSegments_TwoParallelSegments_ReturnNoIntersection() { // Setup var segment1 = new Segment2D(new Point2D(1.1, 3.3), new Point2D(2.2, 4.4)); var segment2 = new Segment2D(new Point2D(1.1, 5.5), new Point2D(2.2, 6.6)); // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(segment1, segment2); // Assert Assert.AreEqual(Intersection2DType.DoesNotIntersect, result.IntersectionType); CollectionAssert.IsEmpty(result.IntersectionPoints); } [Test] [TestCase(12.34)] [TestCase(1.1 + 1e-6)] [TestCase(-1.1 - 1e-6)] [TestCase(-56.78)] public void GetIntersectionBetweenSegments_TwoCollinearHorizontalLinesWithoutOverlap_ReturnNoIntersection(double dx) { // Setup const double x1 = 1.1; const double x2 = 2.2; const double y = 3.3; double x3 = x1 + dx; double x4 = x2 + dx; var horizontalSegment1 = new Segment2D(new Point2D(x1, y), new Point2D(x2, y)); var horizontalSegment2 = new Segment2D(new Point2D(x3, y), new Point2D(x4, y)); // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(horizontalSegment1, horizontalSegment2); // Assert Assert.AreEqual(Intersection2DType.DoesNotIntersect, result.IntersectionType); CollectionAssert.IsEmpty(result.IntersectionPoints); } [Test] [TestCase(12.34)] [TestCase(3.3 + 1e-6)] [TestCase(-3.3 - 1e-6)] [TestCase(-56.78)] public void GetIntersectionBetweenSegments_TwoCollinearVerticalLinesWithoutOverlap_ReturnNoIntersection(double dy) { // Setup const double y1 = 1.1; const double y2 = 4.4; const double x = 3.3; double y3 = y2 + dy; double y4 = y1 + dy; var horizontalSegment1 = new Segment2D(new Point2D(x, y1), new Point2D(x, y2)); var horizontalSegment2 = new Segment2D(new Point2D(x, y3), new Point2D(x, y4)); // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(horizontalSegment1, horizontalSegment2); // Assert Assert.AreEqual(Intersection2DType.DoesNotIntersect, result.IntersectionType); CollectionAssert.IsEmpty(result.IntersectionPoints); } [Test] [TestCase(-3.3)] [TestCase(7.7)] public void GetIntersectionBetweenSegments_TwoCollinearSegmentsWithoutOverlap_ReturnNoIntersection( double dx) { // Setup Func getY = x => 1.1*x + 2.2; const double x1 = 1.1; const double x2 = 3.3; var segment1 = new Segment2D(new Point2D(x1, getY(x1)), new Point2D(x2, getY(x2))); double x3 = x1 + dx; double x4 = x2 + dx; var segment2 = new Segment2D(new Point2D(x3, getY(x3)), new Point2D(x4, getY(x4))); // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(segment1, segment2); // Assert Assert.AreEqual(Intersection2DType.DoesNotIntersect, result.IntersectionType); CollectionAssert.IsEmpty(result.IntersectionPoints); } [Test] [TestCase(0)] [TestCase(1)] [TestCase(2)] [TestCase(3)] public void GetIntersectionBetweenSegments_TwoCollinearHorizontalSegmentsConnectedAtSegmentEnds_ReturnIntersectionPoint( int configurationNumber) { // Setup const double y = -6.7; var segment1UniquePoint = new Point2D(-12.34, y); var segmentCommonPoint = new Point2D(56.78, y); var segment2UniquePoint = new Point2D(91.23, y); Segment2D horizontalSegment1, horizontalSegment2; if (configurationNumber == 0 || configurationNumber == 3) { horizontalSegment1 = new Segment2D(segment1UniquePoint, segmentCommonPoint); } else { horizontalSegment1 = new Segment2D(segmentCommonPoint, segment1UniquePoint); } if (configurationNumber == 0 || configurationNumber == 1) { horizontalSegment2 = new Segment2D(segment2UniquePoint, segmentCommonPoint); } else { horizontalSegment2 = new Segment2D(segmentCommonPoint, segment2UniquePoint); } // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(horizontalSegment1, horizontalSegment2); // Assert Assert.AreEqual(Intersection2DType.Intersects, result.IntersectionType); CollectionAssert.AreEqual(new[] { segmentCommonPoint }, result.IntersectionPoints); } [Test] [TestCase(0)] [TestCase(1)] [TestCase(2)] [TestCase(3)] public void GetIntersectionBetweenSegments_TwoCollinearVerticalSegmentsConnectedAtSegmentEnds_ReturnIntersectionPoint( int configurationNumber) { // Setup const double x = 5.5; var segment1UniquePoint = new Point2D(x, -23.45); var segmentCommonPoint = new Point2D(x, -12.34); var segment2UniquePoint = new Point2D(x, 90.76); Segment2D verticalSegment1, verticalSegment2; if (configurationNumber == 0 || configurationNumber == 3) { verticalSegment1 = new Segment2D(segment1UniquePoint, segmentCommonPoint); } else { verticalSegment1 = new Segment2D(segmentCommonPoint, segment1UniquePoint); } if (configurationNumber == 0 || configurationNumber == 1) { verticalSegment2 = new Segment2D(segment2UniquePoint, segmentCommonPoint); } else { verticalSegment2 = new Segment2D(segmentCommonPoint, segment2UniquePoint); } // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(verticalSegment1, verticalSegment2); // Assert Assert.AreEqual(Intersection2DType.Intersects, result.IntersectionType); CollectionAssert.AreEqual(new[] { segmentCommonPoint }, result.IntersectionPoints, new Point2DComparerWithTolerance(1e-6)); } [Test] [TestCase(0)] [TestCase(1)] [TestCase(2)] [TestCase(3)] public void GetIntersectionBetweenSegments_TwoCollinearSegmentsConnectedAtSegmentEnds_ReturnIntersectionPoint( int configurationNumber) { // Setup Func getY = x => 3.3*x + 4.4; const double x1 = 5.5; const double x2 = 6.6; const double x3 = 8.8; var segment1UniquePoint = new Point2D(x1, getY(x1)); var segmentCommonPoint = new Point2D(x2, getY(x2)); var segment2UniquePoint = new Point2D(x3, getY(x3)); Segment2D segment1, segment2; if (configurationNumber == 0 || configurationNumber == 3) { segment1 = new Segment2D(segment1UniquePoint, segmentCommonPoint); } else { segment1 = new Segment2D(segmentCommonPoint, segment1UniquePoint); } if (configurationNumber == 0 || configurationNumber == 1) { segment2 = new Segment2D(segment2UniquePoint, segmentCommonPoint); } else { segment2 = new Segment2D(segmentCommonPoint, segment2UniquePoint); } // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(segment1, segment2); // Assert Assert.AreEqual(Intersection2DType.Intersects, result.IntersectionType); CollectionAssert.AreEqual(new[] { segmentCommonPoint }, result.IntersectionPoints); } [Test] [TestCase(0)] [TestCase(1)] [TestCase(2)] [TestCase(3)] public void GetIntersectionBetweenSegments_SegmentEndsOrStartsOnOtherSegment_ReturnIntersectionPoint( int configurationNumber) { // Setup const double y = 12.34; var segment1UniquePoint = new Point2D(15.6, y + 56.78); var pointOnSegment = new Point2D(1.0, y); var segment2Point1 = new Point2D(0.0, y); var segment2Point2 = new Point2D(111.0, y); Segment2D segment1, segment2; if (configurationNumber == 0 || configurationNumber == 3) { segment1 = new Segment2D(segment1UniquePoint, pointOnSegment); } else { segment1 = new Segment2D(pointOnSegment, segment1UniquePoint); } if (configurationNumber == 0 || configurationNumber == 1) { segment2 = new Segment2D(segment2Point1, segment2Point2); } else { segment2 = new Segment2D(segment2Point2, segment2Point1); } // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(segment1, segment2); // Assert Assert.AreEqual(Intersection2DType.Intersects, result.IntersectionType); CollectionAssert.AreEqual(new[] { pointOnSegment }, result.IntersectionPoints, new Point2DComparerWithTolerance(1e-6)); } [Test] [TestCase(10 - 1e-4)] [TestCase(3.45)] [TestCase(0)] [TestCase(-7.96)] [TestCase(-10 + 1e-4)] public void GetIntersectionBetweenSegments_VerticalCollinearSegmentsWithSomeOverlap_ReturnOverlap( double dy) { // Setup const double x = -12.56; const double y1 = 10.0; const double y2 = 0.0; double y3 = y1 + dy; double y4 = y2 + dy; var verticalSegment1 = new Segment2D(new Point2D(x, y1), new Point2D(x, y2)); var verticalSegment2 = new Segment2D(new Point2D(x, y3), new Point2D(x, y4)); // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(verticalSegment1, verticalSegment2); // Assert Assert.AreEqual(Intersection2DType.Overlaps, result.IntersectionType); var expectedOverlappingPoints = dy >= 0 ? new[] { verticalSegment1.FirstPoint, verticalSegment2.SecondPoint } : new[] { verticalSegment1.SecondPoint, verticalSegment2.FirstPoint }; CollectionAssertAreEquivalent(expectedOverlappingPoints, result.IntersectionPoints); } [Test] [TestCase(14 - 1e-4)] [TestCase(7.8)] [TestCase(0)] [TestCase(-2.34)] [TestCase(-14 + 1e-4)] public void GetIntersectionBetweenSegments_HorizontalCollinearSegmentsWithSomeOverlap_ReturnOverlap( double dx) { // Setup const double y = 98.54; const double x1 = 2.0; const double x2 = -12.0; double x3 = x1 + dx; double x4 = x2 + dx; var horizontalSegment1 = new Segment2D(new Point2D(x1, y), new Point2D(x2, y)); var horizontalSegment2 = new Segment2D(new Point2D(x3, y), new Point2D(x4, y)); // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(horizontalSegment1, horizontalSegment2); // Assert Assert.AreEqual(Intersection2DType.Overlaps, result.IntersectionType); var expectedOverlappingPoints = dx >= 0 ? new[] { horizontalSegment1.FirstPoint, horizontalSegment2.SecondPoint } : new[] { horizontalSegment1.SecondPoint, horizontalSegment2.FirstPoint }; CollectionAssertAreEquivalent(expectedOverlappingPoints, result.IntersectionPoints); } [Test] public void GetIntersectionBetweenSegments_SelfIntersection_ReturnOverlap() { // Setup var firstPoint = new Point2D(1.1, 2.2); var secondPoint = new Point2D(-3.3, -4.4); var segment = new Segment2D(firstPoint, secondPoint); // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(segment, segment); // Assert Assert.AreEqual(Intersection2DType.Overlaps, result.IntersectionType); var expectedOverlappingPoints = new[] { firstPoint, secondPoint }; CollectionAssertAreEquivalent(expectedOverlappingPoints, result.IntersectionPoints); } [Test] public void GetIntersectionBetweenSegments_CollinearSegmentsWithFullOverlap_ReturnOverlap() { // Setup Func getY = x => -12.34*x + 45.67; const double x1 = 1.1; const double x2 = 2.2; const double x3 = -3.3; const double x4 = 4.4; var segment1 = new Segment2D(new Point2D(x1, getY(x1)), new Point2D(x2, getY(x2))); var segment2 = new Segment2D(new Point2D(x3, getY(x3)), new Point2D(x4, getY(x4))); // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(segment1, segment2); // Assert Assert.AreEqual(Intersection2DType.Overlaps, result.IntersectionType); var expectedOverlappingPoints = new[] { segment1.FirstPoint, segment1.SecondPoint }; CollectionAssertAreEquivalent(expectedOverlappingPoints, result.IntersectionPoints); } [Test] [TestCase(-12.34, -34.56, -67.78, -91.23)] [TestCase(12.34, 34.56, 67.78, 91.23)] [TestCase(1.0, 2.1, 3.4, 2.1)] [TestCase(3.4, 4.5, 1.0, 4.5)] [TestCase(1.0, 2.1, 1.0, 4.5)] [TestCase(3.4, 4.5, 3.4, 2.1)] [TestCase(1.5, 2.4, 2.2, 2.4)] [TestCase(3.0, 3.8, 2.9, 3.6)] public void GetIntersectionBetweenSegments_SegmentsNotIntersecting_ReturnNoIntersect( double x1, double y1, double x2, double y2) { // Setup var segment1 = new Segment2D(new Point2D(1.1, 2.2), new Point2D(3.3, 4.4)); var segment2 = new Segment2D(new Point2D(x1, y1), new Point2D(x2, y2)); // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(segment1, segment2); // Assert Assert.AreEqual(Intersection2DType.DoesNotIntersect, result.IntersectionType); CollectionAssert.IsEmpty(result.IntersectionPoints); } [Test] [TestCase(0, 0)] [TestCase(-6541.2354, 5.25)] [TestCase(-3.25, -12.55)] [TestCase(6.154, -9684.514)] [TestCase(6840.251, 15.3251)] public void GetIntersectionBetweenSegments_SegmentsArePointsOnTopOfEachOther_ReturnIntersection(double x, double y) { // Setup var segment1 = new Segment2D(new Point2D(x, y), new Point2D(x, y)); var segment2 = new Segment2D(new Point2D(x, y), new Point2D(x, y)); // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(segment1, segment2); // Assert Assert.AreEqual(Intersection2DType.Intersects, result.IntersectionType); CollectionAssert.AreEqual(new[] { new Point2D(x, y) }, result.IntersectionPoints); } [Test] public void GetIntersectionBetweenSegments_SegmentsArePointsNotOnTopOfEachOther_ReturnNoIntersection() { // Setup const double x1 = 1.1; const double y1 = 2.2; const double x2 = 3.3; const double y2 = 4.4; var segment1 = new Segment2D(new Point2D(x1, y1), new Point2D(x1, y1)); var segment2 = new Segment2D(new Point2D(x2, y2), new Point2D(x2, y2)); // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(segment1, segment2); // Assert Assert.AreEqual(Intersection2DType.DoesNotIntersect, result.IntersectionType); CollectionAssert.IsEmpty(result.IntersectionPoints); } [Test] [TestCase(true)] [TestCase(false)] public void GetIntersectionBetweenSegments_SegmentWithPointSegmentOnIt_ReturnIntersection( bool firstSegmentIsPointDegenerate) { // Setup Func getY = x => 1.2*x + 3.4; const double x1 = 1.1; const double x2 = 5.5; var lineSegment = new Segment2D(new Point2D(x1, getY(x1)), new Point2D(x2, getY(x2))); const double x3 = 3.3; var point = new Point2D(x3, getY(x3)); var pointSegment = new Segment2D(point, point); Segment2D segment1, segment2; if (firstSegmentIsPointDegenerate) { segment1 = pointSegment; segment2 = lineSegment; } else { segment1 = lineSegment; segment2 = pointSegment; } // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(segment1, segment2); // Assert Assert.AreEqual(Intersection2DType.Intersects, result.IntersectionType); CollectionAssert.AreEqual(new[] { point }, result.IntersectionPoints); } [Test] [TestCase(true)] [TestCase(false)] public void GetIntersectionBetweenSegments_VerticalSegmentWithPointSegmentOnIt_ReturnIntersection( bool firstSegmentIsPointDegenerate) { // Setup const double x = 1.1; var lineSegment = new Segment2D(new Point2D(x, 1.0), new Point2D(x, 3.0)); var point = new Point2D(x, 2.0); var pointSegment = new Segment2D(point, point); Segment2D segment1, segment2; if (firstSegmentIsPointDegenerate) { segment1 = pointSegment; segment2 = lineSegment; } else { segment1 = lineSegment; segment2 = pointSegment; } // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(segment1, segment2); // Assert Assert.AreEqual(Intersection2DType.Intersects, result.IntersectionType); CollectionAssert.AreEqual(new[] { point }, result.IntersectionPoints); } [Test] [TestCase(true)] [TestCase(false)] public void GetIntersectionBetweenSegments_SegmentAndSeparatePointSegment_ReturnNoIntersection( bool firstSegmentIsPointDegenerate) { // Setup Func getY = x => -5.6*x + 7.8; const double x1 = 1.1; const double x2 = 5.5; var lineSegment = new Segment2D(new Point2D(x1, getY(x1)), new Point2D(x2, getY(x2))); var point = new Point2D(0.0, 0.0); var pointSegment = new Segment2D(point, point); Segment2D segment1, segment2; if (firstSegmentIsPointDegenerate) { segment1 = pointSegment; segment2 = lineSegment; } else { segment1 = lineSegment; segment2 = pointSegment; } // Call Segment2DIntersectSegment2DResult result = Math2D.GetIntersectionBetweenSegments(segment1, segment2); // Assert Assert.AreEqual(Intersection2DType.DoesNotIntersect, result.IntersectionType); CollectionAssert.IsEmpty(result.IntersectionPoints); } [Test] [TestCase(0, "point1")] [TestCase(1, "point2")] public void AreEqualPoints_APointIsNull_ThrowsArgumentNullException(int nullIndex, string expectedParameter) { // Setup var points = new[] { new Point2D(0, 0), new Point2D(1, 0), }; points[nullIndex] = null; // Call TestDelegate test = () => Math2D.AreEqualPoints(points[0], points[1]); // Assert var paramName = Assert.Throws(test).ParamName; Assert.AreEqual(expectedParameter, paramName); } [Test] public void AreEqualPoints_PointsEqual_ReturnsTrue() { // Call var result = Math2D.AreEqualPoints(new Point2D(0, 0), new Point2D(0, 0)); // Assert Assert.IsTrue(result); } [Test] public void AreEqualPoints_PointsNotEqual_ReturnsFalse() { // Call var result = Math2D.AreEqualPoints(new Point2D(1, 0), new Point2D(4, 9)); // Assert Assert.IsFalse(result); } [Test] public void Length_NullPoints_ThrowsArgumentNullException() { // Call TestDelegate test = () => Math2D.Length(null); // Assert var paramName = Assert.Throws(test).ParamName; Assert.AreEqual("points", paramName); } [Test] public void Length_EmptyCollection_ReturnsZero() { // Setup var points = new Point2D[0]; // Call var length = Math2D.Length(points); // Assert Assert.AreEqual(0, length); } [Test] public void Length_CollectionWithSinglePoint_ReturnsZero() { // Setup var random = new Random(21); var points = new[] { new Point2D(random.NextDouble(), random.NextDouble()), }; // Call var length = Math2D.Length(points); // Assert Assert.AreEqual(0, length); } [Test] public void Length_CollectionWithTwoPoints_ReturnsDistanceBetweenPoints() { // Setup var random = new Random(21); var point1 = new Point2D(random.NextDouble(), random.NextDouble()); var point2 = new Point2D(random.NextDouble(), random.NextDouble()); var points = new[] { point1, point2 }; // Call var length = Math2D.Length(points); // Assert Assert.AreEqual(point2.GetEuclideanDistanceTo(point1), length); } [Test] [TestCase(3)] [TestCase(5)] public void Length_CollectionWithMoreThanTwoPoints_ReturnsSumOfDistanceBetweenPoints(int count) { // Setup var random = new Random(21); var points = new List(count); double expectedLength = 0; Point2D previousPoint = null; for (int i = 0; i < count; i++) { var point = new Point2D(random.NextDouble(), random.NextDouble()); if (previousPoint != null) { expectedLength += previousPoint.GetEuclideanDistanceTo(point); } points.Add(point); previousPoint = point; } // Call var length = Math2D.Length(points); // Assert Assert.AreEqual(expectedLength, length); } [Test] public void GetInterpolatedPointAtFraction_LineSegmentIsNull_ThrowsArgumentNullException() { // Call TestDelegate test = () => Math2D.GetInterpolatedPointAtFraction(null, 0.0); // Assert var paramName = Assert.Throws(test).ParamName; Assert.AreEqual("lineSegment", paramName); } [Test] public void GetInterpolatedPoint_WithinLine_InterpolatesBetweenPoints() { // Setup var pointA = new Point2D(1.8, 5.02); var pointB = new Point2D(3.8, -2.2); var segment = new Segment2D(pointA, pointB); var fraction = 0.5; // Call Point2D result = Math2D.GetInterpolatedPointAtFraction(segment, fraction); // Assert Assert.AreEqual(new Point2D(2.8, (5.02 - 2.2)*fraction), result); } [Test] public void GetInterpolatedPoint_OnFirstEndPoint_ReturnsEndPointCoordinate() { // Setup var pointA = new Point2D(1.8, 5.02); var pointB = new Point2D(3.8, -2.2); var segment = new Segment2D(pointA, pointB); var fraction = 0.0; // Call Point2D result = Math2D.GetInterpolatedPointAtFraction(segment, fraction); // Assert Assert.AreEqual(pointA, result); } [Test] public void GetInterpolatedPoint_OnSecondEndPoint_ReturnsEndPointCoordinate() { // Setup var pointA = new Point2D(1.8, 5.02); var pointB = new Point2D(3.8, -2.2); var segment = new Segment2D(pointA, pointB); var fraction = 1.0; // Call Point2D result = Math2D.GetInterpolatedPointAtFraction(segment, fraction); // Assert Assert.AreEqual(pointB, result); } [Test] [TestCase(double.NaN)] [TestCase(-1e-9)] [TestCase(1 + 1e-9)] [TestCase(3)] [TestCase(-2)] public void GetInterpolatedPoint_WithInvalidFraction_ThrowsArgumentOutOfRangeException(double fraction) { // Setup var pointA = new Point2D(1.8, 5.02); var pointB = new Point2D(3.8, -2.2); var segment = new Segment2D(pointA, pointB); // Call TestDelegate test = () => Math2D.GetInterpolatedPointAtFraction(segment, fraction); // Assert TestHelper.AssertThrowsArgumentExceptionAndTestMessage( test, "Fraction needs to be defined in range [0.0, 1.0] in order to reliably interpolate."); } private static void CollectionAssertAreEquivalent(Point2D[] expected, Point2D[] actual) { var comparer = new Point2DComparerWithTolerance(1e-6); foreach (var intersectionPoint in actual) { Assert.AreEqual(1, expected.Count(p => comparer.Compare(p, intersectionPoint) == 0)); } } private double[] GetLengthsBasedOnReletative(double[] relativeLengths, IEnumerable lineGeometryPoints) { var lineLength = Math2D.ConvertLinePointsToLineSegments(lineGeometryPoints).Sum(s => s.Length); return relativeLengths.Select(l => lineLength*l).ToArray(); } } }