View Javadoc
1   package org.djutils.draw.line;
2   
3   import static org.junit.Assert.assertEquals;
4   import static org.junit.Assert.assertFalse;
5   import static org.junit.Assert.assertNotEquals;
6   import static org.junit.Assert.assertTrue;
7   import static org.junit.Assert.fail;
8   
9   import java.util.Iterator;
10  
11  import org.djutils.draw.DrawRuntimeException;
12  import org.djutils.draw.bounds.Bounds3d;
13  import org.djutils.draw.point.Point3d;
14  import org.junit.Test;
15  
16  /**
17   * Segment3dTest.java.
18   * <p>
19   * Copyright (c) 2020-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
20   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
21   * </p>
22   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
23   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
24   */
25  public class LineSegment3dTest
26  {
27      /**
28       * Test the constructors.
29       */
30      @Test
31      public void constructorTest()
32      {
33          verifySegment("Segment from four coordinates", new LineSegment3d(1, 2, 3, 4, 5, 6), 1, 2, 3, 4, 5, 6);
34          verifySegment("Segment from two coordinates and a Point3d", new LineSegment3d(1, 2, 3, new Point3d(4, 5, 6)), 1, 2, 3,
35                  4, 5, 6);
36          verifySegment("Segment from a Point3d and two coordinates", new LineSegment3d(new Point3d(1, 2, 3), 4, 5, 6), 1, 2, 3,
37                  4, 5, 6);
38          verifySegment("Segment from two Point3d objects", new LineSegment3d(new Point3d(1, 2, 3), new Point3d(4, 5, 6)), 1, 2,
39                  3, 4, 5, 6);
40  
41          try
42          {
43              new LineSegment3d(1, 2, 3, 1, 2, 3);
44              fail("idential start and end should have thrown a DrawRuntimeException");
45          }
46          catch (DrawRuntimeException dre)
47          {
48              // Ignore expected exception
49          }
50  
51          new LineSegment3d(1, 2, 3, 1, 2, 4);
52          new LineSegment3d(1, 2, 3, 1, 3, 3);
53          new LineSegment3d(1, 2, 3, 2, 2, 3);
54  
55      }
56  
57      /**
58       * Check that a segment has all the right values.
59       * @param description String; description of the test
60       * @param segment Segment3d; the segment
61       * @param expectedStartX double; the expected x value for the start of the segment
62       * @param expectedStartY double; the expected y value for the start of the segment
63       * @param expectedStartZ double; the expected z value for the start of the segment
64       * @param expectedEndX double; the expected x value for the end of the segment
65       * @param expectedEndY double; the expected y value for the end of the segment
66       * @param expectedEndZ double; the expected y value for the end of the segment
67       */
68      public void verifySegment(final String description, final LineSegment3d segment, final double expectedStartX,
69              final double expectedStartY, final double expectedStartZ, final double expectedEndX, final double expectedEndY,
70              final double expectedEndZ)
71      {
72          assertEquals(description + " startX", expectedStartX, segment.startX, 0.0001);
73          assertEquals(description + " startY", expectedStartY, segment.startY, 0.0001);
74          assertEquals(description + " startZ", expectedStartZ, segment.startZ, 0.0001);
75          assertEquals(description + " toX", expectedEndX, segment.endX, 0.0001);
76          assertEquals(description + " endY", expectedEndY, segment.endY, 0.0001);
77          assertEquals(description + " endZ", expectedEndZ, segment.endZ, 0.0001);
78          assertEquals(description + " getStartPoint x", expectedStartX, segment.getStartPoint().x, 0.0001);
79          assertEquals(description + " getStartPoint y", expectedStartY, segment.getStartPoint().y, 0.0001);
80          assertEquals(description + " getStartPoint z", expectedStartZ, segment.getStartPoint().z, 0.0001);
81          assertEquals(description + " getEndPoint x", expectedEndX, segment.getEndPoint().x, 0.0001);
82          assertEquals(description + " getEndPoint y", expectedEndY, segment.getEndPoint().y, 0.0001);
83          assertEquals(description + " getEndPoint z", expectedEndZ, segment.getEndPoint().z, 0.0001);
84          assertEquals(description + " length", Math
85                  .hypot(Math.hypot(expectedEndX - expectedStartX, expectedEndY - expectedStartY), expectedEndZ - expectedStartZ),
86                  segment.getLength(), 0.0001);
87          assertEquals(description + " size is 2", 2, segment.size());
88          Iterator<? extends Point3d> iterator = segment.getPoints();
89          assertTrue(description + " iterator has data", iterator.hasNext());
90          Point3d point = iterator.next();
91          assertEquals(description + " iterator first point x", expectedStartX, point.x, 0.0001);
92          assertEquals(description + " iterator first point y", expectedStartY, point.y, 0.0001);
93          assertEquals(description + " iterator first point z", expectedStartZ, point.z, 0.0001);
94          assertTrue(description + " iterator has more data", iterator.hasNext());
95          point = iterator.next();
96          assertEquals(description + " iterator second point x", expectedEndX, point.x, 0.0001);
97          assertEquals(description + " iterator second point y", expectedEndY, point.y, 0.0001);
98          assertEquals(description + " iterator second point z", expectedEndZ, point.z, 0.0001);
99          assertFalse(description + " iterator has no more data", iterator.hasNext());
100         Bounds3d bounds = segment.getBounds();
101         assertEquals(description + " bounds minX", Math.min(expectedStartX, expectedEndX), bounds.getMinX(), 0.0001);
102         assertEquals(description + " bounds maxX", Math.max(expectedStartX, expectedEndX), bounds.getMaxX(), 0.0001);
103         assertEquals(description + " bounds minY", Math.min(expectedStartY, expectedEndY), bounds.getMinY(), 0.0001);
104         assertEquals(description + " bounds maxY", Math.max(expectedStartY, expectedEndY), bounds.getMaxY(), 0.0001);
105         assertEquals(description + " bounds minZ", Math.min(expectedStartZ, expectedEndZ), bounds.getMinZ(), 0.0001);
106         assertEquals(description + " bounds maxZ", Math.max(expectedStartZ, expectedEndZ), bounds.getMaxZ(), 0.0001);
107         assertTrue(description + " toString returns something descriptive", segment.toString().startsWith("LineSegment3d "));
108         assertTrue(description + " toString can suppress the class name",
109                 segment.toString().indexOf(segment.toString(true)) > 0);
110     }
111 
112     /**
113      * Test the getLocation methods.
114      */
115     @Test
116     public void locationTest()
117     {
118         Point3d startPoint = new Point3d(3, 4, 5);
119         Point3d endPoint = new Point3d(9, 20, 15);
120         LineSegment3d segment = new LineSegment3d(startPoint, endPoint);
121         try
122         {
123             segment.getLocation(Double.NaN);
124             fail("NaN position should have thrown a DrawRuntimeException");
125         }
126         catch (DrawRuntimeException dre)
127         {
128             // Ignore expected exception
129         }
130 
131         try
132         {
133             segment.getLocationExtended(Double.POSITIVE_INFINITY);
134             fail("Infinity position should have thrown a DrawRuntimeException");
135         }
136         catch (DrawRuntimeException dre)
137         {
138             // Ignore expected exception
139         }
140 
141         try
142         {
143             segment.getLocationExtended(Double.NEGATIVE_INFINITY);
144             fail("Infinity position should have thrown a DrawRuntimeException");
145         }
146         catch (DrawRuntimeException dre)
147         {
148             // Ignore expected exception
149         }
150 
151         for (double position : new double[] {-3, -0.5, 0, 1, 10, 100})
152         {
153             if (position < 0 || position > segment.getLength())
154             {
155                 try
156                 {
157                     segment.getLocation(position);
158                     fail("position out of bounds should have thrown a DrawRuntimeException");
159                 }
160                 catch (DrawRuntimeException dre)
161                 {
162                     // Ignore expected exception
163                 }
164             }
165             else
166             {
167                 Ray3d ray = segment.getLocation(position);
168                 assertEquals("distance from start point", position, ray.distance(startPoint), 0.0001);
169                 assertEquals("distance from end point", segment.getLength() - position, ray.distance(endPoint), 0.0001);
170                 assertEquals("direction of ray phi", startPoint.project().directionTo(endPoint.project()), ray.phi, 0.0001);
171                 assertEquals("direction of ray theta", Math.atan2(endPoint.z - startPoint.z, segment.project().getLength()),
172                         ray.theta, 0.0001);
173             }
174             Ray3d ray = segment.getLocationExtended(position);
175             assertEquals("distance from start point", Math.abs(position), ray.distance(startPoint), 0.0001);
176             assertEquals("distance from end point", Math.abs(segment.getLength() - position), ray.distance(endPoint), 0.0001);
177             assertEquals("direction of ray phi", startPoint.project().directionTo(endPoint.project()), ray.phi, 0.0001);
178             assertEquals("direction of ray theta", Math.atan2(endPoint.z - startPoint.z, segment.project().getLength()),
179                     ray.theta, 0.0001);
180         }
181     }
182 
183     /**
184      * Test the closestPointOnSegment method.
185      */
186     @Test
187     public void closestPointOnSegmentTest()
188     {
189         LineSegment3d segment = new LineSegment3d(1, 2, 3, 20, 10, 15);
190         try
191         {
192             segment.closestPointOnSegment(null);
193             fail("Null for point should have thrown a NullPointerException");
194         }
195         catch (NullPointerException npe)
196         {
197             // Ignore expected exception
198         }
199 
200         Point3d result = segment.closestPointOnSegment(new Point3d(1, 2, 0));
201         assertEquals("result is start point x", segment.startX, result.x, 0);
202         assertEquals("result is start point y", segment.startY, result.y, 0);
203         assertEquals("result is start point z", segment.startZ, result.z, 0);
204         result = segment.closestPointOnSegment(new Point3d(1, 0, 3));
205         assertEquals("result is start point x", segment.startX, result.x, 0);
206         assertEquals("result is start point y", segment.startY, result.y, 0);
207         assertEquals("result is start point z", segment.startZ, result.z, 0);
208         result = segment.closestPointOnSegment(new Point3d(0, 2, 3));
209         assertEquals("result is start point x", segment.startX, result.x, 0);
210         assertEquals("result is start point y", segment.startY, result.y, 0);
211         assertEquals("result is start point z", segment.startZ, result.z, 0);
212         result = segment.closestPointOnSegment(new Point3d(1, 2, 3));
213         assertEquals("result is start point x", segment.startX, result.x, 0);
214         assertEquals("result is start point y", segment.startY, result.y, 0);
215         assertEquals("result is start point z", segment.startZ, result.z, 0);
216 
217         Point3d projectingPoint = new Point3d(10, 10, 10);
218         result = segment.closestPointOnSegment(projectingPoint); // Projects at a point along the segment
219         double distanceFromStart = result.distance(segment.getStartPoint());
220         assertTrue("distance from start is > 0", distanceFromStart > 0);
221         double distanceToEnd = result.distance(segment.getEndPoint());
222         // System.out.println(segment + " projectingPoint=" + projectingPoint + ", result=" + result);
223         assertTrue("distance to end point is > 0", distanceToEnd > 0);
224         assertEquals("sum of distances is length of segment", segment.getLength(), distanceFromStart + distanceToEnd, 0.0001);
225 
226         Point3d doubleProjected = segment.closestPointOnSegment(result);
227         assertEquals("projecting the projection yields the projection", 0, doubleProjected.distance(result), 0.0001);
228 
229         result = segment.closestPointOnSegment(new Point3d(21, 10, 15));
230         assertEquals("result is end point", segment.endX, result.x, 0);
231         assertEquals("result is end point", segment.endY, result.y, 0);
232         result = segment.closestPointOnSegment(new Point3d(20, 11, 15));
233         assertEquals("result is end point", segment.endX, result.x, 0);
234         assertEquals("result is end point", segment.endY, result.y, 0);
235         result = segment.closestPointOnSegment(new Point3d(20, 10, 16));
236         assertEquals("result is end point", segment.endX, result.x, 0);
237         assertEquals("result is end point", segment.endY, result.y, 0);
238         result = segment.closestPointOnSegment(new Point3d(20, 10, 15));
239         assertEquals("result is end point", segment.endX, result.x, 0);
240         assertEquals("result is end point", segment.endY, result.y, 0);
241     }
242 
243     /**
244      * Test the project methods.
245      */
246     @Test
247     public void testProject()
248     {
249         LineSegment3d segment = new LineSegment3d(1, 2, 3, 20, 10, 5);
250         assertTrue("projects outside", Double.isNaN(segment.projectOrthogonalFractional(new Point3d(1, 1, 1))));
251         assertTrue("projects before start", segment.projectOrthogonalFractionalExtended(new Point3d(1, 1, 1)) < 0);
252         assertEquals("projects at -2", -2,
253                 segment.projectOrthogonalFractionalExtended(new Point3d(1 - 19 - 19 + 8, 2 - 8 - 8 - 19, 3 - 2 - 2)), 0.0001);
254         assertEquals("point near half way (not on segment) project at about half way", 0.5,
255                 segment.projectOrthogonalFractional(new Point3d(11, 1, 4)), 0.1);
256         assertTrue("projects outside", Double.isNaN(segment.projectOrthogonalFractional(new Point3d(25, 15, 6))));
257         assertTrue("projects after end", segment.projectOrthogonalFractionalExtended(new Point3d(25, 15, 6)) > 1);
258         assertEquals("projects at 2", 2,
259                 segment.projectOrthogonalFractionalExtended(new Point3d(1 + 19 + 19 - 8, 2 + 8 + 8 + 19, 3 + 2 + 2)), 0.0001);
260     }
261 
262     /**
263      * Test the toExcel method.
264      * @throws NumberFormatException if that happens, this test has failed
265      */
266     @Test
267     public void testToExcel() throws NumberFormatException
268     {
269         LineSegment3d segment = new LineSegment3d(1, 2, 3, 20, 10, 5);
270         String result = segment.toExcel();
271         String[] lines = result.split("\n");
272         assertEquals("result is two lines", 2, lines.length);
273         for (int lineNo = 0; lineNo < lines.length; lineNo++)
274         {
275             String[] fields = lines[lineNo].trim().split("\t");
276             assertEquals("Line consists of three fields", 3, fields.length);
277             for (int fieldNo = 0; fieldNo < fields.length; fieldNo++)
278             {
279                 double value = Double.parseDouble(fields[fieldNo]);
280                 double expectedValue =
281                         lineNo == 0 ? (fieldNo == 0 ? segment.startX : fieldNo == 1 ? segment.startY : segment.startZ)
282                                 : (fieldNo == 0 ? segment.endX : fieldNo == 1 ? segment.endY : segment.endZ);
283                 assertEquals("field contains the correct value", expectedValue, value, 0.0001);
284             }
285         }
286     }
287 
288     /**
289      * Test the equals and hasCode methods.
290      */
291     @Test
292     public void equalsAndHashCodeTest()
293     {
294         LineSegment3d segment = new LineSegment3d(1, 2, 3, -3, -4, -5);
295         assertEquals("equal to itself", segment, segment);
296         assertNotEquals("not equal to null", segment, null);
297         assertNotEquals("not equal to a totally different object", segment, "no way");
298         assertNotEquals("not equal to line segment with different start x", segment, new LineSegment3d(2, 2, 3, -3, -4, -5));
299         assertNotEquals("not equal to line segment with different start y", segment, new LineSegment3d(1, 3, 3, -3, -4, -5));
300         assertNotEquals("not equal to line segment with different start z", segment, new LineSegment3d(1, 2, 4, -3, -4, -5));
301         assertNotEquals("not equal to line segment with different end x", segment, new LineSegment3d(1, 2, 3, -4, -4, -5));
302         assertNotEquals("not equal to line segment with different end y", segment, new LineSegment3d(1, 2, 3, -3, -5, -5));
303         assertNotEquals("not equal to line segment with different end y", segment, new LineSegment3d(1, 2, 3, -3, -4, -6));
304         assertEquals("equal to another line segment with same start and end x, y, z", segment,
305                 new LineSegment3d(1, 2, 3, -3, -4, -5));
306 
307         assertNotEquals("hashCode depends on start x", segment.hashCode(), new LineSegment3d(2, 2, 3, -3, -4, -5));
308         assertNotEquals("hashCode depends on start y", segment.hashCode(), new LineSegment3d(1, 3, 3, -3, -4, -5));
309         assertNotEquals("hashCode depends on start z", segment.hashCode(), new LineSegment3d(1, 3, 4, -3, -4, -5));
310         assertNotEquals("hashCode depends on end x", segment.hashCode(), new LineSegment3d(1, 2, 3, -4, -4, -5));
311         assertNotEquals("hashCode depends on end y", segment.hashCode(), new LineSegment3d(1, 2, 3, -4, -5, -5));
312         assertNotEquals("hashCode depends on end z", segment.hashCode(), new LineSegment3d(1, 2, 3, -4, -5, -6));
313     }
314 
315 }