1   package org.djutils.draw.line;
2   
3   import static org.junit.jupiter.api.Assertions.assertEquals;
4   import static org.junit.jupiter.api.Assertions.assertFalse;
5   import static org.junit.jupiter.api.Assertions.assertNotEquals;
6   import static org.junit.jupiter.api.Assertions.assertNull;
7   import static org.junit.jupiter.api.Assertions.assertTrue;
8   import static org.junit.jupiter.api.Assertions.fail;
9   
10  import java.util.Iterator;
11  import java.util.NoSuchElementException;
12  
13  import org.djutils.draw.Direction3d;
14  import org.djutils.draw.bounds.Bounds3d;
15  import org.djutils.draw.point.OrientedPoint3d;
16  import org.djutils.draw.point.Point3d;
17  import org.djutils.math.AngleUtil;
18  import org.junit.jupiter.api.Test;
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  public class Ray3dTest
31  {
32      
33  
34  
35      @Test
36      public void testConstructors()
37      {
38          
39          verifyRay("positive x", new Ray3d(0, 0, 0, 1, 0, 0), 0, 0, 0, Math.PI / 2, 0);
40          verifyRay("positive y", new Ray3d(0, 0, 0, 0, 1, 0), 0, 0, 0, Math.PI / 2, Math.PI / 2);
41          verifyRay("positive z", new Ray3d(0, 0, 0, 0, 0, 1), 0, 0, 0, 0, 0);
42          verifyRay("negative x", new Ray3d(0, 0, 0, -1, 0, 0), 0, 0, 0, Math.PI / 2, Math.PI);
43          verifyRay("negative y", new Ray3d(0, 0, 0, 0, -1, 0), 0, 0, 0, Math.PI / 2, -Math.PI / 2);
44          verifyRay("negative z", new Ray3d(0, 0, 0, 0, 0, -1), 0, 0, 0, Math.PI, 0);
45          verifyRay("Constructor from x, y, z, dirY, dirZ", new Ray3d(1, 2, 3, 4, 5), 1, 2, 3, 4, 5);
46          verifyRay("Constructor from [x, y, z], dirY, dirZ", new Ray3d(new double[] {1, 2, 3}, 4, 5), 1, 2, 3, 4, 5);
47          Direction3d dir = new Direction3d(4, 5);
48          verifyRay("Constructor from [x, y, z], dir", new Ray3d(new double[] {1, 2, 3}, dir), 1, 2, 3, 4, 5);
49          verifyRay("Constructor from x, y, z, [dirY, dirZ]", new Ray3d(1, 2, 3, new double[] {4, 5}), 1, 2, 3, 4, 5);
50          verifyRay("Constructor from x, y, z, dir", new Ray3d(1, 2, 3, dir), 1, 2, 3, 4, 5);
51          verifyRay("Constructor from [x, y, z], [dirY, dirZ]", new Ray3d(new double[] {1, 2, 3}, new double[] {4, 5}), 1, 2, 3,
52                  4, 5);
53          verifyRay("Constructor from Point3d, dirY, dirZ", new Ray3d(new Point3d(0.1, 0.2, 0.3), -0.4, -0.5), 0.1, 0.2, 0.3,
54                  -0.4, -0.5);
55          dir = new Direction3d(-0.4, -0.5);
56          verifyRay("Constructor from Point3d, dir", new Ray3d(new Point3d(0.1, 0.2, 0.3), dir), 0.1, 0.2, 0.3, -0.4, -0.5);
57          verifyRay("Constructor from x, y, z, throughX, throughY, throughZ", new Ray3d(1, 2, 3, 4, 6, 15), 1, 2, 3,
58                  Math.atan2(5, 12), Math.atan2(4, 3));
59          verifyRay("Constructor from x, y, z, throughX, throughY, throughZ", new Ray3d(1, 2, 3, 1, 6, 15), 1, 2, 3,
60                  Math.atan2(4, 12), Math.PI / 2);
61          verifyRay("Constructor from x, y, z, throughX, throughY, throughZ", new Ray3d(1, 2, 3, 1, 2, 15), 1, 2, 3, 0,
62                  Math.atan2(0, 0));
63          verifyRay("Constructor from Point3d, throughX, throughY, throughZ", new Ray3d(new Point3d(1, 2, 3), 4, 6, 15), 1, 2, 3,
64                  Math.atan2(5, 12), Math.atan2(4, 3));
65          verifyRay("Constructor from Point3d, throughX, throughY, throughZ", new Ray3d(new Point3d(1, 2, 3), 1, 6, 15), 1, 2, 3,
66                  Math.atan2(4, 12), Math.PI / 2);
67          verifyRay("Constructor from Point3d, throughX, throughY, throughZ", new Ray3d(new Point3d(1, 2, 3), 1, 2, 15), 1, 2, 3,
68                  Math.atan2(0, 0), Math.atan2(0, 12));
69          verifyRay("Constructor from x, y, z, Point3d", new Ray3d(1, 2, 3, new Point3d(4, 6, 15)), 1, 2, 3, Math.atan2(5, 12),
70                  Math.atan2(4, 3));
71          verifyRay("Constructor from x, y, z, Point3d", new Ray3d(1, 2, 3, new Point3d(1, 6, 15)), 1, 2, 3, Math.atan2(4, 12),
72                  Math.PI / 2);
73          verifyRay("Constructor from x, y, z, Point3d", new Ray3d(1, 2, 3, new Point3d(1, 2, 15)), 1, 2, 3, Math.atan2(0, 12),
74                  Math.atan2(0, 0));
75          verifyRay("Constructor from Point3d, Point3d", new Ray3d(new Point3d(1, 2, 3), new Point3d(4, 6, 15)), 1, 2, 3,
76                  Math.atan2(5, 12), Math.atan2(4, 3));
77          verifyRay("Constructor from Point3d, Point3d", new Ray3d(new Point3d(1, 2, 3), new Point3d(1, 6, 15)), 1, 2, 3,
78                  Math.atan2(4, 12), Math.PI / 2);
79          verifyRay("Constructor from Point3d, Point3d", new Ray3d(new Point3d(1, 2, 3), new Point3d(1, 2, 15)), 1, 2, 3,
80                  Math.atan2(0, 0), Math.atan2(0, 12));
81  
82          try
83          {
84              new Ray3d(1, 2, 3, Double.NaN, 0);
85              fail("NaN for dirY should have thrown an ArithmeticException");
86          }
87          catch (ArithmeticException e)
88          {
89              
90          }
91  
92          try
93          {
94              new Ray3d(1, 2, 3, 0, Double.NaN);
95              fail("NaN for dirZ should have thrown a ArithmeticException");
96          }
97          catch (ArithmeticException e)
98          {
99              
100         }
101 
102         try
103         {
104             new Ray3d((Point3d) null, 1, 2);
105             fail("null for point should have thrown a NullPointerException");
106         }
107         catch (NullPointerException e)
108         {
109             
110         }
111 
112         try
113         {
114             new Ray3d(1, 2, 3, 1, 2, 3);
115             fail("Same coordinates for through point should have thrown a DrawRuntimeException");
116         }
117         catch (IllegalArgumentException e)
118         {
119             
120         }
121 
122         try
123         {
124             new Ray3d(1, 2, 3, new Point3d(1, 2, 3));
125             fail("Same coordinates for through point should have thrown a DrawRuntimeException");
126         }
127         catch (IllegalArgumentException e)
128         {
129             
130         }
131 
132         try
133         {
134             new Ray3d(new Point3d(1, 2, 3), 1, 2, 3);
135             fail("Same coordinates for through point should have thrown a DrawRuntimeException");
136         }
137         catch (IllegalArgumentException e)
138         {
139             
140         }
141 
142         try
143         {
144             new Ray3d(1, 2, 3, (Point3d) null);
145             fail("null for through point should have thrown a NullPointerException");
146         }
147         catch (NullPointerException e)
148         {
149             
150         }
151 
152         try
153         {
154             new Ray3d(null, new Point3d(4, 5, 6));
155             fail("null for point should have thrown a NullPointerException");
156         }
157         catch (NullPointerException e)
158         {
159             
160         }
161 
162         try
163         {
164             new Ray3d(new Point3d(1, 2, 3), (Point3d) null);
165             fail("null for through point should have thrown a NullPointerException");
166         }
167         catch (NullPointerException e)
168         {
169             
170         }
171         
172         try
173         {
174             new Ray3d(1, 2, 3, new double[] {0.5});
175             fail("too short directionVector should have thrown an IllegalArgumentException");
176         }
177         catch (IllegalArgumentException e)
178         {
179             
180         }
181 
182         Ray3d ray = new Ray3d(1, 2, 3, 0.2, 0.3);
183         assertTrue(ray.toString().startsWith("Ray3d"), "toString returns something descriptive");
184         assertTrue(ray.toString().indexOf(ray.toString(true)) > 0, "toString can suppress the class name");
185     }
186 
187     
188 
189 
190 
191 
192 
193 
194 
195 
196 
197     private void verifyRay(final String description, final Ray3d ray, final double expectedX, final double expectedY,
198             final double expectedZ, final double expectedDirY, final double expectedDirZ)
199     {
200         assertEquals(expectedX, ray.getX(), 0.0001, description + " getX");
201         assertEquals(expectedX, ray.x, 0.0001, description + " x");
202         assertEquals(expectedY, ray.getY(), 0.0001, description + " getY");
203         assertEquals(expectedY, ray.y, 0.0001, description + " y");
204         assertEquals(expectedZ, ray.getZ(), 0.0001, description + " getZ");
205         assertEquals(expectedZ, ray.z, 0.0001, description + " z");
206         assertEquals(expectedDirZ, ray.getDirZ(), 0.0001, description + " getDirZ");
207         assertEquals(expectedDirZ, ray.dirZ, 0.0001, description + " dirZ");
208         assertEquals(expectedDirY, ray.getDirY(), 0.0001, description + " getDirY");
209         assertEquals(expectedDirY, ray.dirY, 0.0001, description + " dirY");
210         Point3d startPoint = ray.getEndPoint();
211         assertEquals(expectedX, startPoint.x, 0.0001, description + " getStartPoint x");
212         assertEquals(expectedY, startPoint.y, 0.0001, description + " getStartPoint y");
213         assertEquals(expectedZ, startPoint.z, 0.0001, description + " getStartPoint z");
214         Ray3d negated = ray.neg();
215         assertEquals(-expectedX, negated.x, 0.0001, description + " neg x");
216         assertEquals(-expectedY, negated.y, 0.0001, description + " neg y");
217         assertEquals(-expectedZ, negated.z, 0.0001, description + " neg z");
218         assertEquals(AngleUtil.normalizeAroundZero(expectedDirZ + Math.PI), negated.dirZ, 0.0001, description + " neg dirZ");
219         assertEquals(AngleUtil.normalizeAroundZero(expectedDirY + Math.PI), negated.dirY, 0.0001, description + " neg dirY");
220         Ray3d flipped = ray.flip();
221         assertEquals(expectedX, flipped.getX(), 0.0001, description + " getX");
222         assertEquals(expectedX, flipped.x, 0.0001, description + " x");
223         assertEquals(expectedY, flipped.getY(), 0.0001, description + " getY");
224         assertEquals(expectedY, flipped.y, 0.0001, description + " y");
225         assertEquals(expectedZ, flipped.getZ(), 0.0001, description + " getZ");
226         assertEquals(expectedZ, flipped.z, 0.0001, description + " z");
227         assertEquals(AngleUtil.normalizeAroundZero(expectedDirZ + Math.PI), flipped.getDirZ(), 0.0001,
228                 description + " getdirZ");
229         assertEquals(AngleUtil.normalizeAroundZero(expectedDirZ + Math.PI), flipped.dirZ, 0.0001, description + " dirZ");
230         assertEquals(AngleUtil.normalizeAroundZero(Math.PI - expectedDirY), flipped.getDirY(), 0.0001,
231                 description + " getDirY");
232         assertEquals(AngleUtil.normalizeAroundZero(Math.PI - expectedDirY), flipped.dirY, 0.0001, description + " dirY");
233         assertEquals(2, ray.size(), description + " size");
234         Iterator<Point3d> iterator = ray.iterator();
235         
236         assertTrue(iterator.hasNext());
237         Point3d point = iterator.next();
238         assertEquals(expectedX, point.x, 0.0001, description + " iterator first point x");
239         assertEquals(expectedY, point.y, 0.0001, description + " iterator first point y");
240         assertEquals(expectedZ, point.z, 0.0001, description + " iterator first point z");
241         assertTrue(iterator.hasNext());
242         point = iterator.next();
243         
244         assertTrue(Double.isInfinite(point.x) || Double.isInfinite(point.y) || Double.isInfinite(point.z),
245                 description + " iterator second point is at infinity");
246         assertFalse(iterator.hasNext());
247         try
248         {
249             iterator.next();
250             fail("Should have thrown a NoSuchElementException");
251         }
252         catch (NoSuchElementException nsee)
253         {
254             
255         }
256     }
257 
258     
259 
260 
261     @Test
262     public void boundsTest()
263     {
264         
265         
266         verifyBounds(new Ray3d(1, 2, 3, 0, 1).getAbsoluteBounds(), 1, 2, 3, 1, 2, Double.POSITIVE_INFINITY);
267 
268         
269         
270         verifyBounds(new Ray3d(1, 2, 3, 0, 0).getAbsoluteBounds(), 1, 2, 3, 1, 2, Double.POSITIVE_INFINITY);
271 
272         
273         verifyBounds(new Ray3d(1, 2, 3, 1.1, 0.2).getAbsoluteBounds(), 1, 2, 3, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY,
274                 Double.POSITIVE_INFINITY);
275 
276         
277         verifyBounds(new Ray3d(1, 2, 3, 1, Math.PI / 2).getAbsoluteBounds(), 1, 2, 3, Double.POSITIVE_INFINITY,
278                 Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
279 
280         
281         verifyBounds(new Ray3d(1, 2, 3, 1, 2).getAbsoluteBounds(), Double.NEGATIVE_INFINITY, 2, 3, 1, Double.POSITIVE_INFINITY,
282                 Double.POSITIVE_INFINITY);
283 
284         
285         verifyBounds(new Ray3d(1, 2, 3, 1, Math.PI).getAbsoluteBounds(), Double.NEGATIVE_INFINITY, 2, 3, 1, Double.POSITIVE_INFINITY,
286                 Double.POSITIVE_INFINITY);
287 
288         
289         verifyBounds(new Ray3d(1, 2, 3, 1, 4).getAbsoluteBounds(), Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 3, 1, 2,
290                 Double.POSITIVE_INFINITY);
291 
292         
293         verifyBounds(new Ray3d(1, 2, 3, 1, -1).getAbsoluteBounds(), 1, Double.NEGATIVE_INFINITY, 3, Double.POSITIVE_INFINITY, 2,
294                 Double.POSITIVE_INFINITY);
295 
296         
297         verifyBounds(new Ray3d(1, 2, 3, 1, -Math.PI / 2).getAbsoluteBounds(), 1, Double.NEGATIVE_INFINITY, 3, Double.POSITIVE_INFINITY,
298                 2, Double.POSITIVE_INFINITY);
299 
300         
301         verifyBounds(new Ray3d(1, 2, 3, 3, 0.2).getAbsoluteBounds(), 1, 2, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
302                 Double.POSITIVE_INFINITY, 3);
303 
304         
305         verifyBounds(new Ray3d(1, 2, 3, 3, 2).getAbsoluteBounds(), Double.NEGATIVE_INFINITY, 2, Double.NEGATIVE_INFINITY, 1,
306                 Double.POSITIVE_INFINITY, 3);
307 
308         
309         verifyBounds(new Ray3d(1, 2, 3, 3, Math.PI).getAbsoluteBounds(), Double.NEGATIVE_INFINITY, 2, Double.NEGATIVE_INFINITY, 1,
310                 Double.POSITIVE_INFINITY, 3);
311 
312         
313         verifyBounds(new Ray3d(1, 2, 3, 3, 4).getAbsoluteBounds(), Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY,
314                 Double.NEGATIVE_INFINITY, 1, 2, 3);
315 
316         
317         verifyBounds(new Ray3d(1, 2, 3, 3, -1).getAbsoluteBounds(), 1, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY,
318                 Double.POSITIVE_INFINITY, 2, 3);
319 
320         
321         verifyBounds(new Ray3d(1, 2, 3, 3, -Math.PI / 2).getAbsoluteBounds(), 1, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY,
322                 Double.POSITIVE_INFINITY, 2, 3);
323 
324         
325         verifyBounds(new Ray3d(1, 2, 3, -1.1, 0.2).getAbsoluteBounds(), Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 3, 1, 2,
326                 Double.POSITIVE_INFINITY);
327 
328         
329         verifyBounds(new Ray3d(1, 2, 3, -1, Math.PI / 2).getAbsoluteBounds(), Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 3, 1,
330                 2, Double.POSITIVE_INFINITY);
331 
332         
333         verifyBounds(new Ray3d(1, 2, 3, -1, 2).getAbsoluteBounds(), 1, Double.NEGATIVE_INFINITY, 3, Double.POSITIVE_INFINITY, 2,
334                 Double.POSITIVE_INFINITY);
335 
336         
337         verifyBounds(new Ray3d(1, 2, 3, -1, Math.PI).getAbsoluteBounds(), 1, Double.NEGATIVE_INFINITY, 3, Double.POSITIVE_INFINITY, 2,
338                 Double.POSITIVE_INFINITY);
339 
340         
341         verifyBounds(new Ray3d(1, 2, 3, -1, 4).getAbsoluteBounds(), 1, 2, 3, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY,
342                 Double.POSITIVE_INFINITY);
343 
344         
345         verifyBounds(new Ray3d(1, 2, 3, -1, -1).getAbsoluteBounds(), Double.NEGATIVE_INFINITY, 2, 3, 1, Double.POSITIVE_INFINITY,
346                 Double.POSITIVE_INFINITY);
347 
348         
349     }
350 
351     
352 
353 
354 
355 
356 
357 
358 
359 
360 
361     private void verifyBounds(final Bounds3d bounds, final double expectedMinX, final double expectedMinY,
362             final double expectedMinZ, final double expectedMaxX, final double expectedMaxY, final double expectedMaxZ)
363     {
364         verifyBound(expectedMinX, bounds.getMinX(), "Bounds minX");
365         verifyBound(expectedMinY, bounds.getMinY(), "Bounds minY");
366         verifyBound(expectedMinZ, bounds.getMinZ(), "Bounds minZ");
367         verifyBound(expectedMaxX, bounds.getMaxX(), "Bounds maxX");
368         verifyBound(expectedMaxY, bounds.getMaxY(), "Bounds maxY");
369         verifyBound(expectedMaxZ, bounds.getMaxZ(), "Bounds maxZ");
370     }
371 
372     
373 
374 
375 
376 
377 
378     private void verifyBound(final double expected, final double got, final String name)
379     {
380         if (expected == Double.POSITIVE_INFINITY)
381         {
382             assertTrue(got == Double.POSITIVE_INFINITY, name);
383         }
384         else if (expected == Double.NEGATIVE_INFINITY)
385         {
386             assertTrue(got == Double.NEGATIVE_INFINITY, name);
387         }
388         else
389         {
390             assertEquals(expected, got, 0.0001, name);
391         }
392     }
393 
394     
395 
396 
397     @Test
398     public void testLocation()
399     {
400         try
401         {
402             new Ray3d(1, 2, 3, 1, 0.5).getLocation(Double.NaN);
403             fail("NaN position should have thrown an ArithmeticException");
404         }
405         catch (ArithmeticException e)
406         {
407             
408         }
409 
410         try
411         {
412             new Ray3d(1, 2, 3, 1, 0.5).getLocation(-1);
413             fail("Negative position should have thrown an IllegalArgumentException");
414         }
415         catch (IllegalArgumentException e)
416         {
417             
418         }
419 
420         try
421         {
422             new Ray3d(1, 2, 3, 1, 0.5).getLocation(Double.POSITIVE_INFINITY);
423             fail("Infite position should have thrown an IllegalArgumentException");
424         }
425         catch (IllegalArgumentException e)
426         {
427             
428         }
429 
430         try
431         {
432             new Ray3d(1, 2, 3, 1, 0.5).getLocation(Double.NEGATIVE_INFINITY);
433             fail("Infinte position should have thrown an IllegalArgumentException");
434         }
435         catch (IllegalArgumentException e)
436         {
437             
438         }
439 
440         try
441         {
442             new Ray3d(1, 2, 3, 1, 0.5).getLocationExtended(Double.POSITIVE_INFINITY);
443             fail("Infinite position should have thrown a IllegalArgumentException");
444         }
445         catch (IllegalArgumentException e)
446         {
447             
448         }
449 
450         try
451         {
452             new Ray3d(1, 2, 3, 1, 0.5).getLocationExtended(Double.NEGATIVE_INFINITY);
453             fail("Infinite position should have thrown a IllegalArgumentException");
454         }
455         catch (IllegalArgumentException e)
456         {
457             
458         }
459 
460         try
461         {
462             new Ray3d(1, 2, 3, 1, 0.5).getLocationExtended(Double.NaN);
463             fail("NaN position should have thrown an ArithmeticException");
464         }
465         catch (ArithmeticException e)
466         {
467             
468         }
469 
470         for (double dirZ : new double[] {0, 1, 2, 3, 4, 5, -1, -2, Math.PI})
471         {
472             for (double dirY : new double[] {0, 1, 2, 3, 4, 5, -1, -2, Math.PI})
473             {
474                 Ray3d ray = new Ray3d(1, 2, 3, dirZ, dirY);
475                 for (double position : new double[] {0, 10, 0.1, -2})
476                 {
477                     Ray3d result = ray.getLocationExtended(position);
478                     assertEquals(Math.abs(position), ray.distance(result), 0.001,
479                             "result is position distance away from base of ray");
480                     assertEquals(ray.dirZ, result.dirZ, 0.00001, "result has same dirZ as ray");
481                     assertTrue(ray.epsilonEquals(result.getLocationExtended(-position), 0.0001),
482                             "Reverse position on result yields ray");
483                     if (position > 0)
484                     {
485                         
486                         assertEquals(ray.dirY, result.dirY, 0.0001, "result lies on ray (dirY)");
487                         assertEquals(ray.dirZ, result.dirZ, 0.0001, "result lies in on ray (dirZ)");
488                     }
489                     if (position < 0)
490                     {
491                         assertEquals(result.dirY, ray.dirY, 0.0001, "ray lies on result (dirY)");
492                         assertEquals(result.dirZ, ray.dirZ, 0.0001, "ray lies on result (dirZ)");
493                     }
494                 }
495             }
496         }
497     }
498 
499     
500 
501 
502     @Test
503     public void testClosestPointAndProjectOrthogonal()
504     {
505         Ray3d ray = new Ray3d(1, 2, 3, 0.4, 0.5);
506         try
507         {
508             ray.closestPointOnRay(null);
509             fail("Null for point should have thrown a NullPointerException");
510         }
511         catch (NullPointerException npe)
512         {
513             
514         }
515 
516         Point3d result = ray.closestPointOnRay(new Point3d(1, 2, 0));
517         assertEquals(ray.x, result.x, 0, "result is start point");
518         assertEquals(ray.y, result.y, 0, "result is start point");
519         assertEquals(ray.z, result.z, 0, "result is start point");
520         result = ray.closestPointOnRay(new Point3d(1, 2, 0));
521         assertEquals(ray.x, result.x, 0, "result is start point");
522         assertEquals(ray.y, result.y, 0, "result is start point");
523         assertEquals(ray.z, result.z, 0, "result is start point");
524         result = ray.closestPointOnRay(new Point3d(0, 2, 3));
525         assertEquals(ray.x, result.x, 0, "result is start point");
526         assertEquals(ray.y, result.y, 0, "result is start point");
527         assertEquals(ray.z, result.z, 0, "result is start point");
528         result = ray.closestPointOnRay(new Point3d(1, 2, 3));
529         assertEquals(ray.x, result.x, 0, "result is start point");
530         assertEquals(ray.y, result.y, 0, "result is start point");
531         assertEquals(ray.z, result.z, 0, "result is start point");
532 
533         assertNull(ray.projectOrthogonal(new Point3d(1, 0, 3)), "projection misses the ray");
534         assertNull(ray.projectOrthogonal(new Point3d(0, 2, 3)), "projection misses the ray");
535         assertNull(ray.projectOrthogonal(new Point3d(1, 2, 2)), "projection misses the ray");
536         assertEquals(new Point3d(1, 2, 3), ray.projectOrthogonal(new Point3d(1, 2, 3)), "projection hits start point of ray");
537         assertEquals(0, new LineSegment3d(ray.getLocationExtended(-100), ray.getLocation(100))
538                 .closestPointOnSegment(new Point3d(1, 0, -1)).distance(ray.projectOrthogonalExtended(new Point3d(1, 0, -1))),
539                 0.0001, "extended projection returns same point as projection on sufficiently long line segment");
540 
541         Point3d projectingPoint = new Point3d(10, 10, 10);
542         result = ray.closestPointOnRay(projectingPoint); 
543         double distance = result.distance(ray.getEndPoint());
544         assertTrue(distance > 0, "distance from start is > 0");
545         
546         
547         assertTrue(ray.getLocation(distance - 0.1).distance(projectingPoint) < distance,
548                 "Point on ray closer than result is further from projectingPoint");
549         assertTrue(ray.getLocation(distance + 0.1).distance(projectingPoint) < distance,
550                 "Point on ray further than result is further from projectingPoint");
551         assertEquals(0, result.distance(ray.projectOrthogonalExtended(projectingPoint)), 0.0001,
552                 "projectOrthogonalExtended returns same result as long as orthogonal projection exists");
553     }
554 
555     
556 
557 
558     @Test
559     public void testProject()
560     {
561         Ray3d ray = new Ray3d(1, 2, 3, 20, 10, 5);
562         assertTrue(Double.isNaN(ray.projectOrthogonalFractional(new Point3d(1, 1, 1))), "projects outside");
563         assertTrue(ray.projectOrthogonalFractionalExtended(new Point3d(1, 1, 1)) < 0, "projects before start");
564         assertEquals(-new Point3d(1 - 19 - 19, 2 - 8 - 8, 3 - 2 - 2).distance(ray),
565                 ray.projectOrthogonalFractionalExtended(new Point3d(1 - 19 - 19 + 8, 2 - 8 - 8 - 19, 3 - 2 - 2)), 0.0001,
566                 "projects at");
567         
568         for (int x = -2; x < 5; x++)
569         {
570             for (int y = -2; y < 5; y++)
571             {
572                 for (int z = -2; z < 5; z++)
573                 {
574                     Point3d point = new Point3d(x, y, z);
575                     double fraction = ray.projectOrthogonalFractionalExtended(point);
576                     if (fraction < 0)
577                     {
578                         assertTrue(Double.isNaN(ray.projectOrthogonalFractional(point)), "non extended version yields NaN");
579                         assertNull(ray.projectOrthogonal(point), "non extended projectOrthogonal yields null");
580                     }
581                     else
582                     {
583                         assertEquals(fraction, ray.projectOrthogonalFractional(point), 0.00001,
584                                 "non extended version yields same");
585                         assertEquals(ray.projectOrthogonal(point), ray.projectOrthogonalExtended(point),
586                                 "non extended version yields same as extended version");
587                     }
588                     Point3d projected = ray.projectOrthogonalExtended(point);
589                     assertEquals(fraction, ray.projectOrthogonalFractionalExtended(projected), 0.00001,
590                             "projecting projected point yields same");
591                 }
592             }
593         }
594     }
595 
596     
597 
598 
599     @Test
600     public void epsilonEqualsTest()
601     {
602         Ray3d ray = new Ray3d(1, 2, 3, 0.5, -0.5);
603         try
604         {
605             ray.epsilonEquals((Ray3d) null, 1, 1);
606             fail("Null pointer should have thrown a NullPointerException");
607         }
608         catch (NullPointerException npe)
609         {
610             
611         }
612 
613         try
614         {
615             ray.epsilonEquals(ray, -0.1, 1);
616             fail("Negative epsilonCoordinate should have thrown an IllegalArgumentException");
617         }
618         catch (IllegalArgumentException npe)
619         {
620             
621         }
622 
623         try
624         {
625             ray.epsilonEquals(ray, 1, -0.1);
626             fail("Negative epsilonDirection should have thrown an IllegalArgumentException");
627         }
628         catch (IllegalArgumentException npe)
629         {
630             
631         }
632 
633         try
634         {
635             ray.epsilonEquals(ray, Double.NaN, 1);
636             fail("NaN epsilonCoordinate should have thrown an ArithmeticException");
637         }
638         catch (ArithmeticException ae)
639         {
640             
641         }
642 
643         try
644         {
645             ray.epsilonEquals(ray, 1, Double.NaN);
646             fail("NaN epsilonDirection should have thrown an ArithmeticException");
647         }
648         catch (ArithmeticException ae)
649         {
650             
651         }
652 
653         double[] deltas = new double[] {0.0, -0.125, 0.125, -1, 1}; 
654         for (double dX : deltas)
655         {
656             for (double dY : deltas)
657             {
658                 for (double dZ : deltas)
659                 {
660                     for (double dDirZ : deltas)
661                     {
662                         for (double dDirY : deltas)
663                         {
664                             for (double epsilon : new double[] {0, 0.125, 0.5, 0.9, 1.0, 1.1})
665                             {
666                                 Ray3d other = new Ray3d(ray.x + dX, ray.y + dY, ray.z + dZ, ray.dirY + dDirY, ray.dirZ + dDirZ);
667                                 boolean result = ray.epsilonEquals(other, epsilon, Double.POSITIVE_INFINITY);
668                                 boolean expected =
669                                         Math.abs(dX) <= epsilon && Math.abs(dY) <= epsilon && Math.abs(dZ) <= epsilon;
670                                 assertEquals(expected, result, "result of epsilonEquals checking x, y, z");
671 
672                                 result = ray.epsilonEquals(other, Double.POSITIVE_INFINITY, epsilon);
673                                 expected = Math.abs(dDirZ) <= epsilon && Math.abs(dDirY) <= epsilon;
674                                 assertEquals(expected, result, "result of epsilonEquals checking dirY and dirZ");
675                                 
676                                 other = new Ray3d(ray.x + dX, ray.y + dY, ray.z + dZ, Math.PI - ray.dirY + dDirY,
677                                         Math.PI + ray.dirZ + dDirZ);
678                                 result = ray.epsilonEquals(other, epsilon, Double.POSITIVE_INFINITY);
679                                 expected = Math.abs(dX) <= epsilon && Math.abs(dY) <= epsilon && Math.abs(dZ) <= epsilon;
680                                 assertEquals(expected, result, "result of epsilonEquals checking x, y, z");
681 
682                                 result = ray.epsilonEquals(other, Double.POSITIVE_INFINITY, epsilon);
683                                 expected = Math.abs(dDirZ) <= Double.POSITIVE_INFINITY && Math.abs(Math.PI + dDirY) <= epsilon;
684                                 if (result != expected)
685                                 {
686                                     System.out.println("Oops: compare " + ray + " to " + other + " using epsilonEquals("
687                                             + Double.POSITIVE_INFINITY + "," + epsilon + ")");
688                                     ray.epsilonEquals(other, Double.POSITIVE_INFINITY, epsilon);
689                                 }
690                                 assertEquals(expected, result, "result of epsilonEquals checking dirY and dirZ");
691                             }
692                         }
693                     }
694                 }
695             }
696         }
697     }
698 
699     
700 
701 
702     @Test
703     public void equalsAndHashCodeTest()
704     {
705         Ray3d ray = new Ray3d(1, 2, 3, 11, 12, 13);
706         assertEquals(ray, ray, "equal to itself");
707         assertNotEquals(ray, null, "not equal to null");
708         assertNotEquals(ray, new OrientedPoint3d(1, 2, 3), "not equal to different object with same parent class");
709         assertNotEquals(ray, new Ray3d(1, 2, 3, 11, 12, 10), "not equal to ray with different dirY");
710         assertNotEquals(ray, new Ray3d(1, 2, 3, 11, 10, 13), "not equal to ray with different dirZ");
711         assertNotEquals(ray, new Ray3d(2, 2, 3, 12, 12, 13), "not equal to ray with different start x");
712         assertNotEquals(ray, new Ray3d(1, 3, 3, 11, 13, 13), "not equal to ray with different start y");
713         assertEquals(ray, new Ray3d(1, 2, 3, 21, 22, 23), "equal to ray with same x, y and direction");
714         assertNotEquals(ray, "not a ray", "not equal to an different kind of object");
715 
716         assertNotEquals(ray.hashCode(), new Ray3d(2, 2, 3, 12, 12, 13), "hashCode depends on x");
717         assertNotEquals(ray.hashCode(), new Ray3d(1, 3, 3, 11, 13, 13), "hashCode depends on y");
718         assertNotEquals(ray.hashCode(), new Ray3d(1, 2, 4, 11, 12, 14), "hashCode depends on y");
719         assertNotEquals(ray.hashCode(), new Ray3d(1, 2, 3, 11, 12, 10), "hashCode depends on dirY");
720         assertNotEquals(ray.hashCode(), new Ray3d(1, 2, 3, 11, 10, 13), "hashCode depends on dirZ");
721     }
722 
723 }