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.assertTrue; import static org.junit.Assert.fail; import java.util.Iterator; import org.djutils.draw.DrawRuntimeException; import org.djutils.draw.bounds.Bounds3d; import org.djutils.draw.point.Point3d; import org.junit.Test; /** * Segment3dTest.java. *

* Copyright (c) 2020-2022 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 LineSegment3dTest { /** * Test the constructors. */ @Test public void constructorTest() { verifySegment("Segment from four coordinates", new LineSegment3d(1, 2, 3, 4, 5, 6), 1, 2, 3, 4, 5, 6); verifySegment("Segment from two coordinates and a Point3d", new LineSegment3d(1, 2, 3, new Point3d(4, 5, 6)), 1, 2, 3, 4, 5, 6); verifySegment("Segment from a Point3d and two coordinates", new LineSegment3d(new Point3d(1, 2, 3), 4, 5, 6), 1, 2, 3, 4, 5, 6); verifySegment("Segment from two Point3d objects", new LineSegment3d(new Point3d(1, 2, 3), new Point3d(4, 5, 6)), 1, 2, 3, 4, 5, 6); try { new LineSegment3d(1, 2, 3, 1, 2, 3); fail("idential start and end should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } new LineSegment3d(1, 2, 3, 1, 2, 4); new LineSegment3d(1, 2, 3, 1, 3, 3); new LineSegment3d(1, 2, 3, 2, 2, 3); } /** * Check that a segment has all the right values. * @param description String; description of the test * @param segment Segment3d; the segment * @param expectedStartX double; the expected x value for the start of the segment * @param expectedStartY double; the expected y value for the start of the segment * @param expectedStartZ double; the expected z value for the start of the segment * @param expectedEndX double; the expected x value for the end of the segment * @param expectedEndY double; the expected y value for the end of the segment * @param expectedEndZ double; the expected y value for the end of the segment */ public void verifySegment(final String description, final LineSegment3d segment, final double expectedStartX, final double expectedStartY, final double expectedStartZ, final double expectedEndX, final double expectedEndY, final double expectedEndZ) { assertEquals(description + " startX", expectedStartX, segment.startX, 0.0001); assertEquals(description + " startY", expectedStartY, segment.startY, 0.0001); assertEquals(description + " startZ", expectedStartZ, segment.startZ, 0.0001); assertEquals(description + " toX", expectedEndX, segment.endX, 0.0001); assertEquals(description + " endY", expectedEndY, segment.endY, 0.0001); assertEquals(description + " endZ", expectedEndZ, segment.endZ, 0.0001); assertEquals(description + " getStartPoint x", expectedStartX, segment.getStartPoint().x, 0.0001); assertEquals(description + " getStartPoint y", expectedStartY, segment.getStartPoint().y, 0.0001); assertEquals(description + " getStartPoint z", expectedStartZ, segment.getStartPoint().z, 0.0001); assertEquals(description + " getEndPoint x", expectedEndX, segment.getEndPoint().x, 0.0001); assertEquals(description + " getEndPoint y", expectedEndY, segment.getEndPoint().y, 0.0001); assertEquals(description + " getEndPoint z", expectedEndZ, segment.getEndPoint().z, 0.0001); assertEquals(description + " length", Math .hypot(Math.hypot(expectedEndX - expectedStartX, expectedEndY - expectedStartY), expectedEndZ - expectedStartZ), segment.getLength(), 0.0001); assertEquals(description + " size is 2", 2, segment.size()); Iterator iterator = segment.getPoints(); assertTrue(description + " iterator has data", iterator.hasNext()); Point3d point = iterator.next(); assertEquals(description + " iterator first point x", expectedStartX, point.x, 0.0001); assertEquals(description + " iterator first point y", expectedStartY, point.y, 0.0001); assertEquals(description + " iterator first point z", expectedStartZ, point.z, 0.0001); assertTrue(description + " iterator has more data", iterator.hasNext()); point = iterator.next(); assertEquals(description + " iterator second point x", expectedEndX, point.x, 0.0001); assertEquals(description + " iterator second point y", expectedEndY, point.y, 0.0001); assertEquals(description + " iterator second point z", expectedEndZ, point.z, 0.0001); assertFalse(description + " iterator has no more data", iterator.hasNext()); Bounds3d bounds = segment.getBounds(); assertEquals(description + " bounds minX", Math.min(expectedStartX, expectedEndX), bounds.getMinX(), 0.0001); assertEquals(description + " bounds maxX", Math.max(expectedStartX, expectedEndX), bounds.getMaxX(), 0.0001); assertEquals(description + " bounds minY", Math.min(expectedStartY, expectedEndY), bounds.getMinY(), 0.0001); assertEquals(description + " bounds maxY", Math.max(expectedStartY, expectedEndY), bounds.getMaxY(), 0.0001); assertEquals(description + " bounds minZ", Math.min(expectedStartZ, expectedEndZ), bounds.getMinZ(), 0.0001); assertEquals(description + " bounds maxZ", Math.max(expectedStartZ, expectedEndZ), bounds.getMaxZ(), 0.0001); assertTrue(description + " toString returns something descriptive", segment.toString().startsWith("LineSegment3d ")); assertTrue(description + " toString can suppress the class name", segment.toString().indexOf(segment.toString(true)) > 0); } /** * Test the getLocation methods. */ @Test public void locationTest() { Point3d startPoint = new Point3d(3, 4, 5); Point3d endPoint = new Point3d(9, 20, 15); LineSegment3d segment = new LineSegment3d(startPoint, endPoint); try { segment.getLocation(Double.NaN); fail("NaN position should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { segment.getLocationExtended(Double.POSITIVE_INFINITY); fail("Infinity position should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } try { segment.getLocationExtended(Double.NEGATIVE_INFINITY); fail("Infinity position should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } for (double position : new double[] {-3, -0.5, 0, 1, 10, 100}) { if (position < 0 || position > segment.getLength()) { try { segment.getLocation(position); fail("position out of bounds should have thrown a DrawRuntimeException"); } catch (DrawRuntimeException dre) { // Ignore expected exception } } else { Ray3d ray = segment.getLocation(position); assertEquals("distance from start point", position, ray.distance(startPoint), 0.0001); assertEquals("distance from end point", segment.getLength() - position, ray.distance(endPoint), 0.0001); assertEquals("direction of ray phi", startPoint.project().directionTo(endPoint.project()), ray.phi, 0.0001); assertEquals("direction of ray theta", Math.atan2(endPoint.z - startPoint.z, segment.project().getLength()), ray.theta, 0.0001); } Ray3d ray = segment.getLocationExtended(position); assertEquals("distance from start point", Math.abs(position), ray.distance(startPoint), 0.0001); assertEquals("distance from end point", Math.abs(segment.getLength() - position), ray.distance(endPoint), 0.0001); assertEquals("direction of ray phi", startPoint.project().directionTo(endPoint.project()), ray.phi, 0.0001); assertEquals("direction of ray theta", Math.atan2(endPoint.z - startPoint.z, segment.project().getLength()), ray.theta, 0.0001); } } /** * Test the closestPointOnSegment method. */ @Test public void closestPointOnSegmentTest() { LineSegment3d segment = new LineSegment3d(1, 2, 3, 20, 10, 15); try { segment.closestPointOnSegment(null); fail("Null for point should have thrown a NullPointerException"); } catch (NullPointerException npe) { // Ignore expected exception } Point3d result = segment.closestPointOnSegment(new Point3d(1, 2, 0)); assertEquals("result is start point x", segment.startX, result.x, 0); assertEquals("result is start point y", segment.startY, result.y, 0); assertEquals("result is start point z", segment.startZ, result.z, 0); result = segment.closestPointOnSegment(new Point3d(1, 0, 3)); assertEquals("result is start point x", segment.startX, result.x, 0); assertEquals("result is start point y", segment.startY, result.y, 0); assertEquals("result is start point z", segment.startZ, result.z, 0); result = segment.closestPointOnSegment(new Point3d(0, 2, 3)); assertEquals("result is start point x", segment.startX, result.x, 0); assertEquals("result is start point y", segment.startY, result.y, 0); assertEquals("result is start point z", segment.startZ, result.z, 0); result = segment.closestPointOnSegment(new Point3d(1, 2, 3)); assertEquals("result is start point x", segment.startX, result.x, 0); assertEquals("result is start point y", segment.startY, result.y, 0); assertEquals("result is start point z", segment.startZ, result.z, 0); Point3d projectingPoint = new Point3d(10, 10, 10); result = segment.closestPointOnSegment(projectingPoint); // Projects at a point along the segment double distanceFromStart = result.distance(segment.getStartPoint()); assertTrue("distance from start is > 0", distanceFromStart > 0); double distanceToEnd = result.distance(segment.getEndPoint()); // System.out.println(segment + " projectingPoint=" + projectingPoint + ", result=" + result); assertTrue("distance to end point is > 0", distanceToEnd > 0); assertEquals("sum of distances is length of segment", segment.getLength(), distanceFromStart + distanceToEnd, 0.0001); Point3d doubleProjected = segment.closestPointOnSegment(result); assertEquals("projecting the projection yields the projection", 0, doubleProjected.distance(result), 0.0001); result = segment.closestPointOnSegment(new Point3d(21, 10, 15)); assertEquals("result is end point", segment.endX, result.x, 0); assertEquals("result is end point", segment.endY, result.y, 0); result = segment.closestPointOnSegment(new Point3d(20, 11, 15)); assertEquals("result is end point", segment.endX, result.x, 0); assertEquals("result is end point", segment.endY, result.y, 0); result = segment.closestPointOnSegment(new Point3d(20, 10, 16)); assertEquals("result is end point", segment.endX, result.x, 0); assertEquals("result is end point", segment.endY, result.y, 0); result = segment.closestPointOnSegment(new Point3d(20, 10, 15)); assertEquals("result is end point", segment.endX, result.x, 0); assertEquals("result is end point", segment.endY, result.y, 0); } /** * Test the project methods. */ @Test public void testProject() { LineSegment3d segment = new LineSegment3d(1, 2, 3, 20, 10, 5); assertTrue("projects outside", Double.isNaN(segment.projectOrthogonalFractional(new Point3d(1, 1, 1)))); assertTrue("projects before start", segment.projectOrthogonalFractionalExtended(new Point3d(1, 1, 1)) < 0); assertEquals("projects at -2", -2, segment.projectOrthogonalFractionalExtended(new Point3d(1 - 19 - 19 + 8, 2 - 8 - 8 - 19, 3 - 2 - 2)), 0.0001); assertEquals("point near half way (not on segment) project at about half way", 0.5, segment.projectOrthogonalFractional(new Point3d(11, 1, 4)), 0.1); assertTrue("projects outside", Double.isNaN(segment.projectOrthogonalFractional(new Point3d(25, 15, 6)))); assertTrue("projects after end", segment.projectOrthogonalFractionalExtended(new Point3d(25, 15, 6)) > 1); assertEquals("projects at 2", 2, segment.projectOrthogonalFractionalExtended(new Point3d(1 + 19 + 19 - 8, 2 + 8 + 8 + 19, 3 + 2 + 2)), 0.0001); } /** * Test the toExcel method. * @throws NumberFormatException if that happens, this test has failed */ @Test public void testToExcel() throws NumberFormatException { LineSegment3d segment = new LineSegment3d(1, 2, 3, 20, 10, 5); String result = segment.toExcel(); String[] lines = result.split("\n"); assertEquals("result is two lines", 2, lines.length); for (int lineNo = 0; lineNo < lines.length; lineNo++) { String[] fields = lines[lineNo].trim().split("\t"); assertEquals("Line consists of three fields", 3, fields.length); for (int fieldNo = 0; fieldNo < fields.length; fieldNo++) { double value = Double.parseDouble(fields[fieldNo]); double expectedValue = lineNo == 0 ? (fieldNo == 0 ? segment.startX : fieldNo == 1 ? segment.startY : segment.startZ) : (fieldNo == 0 ? segment.endX : fieldNo == 1 ? segment.endY : segment.endZ); assertEquals("field contains the correct value", expectedValue, value, 0.0001); } } } /** * Test the equals and hasCode methods. */ @Test public void equalsAndHashCodeTest() { LineSegment3d segment = new LineSegment3d(1, 2, 3, -3, -4, -5); assertEquals("equal to itself", segment, segment); assertNotEquals("not equal to null", segment, null); assertNotEquals("not equal to a totally different object", segment, "no way"); assertNotEquals("not equal to line segment with different start x", segment, new LineSegment3d(2, 2, 3, -3, -4, -5)); assertNotEquals("not equal to line segment with different start y", segment, new LineSegment3d(1, 3, 3, -3, -4, -5)); assertNotEquals("not equal to line segment with different start z", segment, new LineSegment3d(1, 2, 4, -3, -4, -5)); assertNotEquals("not equal to line segment with different end x", segment, new LineSegment3d(1, 2, 3, -4, -4, -5)); assertNotEquals("not equal to line segment with different end y", segment, new LineSegment3d(1, 2, 3, -3, -5, -5)); assertNotEquals("not equal to line segment with different end y", segment, new LineSegment3d(1, 2, 3, -3, -4, -6)); assertEquals("equal to another line segment with same start and end x, y, z", segment, new LineSegment3d(1, 2, 3, -3, -4, -5)); assertNotEquals("hashCode depends on start x", segment.hashCode(), new LineSegment3d(2, 2, 3, -3, -4, -5)); assertNotEquals("hashCode depends on start y", segment.hashCode(), new LineSegment3d(1, 3, 3, -3, -4, -5)); assertNotEquals("hashCode depends on start z", segment.hashCode(), new LineSegment3d(1, 3, 4, -3, -4, -5)); assertNotEquals("hashCode depends on end x", segment.hashCode(), new LineSegment3d(1, 2, 3, -4, -4, -5)); assertNotEquals("hashCode depends on end y", segment.hashCode(), new LineSegment3d(1, 2, 3, -4, -5, -5)); assertNotEquals("hashCode depends on end z", segment.hashCode(), new LineSegment3d(1, 2, 3, -4, -5, -6)); } }