package org.djutils.draw.line; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.awt.geom.Path2D; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import org.djutils.draw.DrawException; import org.djutils.draw.DrawRuntimeException; import org.djutils.draw.Transform2d; import org.djutils.draw.bounds.Bounds2d; import org.djutils.draw.point.Point2d; import org.djutils.draw.point.Point3d; import org.junit.Test; /** * TestLine2d.java. *

* Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See DJUTILS License. *

* @author Alexander Verbraeck * @author Peter Knoppers */ public class PolyLine2dTest { /** * Test the constructors of PolyLine2d. * @throws DrawException on failure */ @Test public final void constructorsTest() throws DrawException { double[] values = { -999, 0, 99, 9999 }; // Keep this list short; execution time grows with 6th power of length Point2d[] points = new Point2d[0]; // Empty array try { runConstructors(points); fail("Should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException exception) { // Ignore expected exception } for (double x0 : values) { for (double y0 : values) { points = new Point2d[1]; // Degenerate array holding one point points[0] = new Point2d(x0, y0); try { runConstructors(points); fail("Should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException exception) { // Ignore expected exception } for (double x1 : values) { for (double y1 : values) { points = new Point2d[2]; // Straight line; two points points[0] = new Point2d(x0, y0); points[1] = new Point2d(x1, y1); if (0 == points[0].distance(points[1])) { try { runConstructors(points); fail("Should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException exception) { // Ignore expected exception } } else { runConstructors(points); for (double x2 : values) { for (double y2 : values) { points = new Point2d[3]; // Line with intermediate point points[0] = new Point2d(x0, y0); points[1] = new Point2d(x1, y1); points[2] = new Point2d(x2, y2); if (0 == points[1].distance(points[2])) { try { runConstructors(points); fail("Should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException exception) { // Ignore expected exception } } else { runConstructors(points); } } } } } } } } } /** * Test all the constructors of PolyLine2d. * @param points Point2d[]; array of Point2d to test with * @throws DrawException should not happen; this test has failed if it does happen */ private void runConstructors(final Point2d[] points) throws DrawException { verifyPointsAndSegments(new PolyLine2d(points), points); List list = new ArrayList<>(); for (int i = 0; i < points.length; i++) { list.add(points[i]); } PolyLine2d line = new PolyLine2d(list); verifyPointsAndSegments(line, points); verifyPointsAndSegments(new PolyLine2d(line.getPoints()), points); assertEquals("length at index 0", 0.0, line.lengthAtIndex(0), 0); double length = 0; for (int i = 1; i < points.length; i++) { length += Math.sqrt(Math.pow(points[i].x - points[i - 1].x, 2) + Math.pow(points[i].y - points[i - 1].y, 2)); assertEquals("length at index", length, line.lengthAtIndex(i), 0.0001); } assertEquals("length", length, line.getLength(), 10 * Math.ulp(length)); assertEquals("size", points.length, line.size()); Bounds2d b2d = line.getBounds(); Bounds2d ref = new Bounds2d(points); assertEquals("bounds is correct", ref, b2d); try { line.get(-1); fail("Negative index should have thrown an IndexOutOfBoundsException"); } catch (IndexOutOfBoundsException ioobe) { // Ignore expected exception } try { line.get(line.size() + 1); fail("Too large index should have thrown an IndexOutOfBoundsException"); } catch (IndexOutOfBoundsException ioobe) { // Ignore expected exception } int horizontalMoves = 0; Path2D path = new Path2D.Double(); path.moveTo(points[0].x, points[0].y); // System.out.print("path is "); printPath2D(path); for (int i = 1; i < points.length; i++) { // Path2D is corrupt if same point is added twice in succession if (points[i].x != points[i - 1].x || points[i].y != points[i - 1].y) { path.lineTo(points[i].x, points[i].y); horizontalMoves++; } } try { line = new PolyLine2d(path); if (0 == horizontalMoves) { fail("Construction of Line2d from path with degenerate projection should have failed"); } assertEquals("number of points should match", horizontalMoves + 1, line.size()); int indexInLine = 0; for (int i = 0; i < points.length; i++) { if (i > 0 && (points[i].x != points[i - 1].x || points[i].y != points[i - 1].y)) { indexInLine++; } assertEquals("x in line", points[i].x, line.get(indexInLine).x, 0.001); assertEquals("y in line", points[i].y, line.get(indexInLine).y, 0.001); } } catch (DrawException e) { if (0 != horizontalMoves) { fail("Construction of Line2d from path with non-degenerate projection should not have failed"); } } } /** * Test construction of a Line2d from a Path2D with SEG_CLOSE. * @throws DrawException on unexpected error */ @Test public void testPathWithClose() throws DrawException { Path2D path = new Path2D.Double(); path.moveTo(1, 2); path.lineTo(4, 5); path.lineTo(4, 8); path.closePath(); PolyLine2d line = new PolyLine2d(path); assertEquals("line has 4 points", 4, line.size()); assertEquals("first point equals last point", line.getFirst(), line.getLast()); // Now the case that the path was already closed path = new Path2D.Double(); path.moveTo(1, 2); path.lineTo(4, 5); path.lineTo(1, 2); path.closePath(); line = new PolyLine2d(path); assertEquals("line has 4 points", 3, line.size()); assertEquals("first point equals last point", line.getFirst(), line.getLast()); path = new Path2D.Double(); path.moveTo(1, 2); path.lineTo(4, 5); path.lineTo(4, 8); path.curveTo(1, 2, 3, 4, 5, 6); try { new PolyLine2d(path); fail("unsupported SEG_CUBICTO should have thrown an exception"); } catch (DrawException de) { // Ignore expected exception } } /** * Test all constructors of a Line2d. * @throws DrawRuntimeException if that happens uncaught; this test has failed * @throws DrawException if that happens uncaught; this test has failed */ @Test public void testConstructors() throws DrawRuntimeException, DrawException { runConstructors(new Point2d[] { new Point2d(1.2, 3.4), new Point2d(2.3, 4.5), new Point2d(3.4, 5.6) }); try { new PolyLine2d(new double[] { 1, 2, 3 }, new double[] { 4, 5 }); fail("double arrays of unequal length should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { new PolyLine2d(new double[] { 1, 2 }, new double[] { 3, 4, 5 }); fail("double arrays of unequal length should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { new PolyLine2d(null, new double[] { 1, 2 }); fail("null double array should have thrown a NullPointerException"); } catch (NullPointerException npe) { // Ignore expected exception } try { new PolyLine2d(new double[] { 1, 2 }, null); fail("null double array should have thrown a NullPointerException"); } catch (NullPointerException npe) { // Ignore expected exception } try { new PolyLine2d((List) null); fail("null list should have thrown a nullPointerException"); } catch (NullPointerException npe) { // Ignore expected exception } List shortList = new ArrayList<>(); try { new PolyLine2d(shortList); fail("empty list should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } shortList.add(new Point2d(1, 2)); try { new PolyLine2d(shortList); fail("one-point list should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } Point2d p1 = new Point2d(1, 2); Point2d p2 = new Point2d(3, 4); PolyLine2d pl = new PolyLine2d(p1, p2); assertEquals("two points", 2, pl.size()); assertEquals("p1", p1, pl.get(0)); assertEquals("p2", p2, pl.get(1)); pl = new PolyLine2d(p1, p2, (Point2d[]) null); assertEquals("two points", 2, pl.size()); assertEquals("p1", p1, pl.get(0)); assertEquals("p2", p2, pl.get(1)); pl = new PolyLine2d(p1, p2, new Point2d[0]); assertEquals("two points", 2, pl.size()); assertEquals("p1", p1, pl.get(0)); assertEquals("p2", p2, pl.get(1)); try { new PolyLine2d(new Point2d[] {}); fail("empty array should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { new PolyLine2d(new Point2d[] { new Point2d(1, 2) }); fail("single point should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { new PolyLine2d(new Point2d[] { new Point2d(1, 2), new Point2d(1, 2) }); fail("duplicate point should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { new PolyLine2d(new Point2d[] { new Point2d(1, 2), new Point2d(1, 2), new Point2d(3, 4) }); fail("duplicate point should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { new PolyLine2d(new Point2d[] { new Point2d(-1, -2), new Point2d(1, 2), new Point2d(1, 2), new Point2d(3, 4) }); fail("duplicate point should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } } /** * Test the other methods of PolyLine2d. * @throws DrawException if that happens uncaught; this test has failed * @throws NullPointerException if that happens uncaught; this test has failed */ @SuppressWarnings("unlikely-arg-type") @Test public void testOtherMethods() throws NullPointerException, DrawException { Point2d[] array = new Point2d[] { new Point2d(1, 2), new Point2d(3, 4), new Point2d(3.2, 4.1), new Point2d(5, 6) }; PolyLine2d line = new PolyLine2d(Arrays.stream(array).iterator()); assertEquals("size", array.length, line.size()); for (int i = 0; i < array.length; i++) { assertEquals("i-th point", array[i], line.get(i)); } int nextIndex = 0; for (Iterator iterator = line.getPoints(); iterator.hasNext();) { assertEquals("i-th point from line iterator", array[nextIndex++], iterator.next()); } assertEquals("iterator returned all points", array.length, nextIndex); PolyLine2d filtered = line.noiseFilteredLine(0.0); assertEquals("filtered with 0 tolerance returns line", line, filtered); filtered = line.noiseFilteredLine(0.01); assertEquals("filtered with very low tolerance returns line", line, filtered); filtered = line.noiseFilteredLine(0.5); assertEquals("size of filtered line is 3", 3, filtered.size()); assertEquals("first point of filtered line matches", line.getFirst(), filtered.getFirst()); assertEquals("last point of filtered line matches", line.getLast(), filtered.getLast()); assertEquals("mid point of filtered line is point 1 of unfiltered line", line.get(1), filtered.get(1)); filtered = line.noiseFilteredLine(10); assertEquals("size of filtered line is 2", 2, filtered.size()); assertEquals("first point of filtered line matches", line.getFirst(), filtered.getFirst()); assertEquals("last point of filtered line matches", line.getLast(), filtered.getLast()); array = new Point2d[] { new Point2d(1, 2), new Point2d(3, 4), new Point2d(3.2, 4.1), new Point2d(1, 2) }; line = new PolyLine2d(Arrays.stream(array).iterator()); filtered = line.noiseFilteredLine(10); assertEquals("size of filtered line is 3", 3, filtered.size()); assertEquals("first point of filtered line matches", line.getFirst(), filtered.getFirst()); assertEquals("last point of filtered line matches", line.getLast(), filtered.getLast()); assertEquals("mid point of filtered line is point 1 of unfiltered line", line.get(1), filtered.get(1)); array = new Point2d[] { new Point2d(1, 2), new Point2d(3, 4), new Point2d(1.1, 2.1), new Point2d(1, 2) }; line = new PolyLine2d(Arrays.stream(array).iterator()); filtered = line.noiseFilteredLine(0.5); assertEquals("size of filtered line is 3", 3, filtered.size()); assertEquals("first point of filtered line matches", line.getFirst(), filtered.getFirst()); assertEquals("last point of filtered line matches", line.getLast(), filtered.getLast()); assertEquals("mid point of filtered line is point 1 of unfiltered line", line.get(1), filtered.get(1)); array = new Point2d[] { new Point2d(1, 2), new Point2d(3, 4) }; line = new PolyLine2d(Arrays.stream(array).iterator()); filtered = line.noiseFilteredLine(10); assertEquals("Filtering a two-point line returns that line", line, filtered); array = new Point2d[] { new Point2d(1, 2), new Point2d(1, 2), new Point2d(1, 2), new Point2d(3, 4) }; line = PolyLine2d.createAndCleanPolyLine2d(array); assertEquals("cleaned line has 2 points", 2, line.size()); assertEquals("first point", array[0], line.getFirst()); assertEquals("last point", array[array.length - 1], line.getLast()); array = new Point2d[] { new Point2d(1, 2), new Point2d(1, 2), new Point2d(3, 4), new Point2d(3, 4) }; line = PolyLine2d.createAndCleanPolyLine2d(array); assertEquals("cleaned line has 2 points", 2, line.size()); assertEquals("first point", array[0], line.getFirst()); assertEquals("last point", array[array.length - 1], line.getLast()); array = new Point2d[] { new Point2d(0, -1), new Point2d(1, 2), new Point2d(1, 2), new Point2d(3, 4) }; line = PolyLine2d.createAndCleanPolyLine2d(array); assertEquals("cleaned line has 2 points", 3, line.size()); assertEquals("first point", array[0], line.getFirst()); assertEquals("last point", array[array.length - 1], line.getLast()); array = new Point2d[] { new Point2d(0, -1), new Point2d(1, 2), new Point2d(1, 2), new Point2d(1, 2), new Point2d(3, 4) }; line = PolyLine2d.createAndCleanPolyLine2d(array); assertEquals("cleaned line has 3 points", 3, line.size()); assertEquals("first point", array[0], line.getFirst()); assertEquals("mid point", array[1], line.get(1)); assertEquals("last point", array[array.length - 1], line.getLast()); try { PolyLine2d.createAndCleanPolyLine2d(new Point2d[0]); fail("Too short array should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } try { PolyLine2d.createAndCleanPolyLine2d(new Point2d[] { new Point2d(1, 2) }); fail("Too short array should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } try { PolyLine2d.createAndCleanPolyLine2d(new Point2d[] { new Point2d(1, 2), new Point2d(1, 2) }); fail("All duplicate points in array should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { PolyLine2d.createAndCleanPolyLine2d(new Point2d[] { new Point2d(1, 2), new Point2d(1, 2), new Point2d(1, 2) }); fail("All duplicate points in array should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } array = new Point2d[] { new Point2d(1, 2), new Point2d(4, 6), new Point2d(8, 9) }; line = new PolyLine2d(array); try { line.getLocation(-0.1); fail("negative location should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } double length = line.getLength(); assertEquals("Length of line is 10", 10, length, 0.000001); try { line.getLocation(length + 0.1); fail("location beyond length should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } try { line.getLocation(-0.1); fail("negative location should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } assertEquals("Length of line is 10", 10, length, 0.000001); try { line.getLocationFraction(1.1); fail("location beyond length should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } try { line.getLocationFraction(-0.1); fail("negative location should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } for (double position : new double[] { -1, 0, 2.5, 4.9, 5.1, 7.5, 9.9, 10, 11 }) { Ray2d ray = line.getLocationExtended(position); if (position < 5) { Ray2d expected = new Ray2d(array[0].interpolate(array[1], position / 5), Math.atan2(4, 3)); assertTrue("interpolated/extrapolated point", expected.epsilonEquals(ray, 0.0001, 0.00001)); } else { Ray2d expected = new Ray2d(array[1].interpolate(array[2], (position - 5) / 5), Math.atan2(3, 4)); assertTrue("interpolated/extrapolated point", expected.epsilonEquals(ray, 0.0001, 0.00001)); } ray = line.getLocationFractionExtended(position / line.getLength()); if (position < 5) { Ray2d expected = new Ray2d(array[0].interpolate(array[1], position / 5), Math.atan2(4, 3)); assertTrue("interpolated/extrapolated point", expected.epsilonEquals(ray, 0.0001, 0.00001)); } else { Ray2d expected = new Ray2d(array[1].interpolate(array[2], (position - 5) / 5), Math.atan2(3, 4)); assertTrue("interpolated/extrapolated point", expected.epsilonEquals(ray, 0.0001, 0.00001)); } } // Test the projectOrthogonal methods array = new Point2d[] { new Point2d(1, 2), new Point2d(4, 6), new Point2d(8, 9) }; line = new PolyLine2d(array); // System.out.println(line.toPlot()); for (double x = -15; x <= 20; x++) { for (double y = -15; y <= 20; y++) { Point2d xy = new Point2d(x, y); // System.out.println("x=" + x + ", y=" + y); double result = line.projectOrthogonalFractional(xy); if (!Double.isNaN(result)) { assertTrue("result must be >= 0.0", result >= 0); assertTrue("result must be <= 1.0", result <= 1.0); Ray2d ray = line.getLocationFraction(result); Point2d projected = line.projectOrthogonal(xy); assertEquals("if fraction is between 0 and 1; projectOrthogonal yiels point at that fraction", ray.x, projected.x, 00001); assertEquals("if fraction is between 0 and 1; projectOrthogonal yiels point at that fraction", ray.y, projected.y, 00001); } else { assertNull("point projects outside line", line.projectOrthogonal(xy)); } result = line.projectOrthogonalFractionalExtended(xy); if (!Double.isNaN(result)) { Point2d resultPoint = line.getLocationFractionExtended(result); if (result >= 0.0 && result <= 1.0) { Point2d closestPointOnLine = line.closestPointOnPolyLine(xy); assertEquals("resultPoint is equal to closestPoint", resultPoint, closestPointOnLine); assertEquals("getLocationFraction returns same as getLocationfractionExtended", resultPoint, line.getLocationFraction(result)); // try // { // System.out.print("c1,0,0 " + new LineSegment2d(xy, line.projectOrthogonal(xy)).toPlot()); // } // catch (DrawRuntimeException dre) // { // // Ignore - for now // } } else { // try // { // System.out.print("c0,0,1 " + new LineSegment2d(xy, line.projectOrthogonalExtended(xy)).toPlot()); // } // catch (DrawRuntimeException dre) // { // // Ignore - for now // } try { line.getLocationFraction(result); fail("illegal fraction should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } if (result < 0) { assertEquals("resultPoint lies on extention of start segment", resultPoint.distance(line.get(1)) - resultPoint.distance(line.getFirst()), line.getFirst().distance(line.get(1)), 0.0001); } else { // result > 1 assertEquals("resultPoint lies on extention of end segment", resultPoint.distance(line.get(line.size() - 2)) - resultPoint.distance(line.getLast()), line.getLast().distance(line.get(line.size() - 2)), 0.0001); } } } else { assertNull("point projects outside extended line", line.projectOrthogonalExtended(xy)); Point2d closestPointOnLine = line.closestPointOnPolyLine(xy); assertNotNull("closest point is never null", closestPointOnLine); boolean found = false; for (int index = 0; index < line.size(); index++) { Point2d linePoint = line.get(index); if (linePoint.x == closestPointOnLine.x && linePoint.y == closestPointOnLine.y) { found = true; } } assertTrue("closestPointOnLine is one of the construction points of the line", found); // try // { // System.out.print("c0,1,0 " + new LineSegment2d(xy, closestPointOnLine).toPlot()); // } // catch (DrawRuntimeException dre) // { // // Ignore - for now // } } Point2d closestPointOnLine = line.closestPointOnPolyLine(xy); // try // { // System.out.print("c0,1,0 " + new LineSegment2d(xy, closestPointOnLine).toPlot()); // } // catch (DrawRuntimeException dre) // { // // Ignore - for now // } assertNotNull("closest point is never null", closestPointOnLine); } } Point2d toleranceResultPoint = line.getLocationFraction(-0.01, 0.01); assertEquals("tolerance result matches extended fraction result", line.getLocationFraction(0), toleranceResultPoint); toleranceResultPoint = line.getLocationFraction(1.01, 0.01); assertEquals("tolerance result matches extended fraction result", line.getLocationFraction(1), toleranceResultPoint); try { line.getLocationFraction(-.011, 0.01); fail("fraction outside tolerance should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } try { line.getLocationFraction(1.011, 0.01); fail("fraction outside tolerance should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } // Test the extract and truncate methods array = new Point2d[] { new Point2d(1, 2), new Point2d(4, 6), new Point2d(8, 9) }; line = new PolyLine2d(array); length = line.getLength(); for (double to : new double[] { -10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20 }) { if (to <= 0 || to > length) { try { line.truncate(to); fail("illegal truncate should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } } else { PolyLine2d truncated = line.truncate(to); assertEquals("truncated line start with start point of line", line.getFirst(), truncated.getFirst()); assertEquals("Length of truncated line is truncate position", to, truncated.getLength(), 0.0001); } for (double from : new double[] { -10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20 }) { if (from >= to || from < 0 || to > length) { try { line.extract(from, to); fail("Illegal range should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } } else { PolyLine2d fragment = line.extract(from, to); Point2d fromPoint = line.getLocation(from); assertTrue("fragment starts at from", fromPoint.epsilonEquals(fragment.getFirst(), 0.00001)); Point2d toPoint = line.getLocation(to); assertTrue("fragment ends at to", toPoint.epsilonEquals(fragment.getLast(), 0.00001)); assertEquals("Length of fragment", to - from, fragment.getLength(), 0.0001); if (from == 0) { assertEquals("fragment starts at begin of line", line.getFirst(), fragment.getFirst()); } if (to == length) { assertEquals("fragment ends at end of line", line.getLast(), fragment.getLast()); } } } } try { line.extract(Double.NaN, 10.0); fail("NaN value should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } try { line.extract(0.0, Double.NaN); fail("NaN value should have thrown a DrawException"); } catch (DrawException de) { // Ignore expected exception } // Verify that hashCode. Check that the result depends on the actual coordinates. assertNotEquals("hash code takes x coordinate of first point into account", new PolyLine2d(new Point2d(0, 0), new Point2d(1, 1)).hashCode(), new PolyLine2d(new Point2d(1, 0), new Point2d(1, 1)).hashCode()); assertNotEquals("hash code takes y coordinate of first point into account", new PolyLine2d(new Point2d(0, 0), new Point2d(1, 1)).hashCode(), new PolyLine2d(new Point2d(0, 1), new Point2d(1, 1)).hashCode()); assertNotEquals("hash code takes x coordinate of second point into account", new PolyLine2d(new Point2d(0, 0), new Point2d(1, 1)).hashCode(), new PolyLine2d(new Point2d(0, 0), new Point2d(2, 1)).hashCode()); assertNotEquals("hash code takes y coordinate of second point into account", new PolyLine2d(new Point2d(0, 0), new Point2d(1, 1)).hashCode(), new PolyLine2d(new Point2d(0, 0), new Point2d(1, 2)).hashCode()); // Verify the equals method. assertTrue("line is equal to itself", line.equals(line)); assertFalse("line is not equal to a different line", line.equals(new PolyLine2d(new Point2d(123, 456), new Point2d(789, 101112)))); assertFalse("line is not equal to null", line.equals(null)); assertFalse("line is not equal to a different kind of object", line.equals("unlikely")); assertTrue("Line is equal to line from same set of points", line.equals(new PolyLine2d(line.getPoints()))); // Make a line that differs only in the very last point Point2d[] otherArray = Arrays.copyOf(array, array.length); otherArray[otherArray.length - 1] = new Point2d(otherArray[otherArray.length - 1].x, otherArray[otherArray.length - 1].y + 5); PolyLine2d other = new PolyLine2d(otherArray); assertFalse("PolyLine2d that differs in y of last point is different", line.equals(other)); } /** * Test the concatenate method. * @throws DrawException should not happen; this test has failed if it does happen */ @Test public final void concatenateTest() throws DrawException { Point2d p0 = new Point2d(1.1, 2.2); Point2d p1 = new Point2d(2.1, 2.2); Point2d p2 = new Point2d(3.1, 2.2); Point2d p3 = new Point2d(4.1, 2.2); Point2d p4 = new Point2d(5.1, 2.2); Point2d p5 = new Point2d(6.1, 2.2); PolyLine2d l0 = new PolyLine2d(p0, p1, p2); PolyLine2d l1 = new PolyLine2d(p2, p3); PolyLine2d l2 = new PolyLine2d(p3, p4, p5); PolyLine2d ll = PolyLine2d.concatenate(l0, l1, l2); assertEquals("size is 6", 6, ll.size()); assertEquals("point 0 is p0", p0, ll.get(0)); assertEquals("point 1 is p1", p1, ll.get(1)); assertEquals("point 2 is p2", p2, ll.get(2)); assertEquals("point 3 is p3", p3, ll.get(3)); assertEquals("point 4 is p4", p4, ll.get(4)); assertEquals("point 5 is p5", p5, ll.get(5)); ll = PolyLine2d.concatenate(l1); assertEquals("size is 2", 2, ll.size()); assertEquals("point 0 is p2", p2, ll.get(0)); assertEquals("point 1 is p3", p3, ll.get(1)); try { PolyLine2d.concatenate(l0, l2); fail("Gap should have throw an exception"); } catch (DrawException e) { // Ignore expected exception } try { PolyLine2d.concatenate(); fail("concatenate of empty list should have thrown an exception"); } catch (DrawException e) { // Ignore expected exception } // Test concatenate methods with tolerance PolyLine2d thirdLine = new PolyLine2d(p4, p5); for (double tolerance : new double[] { 0.1, 0.01, 0.001, 0.0001, 0.00001 }) { for (double actualError : new double[] { tolerance * 0.9, tolerance * 1.1 }) { int maxDirection = 10; for (int direction = 0; direction < maxDirection; direction++) { double dx = actualError * Math.cos(Math.PI * 2 * direction / maxDirection); double dy = actualError * Math.sin(Math.PI * 2 * direction / maxDirection); PolyLine2d otherLine = new PolyLine2d(new Point2d(p2.x + dx, p2.y + dy), p3, p4); if (actualError < tolerance) { try { PolyLine2d.concatenate(tolerance, l0, otherLine); } catch (DrawException oge) { PolyLine2d.concatenate(tolerance, l0, otherLine); fail("concatenation with error " + actualError + " and tolerance " + tolerance + " should not have failed"); } try { PolyLine2d.concatenate(tolerance, l0, otherLine, thirdLine); } catch (DrawException oge) { fail("concatenation with error " + actualError + " and tolerance " + tolerance + " should not have failed"); } } else { try { PolyLine2d.concatenate(tolerance, l0, otherLine); } catch (DrawException oge) { // Ignore expected exception } try { PolyLine2d.concatenate(tolerance, l0, otherLine, thirdLine); } catch (DrawException oge) { // Ignore expected exception } } } } } } /** * Test the offsetLine methods. * @throws DrawException when that happens uncaught; this test has failed */ @Test public void testOffsetLine() throws DrawException { for (Point2d[] points : new Point2d[][] { { new Point2d(1, 2), new Point2d(3, 50) }, { new Point2d(-40, -20), new Point2d(5, -2), new Point2d(3, 50) }, { new Point2d(-40, -20), new Point2d(5, -2), new Point2d(3, -50) } }) { for (double angle = 0; angle < 2 * Math.PI; angle += Math.PI / 360) { Transform2d transform = new Transform2d().rotation(angle); Point2d[] transformed = new Point2d[points.length]; for (int index = 0; index < points.length; index++) { transformed[index] = transform.transform(points[index]); } PolyLine2d line = new PolyLine2d(transformed); // System.out.println("angle " + Math.toDegrees(angle) + " line " + line); try { line.offsetLine(Double.NaN); fail("NaN offset should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException iae) { // Ignore expected exception } assertEquals("offset 0 yields the reference line", line, line.offsetLine(0)); // System.out.print("reference line " + line.toPlot()); for (double offset : new double[] { 1, 10, 0.1, -0.1, -10 }) { PolyLine2d offsetLine = line.offsetLine(offset); // System.out.print("angle " + angle + ", offset " + offset + ": " + offsetLine.toPlot()); if (points.length == 2) { assertEquals("two-point line should have a two-point offset line", 2, offsetLine.size()); assertEquals("length of offset line of two-point reference line equals length of reference line", line.getLength(), offsetLine.getLength(), 0.01); } assertEquals("offset at start", Math.abs(offset), line.getFirst().distance(offsetLine.getFirst()), 0.01); assertEquals("offset at end", Math.abs(offset), line.getLast().distance(offsetLine.getLast()), 0.01); // Verify that negative offset works in the direction opposite to positive assertEquals("offset to the left vs to the right differs by twice the offset", Math.abs(2 * offset), offsetLine.getFirst().distance(line.offsetLine(-offset).getFirst()), 0.001); // The following four may be false if the offset is not small comparable to the lenght of the first or last // segment of the line assertEquals("projection of first point of line onto offset line is (almost) first point of offset line", 0, offsetLine.getLocationExtended( offsetLine.projectOrthogonalFractionalExtended(line.getFirst()) * offsetLine.getLength()) .distance(offsetLine.getFirst()), 0.01); double fraction = offsetLine.projectOrthogonalFractionalExtended(line.getLast()); assertEquals("fraction should be 1 with maximum error a few ULP", 1, fraction, 0.000001); if (fraction > 1.0) { fraction = 1.0; } assertEquals("projection of last point of line onto offset line is (almost) last point of offset line", 0, offsetLine.getLocation(fraction * offsetLine.getLength()).distance(offsetLine.getLast()), 0.01); assertEquals("projection of first point of offset line onto line is (almost) first point of line", 0, line.getLocationExtended( line.projectOrthogonalFractionalExtended(offsetLine.getFirst()) * line.getLength()) .distance(line.getFirst()), 0.01); fraction = line.projectOrthogonalFractionalExtended(offsetLine.getLast()); assertEquals("fraction should be 1 with maximum error a few ULP", 1, fraction, 0.000001); if (fraction > 1.0) { fraction = 1.0; } assertEquals("projection of last point of offset line onto line is (almost) last point of line", 0, line.getLocation(fraction * line.getLength()).distance(line.getLast()), 0.01); } } } PolyLine2d line = new PolyLine2d(new Point2d(1, 2), new Point2d(3, 4)); try { line.offsetLine(1, 0, PolyLine2d.DEFAULT_OFFSET_MINIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_MAXIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_FILTER_RATIO, PolyLine2d.DEFAULT_OFFSET_PRECISION); fail("zero circle precision should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException iae) { // Ignore expected exception } try { line.offsetLine(1, Double.NaN, PolyLine2d.DEFAULT_OFFSET_MINIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_MAXIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_FILTER_RATIO, PolyLine2d.DEFAULT_OFFSET_PRECISION); fail("NaN circle precision should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException iae) { // Ignore expected exception } try { line.offsetLine(1, PolyLine2d.DEFAULT_CIRCLE_PRECISION, 0, PolyLine2d.DEFAULT_OFFSET_MAXIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_FILTER_RATIO, PolyLine2d.DEFAULT_OFFSET_PRECISION); fail("zero offsetMinimumFilterValue should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException iae) { // Ignore expected exception } try { line.offsetLine(1, PolyLine2d.DEFAULT_CIRCLE_PRECISION, Double.NaN, PolyLine2d.DEFAULT_OFFSET_MAXIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_FILTER_RATIO, PolyLine2d.DEFAULT_OFFSET_PRECISION); fail("NaN offsetMinimumFilterValue should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException iae) { // Ignore expected exception } try { line.offsetLine(1, PolyLine2d.DEFAULT_CIRCLE_PRECISION, PolyLine2d.DEFAULT_OFFSET_MAXIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_MAXIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_FILTER_RATIO, PolyLine2d.DEFAULT_OFFSET_PRECISION); fail("offsetMinimumFilterValue not less than offsetMaximumFilterValue should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException iae) { // Ignore expected exception } try { line.offsetLine(1, PolyLine2d.DEFAULT_CIRCLE_PRECISION, PolyLine2d.DEFAULT_OFFSET_MINIMUM_FILTER_VALUE, 0, PolyLine2d.DEFAULT_OFFSET_FILTER_RATIO, PolyLine2d.DEFAULT_OFFSET_PRECISION); fail("zero offsetMaximumfilterValue should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException iae) { // Ignore expected exception } try { line.offsetLine(1, PolyLine2d.DEFAULT_CIRCLE_PRECISION, PolyLine2d.DEFAULT_OFFSET_MINIMUM_FILTER_VALUE, Double.NaN, PolyLine2d.DEFAULT_OFFSET_FILTER_RATIO, PolyLine2d.DEFAULT_OFFSET_PRECISION); fail("NaN offsetMaximumfilterValue should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException iae) { // Ignore expected exception } try { line.offsetLine(1, PolyLine2d.DEFAULT_CIRCLE_PRECISION, PolyLine2d.DEFAULT_OFFSET_MINIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_MAXIMUM_FILTER_VALUE, 0, PolyLine2d.DEFAULT_OFFSET_PRECISION); fail("zero offsetFilterRatio should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException iae) { // Ignore expected exception } try { line.offsetLine(1, PolyLine2d.DEFAULT_CIRCLE_PRECISION, PolyLine2d.DEFAULT_OFFSET_MINIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_MAXIMUM_FILTER_VALUE, Double.NaN, PolyLine2d.DEFAULT_OFFSET_PRECISION); fail("NaN offsetFilterRatio should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException iae) { // Ignore expected exception } try { line.offsetLine(1, PolyLine2d.DEFAULT_CIRCLE_PRECISION, PolyLine2d.DEFAULT_OFFSET_MINIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_MAXIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_FILTER_RATIO, 0); fail("zero offsetPrecision should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException iae) { // Ignore expected exception } try { line.offsetLine(1, PolyLine2d.DEFAULT_CIRCLE_PRECISION, PolyLine2d.DEFAULT_OFFSET_MINIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_MAXIMUM_FILTER_VALUE, PolyLine2d.DEFAULT_OFFSET_FILTER_RATIO, Double.NaN); fail("NaN offsetPrecision should have thrown an IllegalArgumentException"); } catch (IllegalArgumentException iae) { // Ignore expected exception } } /** * Test the projectRay method. * @throws DrawException cannot happen */ @Test public void testProjectRayTransition() throws DrawException { List innerDesignLinePoints = new ArrayList<>(); List outerDesignLinePoints = new ArrayList<>(); // Approximate a quarter circle with radius 5 double innerRadius = 5; // Approximate a quarter circle with radius 8 double outerRadius = 8; for (int degree = 0; degree <= 90; degree++) { innerDesignLinePoints.add(new Point2d(innerRadius * Math.sin(Math.toRadians(degree)), innerRadius * Math.cos(Math.toRadians(degree)))); outerDesignLinePoints.add(new Point2d(outerRadius * Math.sin(Math.toRadians(degree)), outerRadius * Math.cos(Math.toRadians(degree)))); } PolyLine2d innerDesignLine = new PolyLine2d(innerDesignLinePoints); PolyLine2d outerDesignLine = new PolyLine2d(outerDesignLinePoints); List transitionLinePoints = new ArrayList<>(); int degree = 0; Point2d prevPoint = innerDesignLinePoints.get(0); while (degree < 10) { double x = innerRadius * Math.sin(Math.toRadians(degree)); double y = innerRadius * Math.cos(Math.toRadians(degree)); double direction = prevPoint.directionTo(new Point2d(x, y)); Ray2d ray = new Ray2d(x, y, direction); transitionLinePoints.add(ray); prevPoint = ray; degree++; } while (degree <= 80) { double phase = Math.PI * (degree - 10) / 70; double radius = innerRadius + (outerRadius - innerRadius) * (1 - Math.cos(phase) / 2 - 0.5); double x = radius * Math.sin(Math.toRadians(degree)); double y = radius * Math.cos(Math.toRadians(degree)); double direction = prevPoint.directionTo(new Point2d(x, y)); Ray2d ray = new Ray2d(x, y, direction); transitionLinePoints.add(ray); prevPoint = ray; degree++; } while (degree < 90) { double x = outerRadius * Math.sin(Math.toRadians(degree)); double y = outerRadius * Math.cos(Math.toRadians(degree)); double direction = prevPoint.directionTo(new Point2d(x, y)); Ray2d ray = new Ray2d(x, y, direction); transitionLinePoints.add(ray); prevPoint = ray; degree++; } PolyLine2d transitionLine = new PolyLine2d(transitionLinePoints); // System.out.print("inner design line: " + innerDesignLine.toPlot()); // System.out.print("outer design line: " + outerDesignLine.toPlot()); // System.out.print("transition line: " + transitionLine.toPlot()); List projections = new ArrayList<>(); for (Iterator iterator = transitionLine.getPoints(); iterator.hasNext();) { Point2d p = iterator.next(); if (p instanceof Ray2d) { Ray2d ray = (Ray2d) p; Point2d transitionLinePoint = new Point2d(ray.x, ray.y); projections.add(transitionLinePoint); double location = innerDesignLine.projectRay(ray); if (!Double.isNaN(location)) { Point2d projection = innerDesignLine.getLocation(location); projections.add(new Point2d(projection.x, projection.y)); projections.add(transitionLinePoint); } location = outerDesignLine.projectRay(ray); if (!Double.isNaN(location)) { Point2d projection = outerDesignLine.getLocation(location); projections.add(new Point2d(projection.x, projection.y)); projections.add(transitionLinePoint); } } } // System.out.print("cosine projections: " + PolyLine2d.createAndCleanPolyLine2d(projections).toPlot()); Ray2d from = new Ray2d(outerDesignLine.get(10).x, outerDesignLine.get(10).y, outerDesignLine.get(10).directionTo(outerDesignLine.get(11))); Ray2d to = new Ray2d(innerDesignLine.get(80).x, innerDesignLine.get(80).y, innerDesignLine.get(80).directionTo(innerDesignLine.get(81))); transitionLine = Bezier.cubic(from, to); // System.out.print("Bezier: " + transitionLine.toPlot()); projections = new ArrayList<>(); Point2d prev = null; for (Iterator iterator = transitionLine.getPoints(); iterator.hasNext();) { Point2d p = iterator.next(); if (prev != null) { Ray2d ray = new Ray2d(prev, prev.directionTo(p)); Point2d transitionLinePoint = new Point2d(ray.x, ray.y); projections.add(transitionLinePoint); double location = innerDesignLine.projectRay(ray); if (!Double.isNaN(location)) { try { innerDesignLine.getLocation(location); } catch (DrawException de) { System.out.println("WTF"); outerDesignLine.projectRay(ray); // WTF } Point2d projection = innerDesignLine.getLocation(location); projections.add(new Point2d(projection.x, projection.y)); projections.add(transitionLinePoint); } location = outerDesignLine.projectRay(ray); if (!Double.isNaN(location)) { try { outerDesignLine.getLocation(location); } catch (DrawException de) { System.out.println("WTF"); outerDesignLine.projectRay(ray); // WTF } Point2d projection = outerDesignLine.getLocation(location); projections.add(new Point2d(projection.x, projection.y)); projections.add(transitionLinePoint); } } prev = p; } // System.out.print("Bezier projections: " + PolyLine2d.createAndCleanPolyLine2d(projections).toPlot()); } /** * Test the projectRay method. */ @Test public void testProjectRay() { PolyLine2d reference = new PolyLine2d(new Point2d(0, 1), new Point2d(5, 1), new Point2d(10, 6), new Point2d(20, 6)); // System.out.print("reference line is " + reference.toPlot()); PolyLine2d offsetLine = reference.offsetLine(-10); // Now we have a line with a somewhat smooth 45 degree curve around (5, 1) with radius 10 // System.out.print("offset line is " + offsetLine.toPlot()); double slope = 0.25; double slopeAngle = Math.atan2(slope, 1); double prevProjection = -100; List projections = new ArrayList<>(); for (double x = -0.5; x < 19; x += 0.25) { double y = -5 + x * slope; Ray2d ray = new Ray2d(x, y, slopeAngle); projections.add(ray); double projectionLocation = offsetLine.projectRay(ray); if (Double.isNaN(projectionLocation)) { offsetLine.projectRay(ray); } assertFalse("There is a projection", Double.isNaN(projectionLocation)); try { Point2d projectedPoint = offsetLine.getLocation(projectionLocation); // System.out.println(String.format("DirectedPoint %s projects on line at %.3f. which is at %s", ray, // projectionLocation, projectedPoint)); projections.add(projectedPoint); projections.add(ray); // And back to ray } catch (DrawException e) { e.printStackTrace(); } assertTrue("projection increases monotonous", projectionLocation > prevProjection); prevProjection = projectionLocation; } // System.out.print("projections: " + new PolyLine2d(projections).toPlot()); projections.clear(); prevProjection = -100; for (double x = 1.5; x < 21; x += 0.25) { double y = -15 + x * slope; Ray2d ray = new Ray2d(x, y, slopeAngle); double projectionLocation = offsetLine.projectRay(ray); if (Double.isNaN(projectionLocation)) { System.out.println("x " + x + " gives NaN result"); continue; } try { projections.add(ray); Point2d projectedPoint = offsetLine.getLocation(projectionLocation); // System.out.println(String.format("DirectedPoint %s projects on line at %.3f. which is at %s", ray, // projectionLocation, projectedPoint)); projections.add(projectedPoint); projections.add(ray); // And back to ray } catch (DrawException e) { e.printStackTrace(); } assertTrue("projection increases monotonous", projectionLocation > prevProjection); prevProjection = projectionLocation; } // System.out.print("projections: " + new PolyLine2d(projections).toPlot()); } /** * Test the debugging output methods. */ @Test public void testExports() { Point2d[] points = new Point2d[] { new Point2d(123.456, 345.678), new Point2d(234.567, 456.789), new Point2d(-12.345, -34.567) }; PolyLine2d pl = new PolyLine2d(points); String[] out = pl.toExcel().split("\\n"); assertEquals("Excel output consists of one line per point", points.length, out.length); for (int index = 0; index < points.length; index++) { String[] fields = out[index].split("\\t"); assertEquals("each line consists of two fields", 2, fields.length); try { double x = Double.parseDouble(fields[0].trim()); assertEquals("x matches", points[index].x, x, 0.001); } catch (NumberFormatException nfe) { fail("First field " + fields[0] + " does not parse as a double"); } try { double y = Double.parseDouble(fields[1].trim()); assertEquals("y matches", points[index].y, y, 0.001); } catch (NumberFormatException nfe) { fail("Second field " + fields[1] + " does not parse as a double"); } } out = pl.toPlot().split(" L"); assertEquals("Plotter output consists of one coordinate pair per point", points.length, out.length); for (int index = 0; index < points.length; index++) { String[] fields = out[index].split(","); assertEquals("each line consists of two fields", 2, fields.length); if (index == 0) { assertTrue(fields[0].startsWith("M")); fields[0] = fields[0].substring(1); } try { double x = Double.parseDouble(fields[0].trim()); assertEquals("x matches", points[index].x, x, 0.001); } catch (NumberFormatException nfe) { fail("First field " + fields[0] + " does not parse as a double"); } try { double y = Double.parseDouble(fields[1].trim()); assertEquals("y matches", points[index].y, y, 0.001); } catch (NumberFormatException nfe) { fail("Second field " + fields[1] + " does not parse as a double"); } } } /** * Verify that a Line2d contains the same points as an array of Point2d. * @param line Line2d; the OTS line * @param points Point2d[]; the OTSPoint array * @throws DrawException should not happen; this test has failed if it does happen */ private void verifyPointsAndSegments(final PolyLine2d line, final Point2d[] points) throws DrawException { assertEquals("Line should have same number of points as point array", line.size(), points.length); for (int i = 0; i < points.length; i++) { assertEquals("x of point i should match", points[i].x, line.get(i).x, Math.ulp(points[i].x)); assertEquals("y of point i should match", points[i].y, line.get(i).y, Math.ulp(points[i].y)); assertEquals("x of point i should match", points[i].x, line.getX(i), Math.ulp(points[i].x)); assertEquals("y of point i should match", points[i].y, line.getY(i), Math.ulp(points[i].y)); if (i < points.length - 1) { LineSegment2d segment = line.getSegment(i); assertEquals("begin x of line segment i should match", points[i].x, segment.startX, Math.ulp(points[i].x)); assertEquals("begin y of line segment i should match", points[i].y, segment.startY, Math.ulp(points[i].y)); assertEquals("end x of line segment i should match", points[i + 1].x, segment.endX, Math.ulp(points[i + 1].x)); assertEquals("end y of line segment i should match", points[i + 1].y, segment.endY, Math.ulp(points[i + 1].y)); } else { try { line.getSegment(i); fail("Too large index should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { line.getSegment(-1); fail("Negative index should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } } } } /** * Test the hashCode and Equals methods. * @throws DrawException when that happens uncaught; this test has failed * @throws NullPointerException when that happens uncaught; this test has failed */ @SuppressWarnings("unlikely-arg-type") @Test public void testToStringHashCodeAndEquals() throws NullPointerException, DrawException { PolyLine2d line = new PolyLine2d(new Point2d[] { new Point2d(1, 2), new Point2d(4, 6), new Point2d(8, 9) }); assertTrue("toString returns something descriptive", line.toString().startsWith("PolyLine2d [")); assertTrue("toString can suppress the class name", line.toString().indexOf(line.toString(true)) > 0); // Verify that hashCode. Check that the result depends on the actual coordinates. assertNotEquals("hash code takes x coordinate into account", new PolyLine2d(new Point2d(0, 0), new Point2d(1, 1)).hashCode(), new PolyLine2d(new Point2d(1, 0), new Point2d(1, 1)).hashCode()); assertNotEquals("hash code takes y coordinate into account", new PolyLine2d(new Point2d(0, 0), new Point2d(1, 1)).hashCode(), new PolyLine2d(new Point2d(0, 1), new Point2d(1, 1)).hashCode()); assertNotEquals("hash code takes x coordinate into account", new PolyLine2d(new Point2d(0, 0), new Point2d(1, 1)).hashCode(), new PolyLine2d(new Point2d(0, 0), new Point2d(2, 1)).hashCode()); assertNotEquals("hash code takes y coordinate into account", new PolyLine2d(new Point2d(0, 0), new Point2d(1, 1)).hashCode(), new PolyLine2d(new Point2d(0, 0), new Point2d(1, 2)).hashCode()); // Verify the equals method. assertTrue("line is equal to itself", line.equals(line)); assertFalse("line is not equal to a different line", line.equals(new PolyLine3d(new Point3d(123, 456, 789), new Point3d(789, 101112, 2)))); assertFalse("line is not equal to null", line.equals(null)); assertFalse("line is not equal to a different kind of object", line.equals("unlikely")); assertEquals("equals verbatim copy", line, new PolyLine2d(new Point2d[] { new Point2d(1, 2), new Point2d(4, 6), new Point2d(8, 9) })); assertNotEquals("equals checks x", line, new PolyLine2d(new Point2d[] { new Point2d(2, 2), new Point2d(4, 6), new Point2d(8, 9) })); assertNotEquals("equals checks y", line, new PolyLine2d(new Point2d[] { new Point2d(1, 2), new Point2d(4, 7), new Point2d(8, 9) })); assertTrue("Line is equal to line from same set of points", line.equals(new PolyLine2d(line.getPoints()))); } /** * Problem with limited precision when getting location almost at end. * @throws DrawException when that happens this test has triggered the problem */ @Test public void testOTS2Problem() throws DrawException { PolyLine2d line = new PolyLine2d(new Point2d(100, 0), new Point2d(100.1, 0)); double length = line.getLength(); line.getLocation(length - Math.ulp(length)); } }