View Javadoc
1   package org.djutils.draw.line;
2   
3   import static org.junit.Assert.assertEquals;
4   import static org.junit.Assert.assertTrue;
5   import static org.junit.Assert.fail;
6   
7   import org.djutils.draw.DrawRuntimeException;
8   import org.djutils.draw.bounds.Bounds3d;
9   import org.djutils.draw.point.Point3d;
10  import org.junit.Test;
11  
12  /**
13   * Ray3dTest.java.
14   * <p>
15   * Copyright (c) 2021-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
16   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
17   * </p>
18   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
19   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
20   */
21  public class Ray3dTest
22  {
23      /**
24       * Test the various constructors of a Ray3d.
25       */
26      @Test
27      public void testConstructors()
28      {
29          verifyRay("Constructor from x, y, phi", new Ray3d(1, 2, 3, 4, 5), 1, 2, 3, 4, 5);
30          verifyRay("Constructor from Point3d, phi", new Ray3d(new Point3d(0.1, 0.2, 0.3), -0.4, -0.5), 0.1, 0.2, 0.3, -0.4,
31                  -0.5);
32          verifyRay("Constructor from x, y, z, throughX, throughY, throughZ", new Ray3d(1, 2, 3, 4, 6, 15), 1, 2, 3,
33                  Math.atan2(4, 3), Math.atan2(12, 5));
34          verifyRay("Constructor from x, y, z, throughX, throughY, throughZ", new Ray3d(1, 2, 3, 1, 6, 15), 1, 2, 3,
35                  Math.atan2(4, 0), Math.atan2(12, 4));
36          verifyRay("Constructor from x, y, z, throughX, throughY, throughZ", new Ray3d(1, 2, 3, 1, 2, 15), 1, 2, 3,
37                  Math.atan2(0, 0), Math.atan2(12, 0));
38          verifyRay("Constructor from Point3d, throughX, throughY, throughZ", new Ray3d(new Point3d(1, 2, 3), 4, 6, 15), 1, 2, 3,
39                  Math.atan2(4, 3), Math.atan2(12, 5));
40          verifyRay("Constructor from Point3d, throughX, throughY, throughZ", new Ray3d(new Point3d(1, 2, 3), 1, 6, 15), 1, 2, 3,
41                  Math.atan2(4, 0), Math.atan2(12, 4));
42          verifyRay("Constructor from Point3d, throughX, throughY, throughZ", new Ray3d(new Point3d(1, 2, 3), 1, 2, 15), 1, 2, 3,
43                  Math.atan2(0, 0), Math.atan2(12, 0));
44          verifyRay("Constructor from x, y, z, Point3d", new Ray3d(1, 2, 3, new Point3d(4, 6, 15)), 1, 2, 3, Math.atan2(4, 3),
45                  Math.atan2(12, 5));
46          verifyRay("Constructor from x, y, z, Point3d", new Ray3d(1, 2, 3, new Point3d(1, 6, 15)), 1, 2, 3, Math.atan2(4, 0),
47                  Math.atan2(12, 4));
48          verifyRay("Constructor from x, y, z, Point3d", new Ray3d(1, 2, 3, new Point3d(1, 2, 15)), 1, 2, 3, Math.atan2(0, 0),
49                  Math.atan2(12, 0));
50          verifyRay("Constructor from Point3d, Point3d", new Ray3d(new Point3d(1, 2, 3), new Point3d(4, 6, 15)), 1, 2, 3,
51                  Math.atan2(4, 3), Math.atan2(12, 5));
52          verifyRay("Constructor from Point3d, Point3d", new Ray3d(new Point3d(1, 2, 3), new Point3d(1, 6, 15)), 1, 2, 3,
53                  Math.atan2(4, 0), Math.atan2(12, 4));
54          verifyRay("Constructor from Point3d, Point3d", new Ray3d(new Point3d(1, 2, 3), new Point3d(1, 2, 15)), 1, 2, 3,
55                  Math.atan2(0, 0), Math.atan2(12, 0));
56  
57          try
58          {
59              new Ray3d(1, 2, 3, Double.NaN, 0);
60              fail("NaN for phy should have thrown a DrawRuntimeException");
61          }
62          catch (DrawRuntimeException dre)
63          {
64              // Ignore expected exception
65          }
66  
67          try
68          {
69              new Ray3d(1, 2, 3, 0, Double.NaN);
70              fail("NaN for theta should have thrown a DrawRuntimeException");
71          }
72          catch (DrawRuntimeException dre)
73          {
74              // Ignore expected exception
75          }
76  
77          try
78          {
79              new Ray3d(null, 1, 2);
80              fail("null for point should have thrown a NullPointerException");
81          }
82          catch (NullPointerException dre)
83          {
84              // Ignore expected exception
85          }
86  
87          try
88          {
89              new Ray3d(1, 2, 3, 1, 2, 3);
90              fail("Same coordinates for through point should have thrown a DrawRuntimeException");
91          }
92          catch (DrawRuntimeException dre)
93          {
94              // Ignore expected exception
95          }
96  
97          try
98          {
99              new Ray3d(1, 2, 3, new Point3d(1, 2, 3));
100             fail("Same coordinates for through point should have thrown a DrawRuntimeException");
101         }
102         catch (DrawRuntimeException dre)
103         {
104             // Ignore expected exception
105         }
106 
107         try
108         {
109             new Ray3d(new Point3d(1, 2, 3), 1, 2, 3);
110             fail("Same coordinates for through point should have thrown a DrawRuntimeException");
111         }
112         catch (DrawRuntimeException dre)
113         {
114             // Ignore expected exception
115         }
116 
117         try
118         {
119             new Ray3d(1, 2, 3, null);
120             fail("null for through point should have thrown a NullPointerException");
121         }
122         catch (NullPointerException dre)
123         {
124             // Ignore expected exception
125         }
126 
127         try
128         {
129             new Ray3d(null, new Point3d(4, 5, 6));
130             fail("null for point should have thrown a NullPointerException");
131         }
132         catch (NullPointerException dre)
133         {
134             // Ignore expected exception
135         }
136 
137         try
138         {
139             new Ray3d(new Point3d(1, 2, 3), null);
140             fail("null for through point should have thrown a NullPointerException");
141         }
142         catch (NullPointerException dre)
143         {
144             // Ignore expected exception
145         }
146 
147         assertTrue("toString returns something descriptive", new Ray3d(1, 2, 3, 0.2, 0.3).toString().startsWith("Ray3d"));
148     }
149 
150     /**
151      * Verify all fields of a Ray3d with a tolerance of 0.0001.
152      * @param description String; description of the test
153      * @param ray Ray3d; the Ray3d
154      * @param expectedX double; the expected x value
155      * @param expectedY double; the expected y value
156      * @param expectedZ double; the expected z value
157      * @param expectedPhi double; the expected phi value
158      * @param expectedTheta double; the expected theta value
159      */
160     private void verifyRay(final String description, final Ray3d ray, final double expectedX, final double expectedY,
161             final double expectedZ, final double expectedPhi, final double expectedTheta)
162     {
163         assertEquals(description + " getX", expectedX, ray.getX(), 0.0001);
164         assertEquals(description + " x", expectedX, ray.x, 0.0001);
165         assertEquals(description + " getY", expectedY, ray.getY(), 0.0001);
166         assertEquals(description + " y", expectedY, ray.y, 0.0001);
167         assertEquals(description + " getZ", expectedZ, ray.getZ(), 0.0001);
168         assertEquals(description + " z", expectedZ, ray.z, 0.0001);
169         assertEquals(description + " getPhi", expectedPhi, ray.getPhi(), 0.0001);
170         assertEquals(description + " phi", expectedPhi, ray.phi, 0.0001);
171         assertEquals(description + " getTheta", expectedTheta, ray.getTheta(), 0.0001);
172         assertEquals(description + " theta", expectedTheta, ray.theta, 0.0001);
173         Point3d startPoint = ray.getEndPoint();
174         assertEquals(description + " getStartPoint x", expectedX, startPoint.x, 0.0001);
175         assertEquals(description + " getStartPoint y", expectedY, startPoint.y, 0.0001);
176         assertEquals(description + " getStartPoint z", expectedZ, startPoint.z, 0.0001);
177         Ray3d negated = ray.neg();
178         assertEquals(description + " neg x", -expectedX, negated.x, 0.0001);
179         assertEquals(description + " neg y", -expectedY, negated.y, 0.0001);
180         assertEquals(description + " neg z", -expectedZ, negated.z, 0.0001);
181         assertEquals(description + " neg phi", expectedPhi + Math.PI, negated.phi, 0.0001);
182         assertEquals(description + " neg theta", expectedTheta + Math.PI, negated.theta, 0.0001);
183     }
184 
185     /**
186      * Test the result of the getBounds method.
187      */
188     @Test
189     public void boundsTest()
190     {
191         // X direction
192         Bounds3d b = new Ray3d(1, 2, 3, 0, 0).getBounds();
193         // Angle of 0 is exact; bounds should be infinite in only the positive X direction
194         assertEquals("Bounds minX", 1, b.getMinX(), 0);
195         assertEquals("Bounds.minY", 2, b.getMinY(), 0);
196         assertEquals("Bounds minZ", 3, b.getMinZ(), 0);
197         assertEquals("Bounds.maxX", Double.POSITIVE_INFINITY, b.getMaxX(), 0);
198         assertEquals("Bounds.maxY", 2, b.getMaxY(), 0);
199         assertEquals("Bounds.maxZ", 3, b.getMinZ(), 0);
200 
201         // first quadrant in XY, pointing up (positive Z)
202         b = new Ray3d(1, 2, 3, 0.2, 1.1).getBounds();
203         assertEquals("Bounds minX", 1, b.getMinX(), 0);
204         assertEquals("Bounds.minY", 2, b.getMinY(), 0);
205         assertEquals("Bounds.minZ", 3, b.getMinZ(), 0);
206         assertEquals("Bounds.maxX", Double.POSITIVE_INFINITY, b.getMaxX(), 0);
207         assertEquals("Bounds.maxY", Double.POSITIVE_INFINITY, b.getMaxY(), 0);
208         assertEquals("Bounds.maxZ", Double.POSITIVE_INFINITY, b.getMaxZ(), 0);
209 
210         // Math.PI / 2 is in first quadrant due to finite precision of a double
211         b = new Ray3d(1, 2, 3, Math.PI / 2, 0).getBounds();
212         assertEquals("Bounds minX", 1, b.getMinX(), 0);
213         assertEquals("Bounds.minY", 2, b.getMinY(), 0);
214         assertEquals("Bounds minZ", 3, b.getMinZ(), 0);
215         assertEquals("Bounds.maxX", Double.POSITIVE_INFINITY, b.getMaxX(), 0);
216         assertEquals("Bounds.maxY", Double.POSITIVE_INFINITY, b.getMaxY(), 0);
217         assertEquals("Bounds.maxZ", 3, b.getMinZ(), 0);
218 
219         // second quadrant in XY, pointing down (negative Z)
220         b = new Ray3d(1, 2, 3, 2, -1).getBounds();
221         assertEquals("Bounds minX", Double.NEGATIVE_INFINITY, b.getMinX(), 0);
222         assertEquals("Bounds.minY", 2, b.getMinY(), 0);
223         assertEquals("Bounds.minZ", Double.NEGATIVE_INFINITY, b.getMinZ(), 0);
224         assertEquals("Bounds.maxX", 1, b.getMaxX(), 0);
225         assertEquals("Bounds.maxY", Double.POSITIVE_INFINITY, b.getMaxY(), 0);
226         assertEquals("Bounds.maxZ", 3, b.getMaxZ(), 0);
227 
228         // Math.PI is in second quadrant due to finite precision of a double
229         b = new Ray3d(1, 2, 3, Math.PI, 0).getBounds();
230         assertEquals("Bounds minX", Double.NEGATIVE_INFINITY, b.getMinX(), 0);
231         assertEquals("Bounds.minY", 2, b.getMinY(), 0);
232         assertEquals("Bounds.maxX", 1, b.getMaxX(), 0);
233         assertEquals("Bounds.maxY", Double.POSITIVE_INFINITY, b.getMaxY(), 0);
234 
235         // third quadrant
236         b = new Ray3d(1, 2, 3, 4, 0).getBounds();
237         assertEquals("Bounds minX", Double.NEGATIVE_INFINITY, b.getMinX(), 0);
238         assertEquals("Bounds.minY", Double.NEGATIVE_INFINITY, b.getMinY(), 0);
239         assertEquals("Bounds.maxX", 1, b.getMaxX(), 0);
240         assertEquals("Bounds.maxY", 2, b.getMaxY(), 0);
241 
242         // fourth quadrant
243         b = new Ray3d(1, 2, 3, -1, 0).getBounds();
244         assertEquals("Bounds minX", 1, b.getMinX(), 0);
245         assertEquals("Bounds.minY", Double.NEGATIVE_INFINITY, b.getMinY(), 0);
246         assertEquals("Bounds.maxX", Double.POSITIVE_INFINITY, b.getMaxX(), 0);
247         assertEquals("Bounds.maxY", 2, b.getMaxY(), 0);
248 
249         // -Math.PI / 2 is in fourth quadrant due to finite precision of a double
250         b = new Ray3d(1, 2, 3, -Math.PI / 2, 0).getBounds();
251         assertEquals("Bounds minX", 1, b.getMinX(), 0);
252         assertEquals("Bounds.minY", Double.NEGATIVE_INFINITY, b.getMinY(), 0);
253         assertEquals("Bounds.maxX", Double.POSITIVE_INFINITY, b.getMaxX(), 0);
254         assertEquals("Bounds.maxY", 2, b.getMaxY(), 0);
255 
256         // TODO theta values at boundaries and outside of [0..PI/2]
257     }
258 
259     /**
260      * Test the getLocation and getLocationExtended methods.
261      */
262     @Test
263     public void testLocation()
264     {
265         try
266         {
267             new Ray3d(1, 2, 3, 1, 0.5).getLocation(Double.NaN);
268             fail("NaN position should have thrown a DrawRuntimeException");
269         }
270         catch (DrawRuntimeException dre)
271         {
272             // Ignore expected exception
273         }
274 
275         try
276         {
277             new Ray3d(1, 2, 3, 1, 0.5).getLocation(-1);
278             fail("Negative position should have thrown a DrawRuntimeException");
279         }
280         catch (DrawRuntimeException dre)
281         {
282             // Ignore expected exception
283         }
284 
285         try
286         {
287             new Ray3d(1, 2, 3, 1, 0.5).getLocation(Double.POSITIVE_INFINITY);
288             fail("Infited position should have thrown a DrawRuntimeException");
289         }
290         catch (DrawRuntimeException dre)
291         {
292             // Ignore expected exception
293         }
294 
295         try
296         {
297             new Ray3d(1, 2, 3, 1, 0.5).getLocation(Double.NEGATIVE_INFINITY);
298             fail("Infinte position should have thrown a DrawRuntimeException");
299         }
300         catch (DrawRuntimeException dre)
301         {
302             // Ignore expected exception
303         }
304 
305         for (double phi : new double[] {0, 1, 2, 3, 4, 5, -1, -2, Math.PI})
306         {
307             for (double theta : new double[] {0, 1, 2, 3, 4, 5, -1, -2, Math.PI})
308             {
309                 Ray3d ray = new Ray3d(1, 2, 3, phi, theta);
310                 for (double position : new double[] {0, 10, 0.1, -2})
311                 {
312                     Ray3d result = ray.getLocationExtended(position);
313                     assertEquals("result is position distance away from base of ray", Math.abs(position), ray.distance(result),
314                             0.001);
315                     assertEquals("result has same phi as ray", ray.phi, result.phi, 0.00001);
316                     assertTrue("Reverse position on result yields ray",
317                             ray.epsilonEquals(result.getLocationExtended(-position), 0.0001));
318                     if (position > 0)
319                     {
320                         // TODO verify that it is on positive side of ray
321                         assertEquals("result lies in on ray (phi)", ray.phi, result.phi, 0.0001);
322                         assertEquals("result lies on ray (theta)", ray.theta, result.theta, 0.0001);
323                     }
324                     if (position < 0)
325                     {
326                         assertEquals("ray lies on result (phi)", result.phi, ray.phi, 0.0001);
327                         assertEquals("ray lies on result (theta)", result.theta, ray.theta, 0.0001);
328                     }
329                 }
330             }
331         }
332     }
333 
334     /**
335      * Test the closestPointOnRay method.
336      */
337     @Test
338     public void testClosestPoint()
339     {
340         Ray3d ray = new Ray3d(1, 2, 3, 0.4, 0.5);
341         try
342         {
343             ray.closestPointOnLine(null);
344             fail("Null for point should have thrown a NullPointerException");
345         }
346         catch (NullPointerException npe)
347         {
348             // Ignore expected exception
349         }
350 
351         Point3d result = ray.closestPointOnRay(new Point3d(1, 2, 0));
352         assertEquals("result is start point", ray.x, result.x, 0);
353         assertEquals("result is start point", ray.y, result.y, 0);
354         assertEquals("result is start point", ray.z, result.z, 0);
355         result = ray.closestPointOnRay(new Point3d(1, 2, 0));
356         assertEquals("result is start point", ray.x, result.x, 0);
357         assertEquals("result is start point", ray.y, result.y, 0);
358         assertEquals("result is start point", ray.z, result.z, 0);
359         result = ray.closestPointOnRay(new Point3d(0, 2, 3));
360         assertEquals("result is start point", ray.x, result.x, 0);
361         assertEquals("result is start point", ray.y, result.y, 0);
362         assertEquals("result is start point", ray.z, result.z, 0);
363         result = ray.closestPointOnRay(new Point3d(1, 2, 3));
364         assertEquals("result is start point", ray.x, result.x, 0);
365         assertEquals("result is start point", ray.y, result.y, 0);
366         assertEquals("result is start point", ray.z, result.z, 0);
367 
368         Point3d projectingPoint = new Point3d(10, 10, 10);
369         result = ray.closestPointOnRay(projectingPoint); // Projects at a point along the ray
370         double distance = result.distance(ray.getEndPoint());
371         assertTrue("distance from start is > 0", distance > 0);
372         // Check that points on the ray slightly closer to start point or slightly further are indeed further from
373         // projectingPoint
374         assertTrue("Point on ray closer than result is further from projectingPoint",
375                 ray.getLocation(distance - 0.1).distance(projectingPoint) < distance);
376         assertTrue("Point on ray further than result is further from projectingPoint",
377                 ray.getLocation(distance + 0.1).distance(projectingPoint) < distance);
378     }
379 
380     /**
381      * Test the epsilonEquals method.
382      */
383     @Test
384     public void epsilonEqualsTest()
385     {
386         Ray3d ray = new Ray3d(1, 2, 3, 0.5, -0.5);
387         try
388         {
389             ray.epsilonEquals((Ray3d) null, 1, 1);
390             fail("Null pointer should have thrown a NullPointerException");
391         }
392         catch (NullPointerException npe)
393         {
394             // Ignore expected exception
395         }
396 
397         try
398         {
399             ray.epsilonEquals(ray, -0.1, 1);
400             fail("Negative epsilonCoordinate should have thrown an IllegalArgumentException");
401         }
402         catch (IllegalArgumentException npe)
403         {
404             // Ignore expected exception
405         }
406 
407         try
408         {
409             ray.epsilonEquals(ray, 1, -0.1);
410             fail("Negative epsilonDirection should have thrown an IllegalArgumentException");
411         }
412         catch (IllegalArgumentException npe)
413         {
414             // Ignore expected exception
415         }
416 
417         try
418         {
419             ray.epsilonEquals(ray, Double.NaN, 1);
420             fail("NaN epsilonCoordinate should have thrown an IllegalArgumentException");
421         }
422         catch (IllegalArgumentException npe)
423         {
424             // Ignore expected exception
425         }
426 
427         try
428         {
429             ray.epsilonEquals(ray, 1, Double.NaN);
430             fail("NaN epsilonDirection should have thrown an IllegalArgumentException");
431         }
432         catch (IllegalArgumentException npe)
433         {
434             // Ignore expected exception
435         }
436 
437         double[] deltas = new double[] {0.0, -0.125, 0.125, -1, 1}; // Use values that can be represented exactly in a double
438         for (double dX : deltas)
439         {
440             for (double dY : deltas)
441             {
442                 for (double dZ : deltas)
443                 {
444                     for (double dPhi : deltas)
445                     {
446                         for (double dTheta : deltas)
447                         {
448                             for (double epsilon : new double[] {0, 0.125, 0.5, 0.9, 1.0, 1.1})
449                             {
450                                 Ray3d other = new Ray3d(ray.x + dX, ray.y + dY, ray.z + dZ, ray.phi + dPhi, ray.theta + dTheta);
451                                 // System.out.println(String.format("dX=%f, dY=%f, dZ=%f, dPhi=%f, dTheta=%f, epsilon=%f", dX,
452                                 // dY,
453                                 // dZ, dPhi, dTheta, epsilon));
454                                 boolean result = ray.epsilonEquals(other, epsilon, Double.POSITIVE_INFINITY);
455                                 boolean expected =
456                                         Math.abs(dX) <= epsilon && Math.abs(dY) <= epsilon && Math.abs(dZ) <= epsilon;
457                                 assertEquals("result of epsilonEquals checking x, y, z", expected, result);
458 
459                                 result = ray.epsilonEquals(other, Double.POSITIVE_INFINITY, epsilon);
460                                 expected = Math.abs(dPhi) <= epsilon && Math.abs(dTheta) <= epsilon;
461                                 assertEquals("result of epsilonEquals checking phi and theta", expected, result);
462                             }
463                         }
464                     }
465                 }
466             }
467         }
468     }
469 
470 }