View Javadoc
1   package org.djutils.draw.point;
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.assertNotNull;
7   import static org.junit.Assert.assertTrue;
8   import static org.junit.Assert.fail;
9   
10  import java.awt.geom.Point2D;
11  
12  import org.djutils.draw.DrawException;
13  import org.djutils.draw.DrawRuntimeException;
14  import org.djutils.draw.bounds.Bounds3d;
15  import org.djutils.draw.line.PolyLine3d;
16  import org.djutils.exceptions.Try;
17  import org.junit.Test;
18  
19  /**
20   * Point3dTest.java.
21   * <p>
22   * Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
23   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
24   * </p>
25   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
26   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
27   */
28  public class Point3dTest
29  {
30      /**
31       * Test the Point3d construction methods.
32       */
33      @SuppressWarnings("unlikely-arg-type")
34      @Test
35      public void testPoint3dConstruction()
36      {
37          Point3d p = new Point3d(10.0, -20.0, 16.0);
38          assertNotNull(p);
39          assertEquals(10.0, p.x, 0);
40          assertEquals(-20.0, p.y, 0);
41          assertEquals(16.0, p.z, 0);
42  
43          assertEquals("size method returns 1", 1, p.size());
44  
45          Point2d projection = p.project();
46          assertEquals(10.0, projection.x, 0);
47          assertEquals(-20.0, projection.y, 0);
48  
49          try
50          {
51              new Point3d(Double.NaN, 0, 0);
52              fail("NaN should have thrown an IllegalArgumentException");
53          }
54          catch (IllegalArgumentException iae)
55          {
56              // Ignore expected exception
57          }
58  
59          try
60          {
61              new Point3d(0, Double.NaN, 0);
62              fail("NaN should have thrown an IllegalArgumentException");
63          }
64          catch (IllegalArgumentException iae)
65          {
66              // Ignore expected exception
67          }
68  
69          try
70          {
71              new Point3d(0, 0, Double.NaN);
72              fail("NaN should have thrown an IllegalArgumentException");
73          }
74          catch (IllegalArgumentException iae)
75          {
76              // Ignore expected exception
77          }
78  
79          double[] p3Arr = new double[] {5.0, 6.0, 7.0};
80          p = new Point3d(p3Arr);
81          assertEquals(5.0, p.x, 0);
82          assertEquals(6.0, p.y, 0);
83          assertEquals(7.0, p.z, 0);
84          Try.testFail(new Try.Execution()
85          {
86              @Override
87              public void execute() throws Throwable
88              {
89                  new Point3d(new double[] {});
90              }
91          }, "Should throw IAE", IllegalArgumentException.class);
92  
93          Try.testFail(new Try.Execution()
94          {
95              @Override
96              public void execute() throws Throwable
97              {
98                  new Point3d(new double[] {1.0});
99              }
100         }, "Should throw IAE", IllegalArgumentException.class);
101 
102         Try.testFail(new Try.Execution()
103         {
104             @Override
105             public void execute() throws Throwable
106             {
107                 new Point3d(new double[] {1.0, 2.0});
108             }
109         }, "Should throw IAE", IllegalArgumentException.class);
110 
111         Try.testFail(new Try.Execution()
112         {
113             @Override
114             public void execute() throws Throwable
115             {
116                 new Point3d(new double[] {1.0, 2.0, 3.0, 4.0});
117             }
118         }, "Should throw IAE", IllegalArgumentException.class);
119 
120         Try.testFail(new Try.Execution()
121         {
122             @Override
123             public void execute() throws Throwable
124             {
125                 new Point3d((Point2d) null, 0);
126             }
127         }, "Should throw NPE", NullPointerException.class);
128 
129         Try.testFail(new Try.Execution()
130         {
131             @Override
132             public void execute() throws Throwable
133             {
134                 new Point3d((Point2D.Double) null, 0);
135             }
136         }, "Should throw NPE", NullPointerException.class);
137 
138         Try.testFail(new Try.Execution()
139         {
140             @Override
141             public void execute() throws Throwable
142             {
143                 new Point3d(new Point2D.Double(Double.NaN, 2), 0);
144             }
145         }, "Should throw IAE", IllegalArgumentException.class);
146 
147         Try.testFail(new Try.Execution()
148         {
149             @Override
150             public void execute() throws Throwable
151             {
152                 new Point3d(new Point2D.Double(1, Double.NaN), 0);
153             }
154         }, "Should throw IAE", IllegalArgumentException.class);
155 
156         // equals and hashCode
157         assertTrue(p.equals(p));
158         assertEquals(p.hashCode(), p.hashCode());
159         Point2d p2d = new Point2d(1.0, 1.0);
160         assertFalse(p.equals(p2d));
161         assertFalse(p.equals(null));
162         assertNotEquals(p2d.hashCode(), p.hashCode());
163         assertEquals("Translating over 0,0,0 returns p", p, p.translate(0.0, 0.0, 0.0));
164         assertNotEquals(p, p.translate(1.0, 0.0, 0.0));
165         assertNotEquals(p, p.translate(0.0, 1.0, 0.0));
166         assertNotEquals(p, p.translate(0.0, 0.0, 1.0));
167 
168         // toString
169         p = new Point3d(10.0, 20.0, 30.0);
170         assertEquals("(10.000000,20.000000,30.000000)", p.toString());
171         assertEquals("(10.0,20.0,30.0)", p.toString(1));
172         assertEquals("(10,20,30)", p.toString(0));
173         assertEquals("(10,20,30)", p.toString(-1));
174 
175         // epsilonEquals
176         assertTrue(p.epsilonEquals(p, 0.1));
177         assertTrue(p.epsilonEquals(p, 0.001));
178         assertTrue(p.epsilonEquals(p, 0.0));
179         Point3d p3 = p.translate(0.001, 0.0, 0.0);
180         assertTrue(p.epsilonEquals(p3, 0.09));
181         assertTrue(p3.epsilonEquals(p, 0.09));
182         assertFalse(p.epsilonEquals(p3, 0.0009));
183         assertFalse(p3.epsilonEquals(p, 0.0009));
184         p3 = p.translate(0.0, 0.001, 0.0);
185         assertTrue(p.epsilonEquals(p3, 0.09));
186         assertTrue(p3.epsilonEquals(p, 0.09));
187         assertFalse(p.epsilonEquals(p3, 0.0009));
188         assertFalse(p3.epsilonEquals(p, 0.0009));
189         p3 = p.translate(0.0, 0.0, 0.001);
190         assertTrue(p.epsilonEquals(p3, 0.09));
191         assertTrue(p3.epsilonEquals(p, 0.09));
192         assertFalse(p.epsilonEquals(p3, 0.0009));
193         assertFalse(p3.epsilonEquals(p, 0.0009));
194 
195         p2d = new Point2d(123, 456);
196         p3 = new Point3d(p2d, 789);
197         assertEquals("x", 123, p3.x, 0);
198         assertEquals("y", 456, p3.y, 0);
199         assertEquals("z", 789, p3.z, 0);
200 
201         Point2D p2D = new java.awt.geom.Point2D.Double(123, 456);
202         p3 = new Point3d(p2D, 789);
203         assertEquals("x", 123, p3.x, 0);
204         assertEquals("y", 456, p3.y, 0);
205         assertEquals("z", 789, p3.z, 0);
206     }
207 
208     /**
209      * Test the Point3d operators.
210      */
211     @Test
212     public void testPoint3dOperators()
213     {
214         Point3d p = new Point3d(-0.1, -0.2, -0.3);
215         assertEquals(0.1, p.abs().x, 1E-6);
216         assertEquals(0.2, p.abs().y, 1E-6);
217         assertEquals(0.3, p.abs().z, 1E-6);
218         p = p.neg();
219         assertEquals(0.1, p.x, 1E-6);
220         assertEquals(0.2, p.y, 1E-6);
221         assertEquals(0.3, p.z, 1E-6);
222         p = p.scale(1.0);
223         assertEquals(0.1, p.x, 1E-6);
224         assertEquals(0.2, p.y, 1E-6);
225         assertEquals(0.3, p.z, 1E-6);
226         p = p.scale(10.0);
227         assertEquals(1.0, p.x, 1E-6);
228         assertEquals(2.0, p.y, 1E-6);
229         assertEquals(3.0, p.z, 1E-6);
230         p = p.translate(5.0, -1.0, 0.5);
231         assertEquals(6.0, p.x, 1E-6);
232         assertEquals(1.0, p.y, 1E-6);
233         assertEquals(3.5, p.z, 1E-6);
234         Point3d p3d = p.translate(1.0, 1.0, 1.0);
235         assertEquals(7.0, p3d.x, 1E-6);
236         assertEquals(2.0, p3d.y, 1E-6);
237         assertEquals(4.5, p3d.z, 1E-6);
238         p3d = p.translate(6.0, 1.0);
239         assertEquals(12.0, p3d.x, 1E-6);
240         assertEquals(2.0, p3d.y, 1E-6);
241         assertEquals(3.5, p3d.z, 1E-6);
242 
243         try
244         {
245             p.translate(Double.NaN, 2.0);
246             fail("NaN translation should have thrown an IllegalArgumentException");
247         }
248         catch (IllegalArgumentException iae)
249         {
250             // Ignore expected exception
251         }
252 
253         try
254         {
255             p.translate(1.0, Double.NaN);
256             fail("NaN translation should have thrown an IllegalArgumentException");
257         }
258         catch (IllegalArgumentException iae)
259         {
260             // Ignore expected exception
261         }
262 
263         try
264         {
265             p.translate(Double.NaN, 2.0, 3.0);
266             fail("NaN translation should have thrown an IllegalArgumentException");
267         }
268         catch (IllegalArgumentException iae)
269         {
270             // Ignore expected exception
271         }
272 
273         try
274         {
275             p.translate(1.0, Double.NaN, 3.0);
276             fail("NaN translation should have thrown an IllegalArgumentException");
277         }
278         catch (IllegalArgumentException iae)
279         {
280             // Ignore expected exception
281         }
282 
283         try
284         {
285             p.translate(1.0, 2.0, Double.NaN);
286             fail("NaN translation should have thrown an IllegalArgumentException");
287         }
288         catch (IllegalArgumentException iae)
289         {
290             // Ignore expected exception
291         }
292 
293         // interpolate
294         Point3d p1 = new Point3d(1.0, 1.0, 1.0);
295         Point3d p2 = new Point3d(5.0, 5.0, 5.0);
296         assertEquals("Interpolate at 0.0 returns this", p1, p1.interpolate(p2, 0.0));
297         assertEquals(p2, p2.interpolate(p1, 0.0));
298         assertEquals(p1, p1.interpolate(p1, 0.0));
299         assertEquals(new Point3d(3.0, 3.0, 3.0), p1.interpolate(p2, 0.5));
300 
301         // distance
302         assertEquals(Math.sqrt(48.0), p1.distance(p2), 0.001);
303         assertEquals(48.0, p1.distanceSquared(p2), 0.001);
304         assertEquals(Math.sqrt(32.0), p1.horizontalDistance(p2), 0.001);
305         assertEquals(32.0, p1.horizontalDistanceSquared(p2), 0.001);
306 
307         // direction
308         assertEquals(Math.toRadians(45.0), p2.horizontalDirection(), 0.001);
309         assertEquals(Math.toRadians(45.0), p1.horizontalDirection(p2), 0.001);
310         assertEquals(0.0, new Point3d(0.0, 0.0, 0.0).horizontalDirection(), 0.001);
311 
312         // normalize
313         Point3d pn = p2.normalize();
314         assertEquals(1.0 / Math.sqrt(3.0), pn.x, 0.001);
315         assertEquals(1.0 / Math.sqrt(3.0), pn.y, 0.001);
316         assertEquals(1.0 / Math.sqrt(3.0), pn.z, 0.001);
317 
318         Try.testFail(new Try.Execution()
319         {
320             @Override
321             public void execute() throws Throwable
322             {
323                 new Point3d(0.0, 0.0, 0.0).normalize();
324             }
325         }, "Should throw DRtE", DrawRuntimeException.class);
326 
327         assertEquals("size of a Point3d is 1", 1, p1.size());
328         Point2d projection = p1.project();
329         assertEquals("projected x", p1.x, projection.x, 0);
330         assertEquals("projected y", p1.y, projection.y, 0);
331 
332         Bounds3d bounds = p1.getBounds();
333         assertEquals("Bounds min x", p1.x, bounds.getMinX(), 0);
334         assertEquals("Bounds min y", p1.y, bounds.getMinY(), 0);
335         assertEquals("Bounds min z", p1.z, bounds.getMinZ(), 0);
336         assertEquals("Bounds max x", p1.x, bounds.getMaxX(), 0);
337         assertEquals("Bounds max y", p1.y, bounds.getMaxY(), 0);
338         assertEquals("Bounds max z", p1.z, bounds.getMaxZ(), 0);
339     }
340 
341     /**
342      * Test the Point3d operators for NPE.
343      */
344     @Test
345     public void testPoint3dOperatorsNPE()
346     {
347         final Point3d p1 = new Point3d(1.0, 1.0, 1.0);
348 
349         Try.testFail(new Try.Execution()
350         {
351             @Override
352             public void execute() throws Throwable
353             {
354                 p1.interpolate(null, 0.5);
355             }
356         }, "Should throw NPE", NullPointerException.class);
357 
358         Try.testFail(new Try.Execution()
359         {
360             @Override
361             public void execute() throws Throwable
362             {
363                 p1.distance(null);
364             }
365         }, "Should throw NPE", NullPointerException.class);
366 
367         Try.testFail(new Try.Execution()
368         {
369             @Override
370             public void execute() throws Throwable
371             {
372                 p1.distanceSquared(null);
373             }
374         }, "Should throw NPE", NullPointerException.class);
375 
376         // FIXME
377         // Try.testFail(new Try.Execution()
378         // {
379         // @Override
380         // public void execute() throws Throwable
381         // {
382         // p1.horizontalDistance((Point2d) null);
383         // }
384         // }, "Should throw NPE", NullPointerException.class);
385         //
386         // Try.testFail(new Try.Execution()
387         // {
388         // @Override
389         // public void execute() throws Throwable
390         // {
391         // p1.horizontalDistanceSquared((Point3d) null);
392         // }
393         // }, "Should throw NPE", NullPointerException.class);
394 
395     }
396 
397     /**
398      * Test the closestPointOnSegment method.
399      * @throws DrawException if that happens uncaught; this test has failed
400      */
401     @Test
402     public void testClosestPointOnSegment() throws DrawException
403     {
404         Point3d p1 = new Point3d(-2, 3, 5);
405         for (Point3d p2 : new Point3d[] {new Point3d(7, 4, -5)/* angled */, new Point3d(-3, 6, 5) /* also angled */,
406                 new Point3d(-2, -5, 5) /* vertical */, new Point3d(8, 3, 5)/* horizontal */, new Point3d(-2, 3, 1)/* z */ })
407         {
408             PolyLine3d line = new PolyLine3d(p1, p2);
409             for (double x = -10; x <= 10; x += 0.5)
410             {
411                 for (double y = -10; y <= 10; y += 0.5)
412                 {
413                     for (double z = -10; z <= 10; z += 0.5)
414                     {
415                         Point3d p = new Point3d(x, y, z);
416                         Point3d result = p.closestPointOnSegment(p1, p2);
417                         // Figure out the correct result using a totally different method (binary search over the line segment)
418                         double fraction = 0.5;
419                         double step = 0.25;
420                         Point3d approximation = line.getLocationFraction(fraction);
421                         double distance = approximation.distance(p);
422                         // 10 iterations should get us to within one thousandth
423                         for (int iteration = 0; iteration < 10; iteration++)
424                         {
425                             // Try stepping up
426                             double upFraction = fraction + step;
427                             Point3d upApproximation = line.getLocationFraction(upFraction);
428                             double upDistance = upApproximation.distance(p);
429                             if (upDistance < distance)
430                             {
431                                 distance = upDistance;
432                                 fraction = upFraction;
433                                 approximation = upApproximation;
434                             }
435                             else
436                             {
437                                 // Try stepping down
438                                 double downFraction = fraction - step;
439                                 Point3d downApproximation = line.getLocationFraction(downFraction);
440                                 double downDistance = downApproximation.distance(p);
441                                 if (downDistance < distance)
442                                 {
443                                     distance = downDistance;
444                                     fraction = downFraction;
445                                     approximation = downApproximation;
446                                 }
447                             }
448                             step /= 2;
449                         }
450                         assertEquals("distance should be less than one thousandth of line length", 0,
451                                 approximation.distance(result), line.getLength() / 1000);
452                         assertEquals("zero length line segment should always return start point", p1,
453                                 p.closestPointOnSegment(p1, p1));
454                     }
455                 }
456             }
457         }
458     }
459 
460 }