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.assertNull;
8   import static org.junit.Assert.assertTrue;
9   import static org.junit.Assert.fail;
10  
11  import java.awt.geom.Point2D;
12  import java.util.List;
13  
14  import org.djutils.draw.DrawRuntimeException;
15  import org.djutils.draw.bounds.Bounds2d;
16  import org.djutils.draw.line.LineSegment2d;
17  import org.djutils.draw.line.PolyLine2d;
18  import org.djutils.exceptions.Try;
19  import org.junit.Test;
20  
21  /**
22   * Point2dTest.java.
23   * <p>
24   * Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
25   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
26   * </p>
27   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
28   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
29   */
30  public class Point2dTest
31  {
32      /**
33       * Test the Point2d construction methods.
34       */
35      @SuppressWarnings("unlikely-arg-type")
36      @Test
37      public void testPoint2dConstruction()
38      {
39          Point2d p = new Point2d(10.0, -20.0);
40          assertNotNull(p);
41          assertEquals("Access x", 10.0, p.x, 1E-6);
42          assertEquals("Access y", -20.0, p.y, 1E-6);
43          assertEquals("Dimensions is 2", 2, p.getDimensions());
44  
45          assertEquals("Size method returns 1", 1, p.size());
46  
47          try
48          {
49              new Point2d(Double.NaN, 0);
50              fail("NaN should have thrown an IllegalArgumentException");
51          }
52          catch (IllegalArgumentException iae)
53          {
54              // Ignore expected exception
55          }
56  
57          try
58          {
59              new Point2d(0, Double.NaN);
60              fail("NaN should have thrown an IllegalArgumentException");
61          }
62          catch (IllegalArgumentException iae)
63          {
64              // Ignore expected exception
65          }
66  
67          double[] p2Arr = new double[] { 5.0, 6.0 };
68          p = new Point2d(p2Arr);
69          assertEquals(5.0, p.x, 0);
70          assertEquals(6.0, p.y, 0);
71          Point2D.Double p2DD = new Point2D.Double(-0.1, -0.2);
72          p = new Point2d(p2DD);
73          assertEquals(-0.1, p.x, 1E-6);
74          assertEquals(-0.2, p.y, 1E-6);
75          assertEquals(p2DD, p.toPoint2D());
76  
77          Try.testFail(new Try.Execution()
78          {
79              @Override
80              public void execute() throws Throwable
81              {
82                  new Point2d((Point2D.Double) null);
83              }
84          }, "Should throw NPE", NullPointerException.class);
85  
86          Try.testFail(new Try.Execution()
87          {
88              @Override
89              public void execute() throws Throwable
90              {
91                  new Point2d(new double[] {});
92              }
93          }, "Should throw IAE", IllegalArgumentException.class);
94  
95          Try.testFail(new Try.Execution()
96          {
97              @Override
98              public void execute() throws Throwable
99              {
100                 new Point2d(new double[] { 1.0 });
101             }
102         }, "Should throw IAE", IllegalArgumentException.class);
103 
104         Try.testFail(new Try.Execution()
105         {
106             @Override
107             public void execute() throws Throwable
108             {
109                 new Point2d(new double[] { 1.0, 2.0, 3.0 });
110             }
111         }, "Should throw IAE", IllegalArgumentException.class);
112 
113         Try.testFail(new Try.Execution()
114         {
115             @Override
116             public void execute() throws Throwable
117             {
118                 new Point2d(new Point2D.Double(Double.NaN, 2));
119             }
120         }, "Should throw IAE", IllegalArgumentException.class);
121 
122         Try.testFail(new Try.Execution()
123         {
124             @Override
125             public void execute() throws Throwable
126             {
127                 new Point2d(new Point2D.Double(1, Double.NaN));
128             }
129         }, "Should throw IAE", IllegalArgumentException.class);
130 
131         // equals and hashCode
132         assertTrue(p.equals(p));
133         assertEquals(p.hashCode(), p.hashCode());
134         Point3d p3d = p.translate(1.0, 2.0, 3.0);
135         assertFalse(p.equals(p3d));
136         assertFalse(p.equals(null));
137         assertNotEquals(p3d.hashCode(), p.hashCode());
138         assertEquals(p, p.translate(0.0, 0.0));
139         assertNotEquals(p, p.translate(1.0, 0.0));
140         assertNotEquals(p, p.translate(0.0, 1.0));
141         assertEquals("x", p.x + 1, p3d.x, 0.00001);
142         assertEquals("y", p.y + 2, p3d.y, 0.00001);
143         assertEquals("z", 3, p3d.z, 0);
144 
145         // toString
146         p = new Point2d(10.0, 20.0);
147         assertEquals("Point2d [x=10.000000, y=20.000000]", p.toString());
148         assertEquals("Point2d [x=10.0, y=20.0]", p.toString("%.1f"));
149         assertEquals("[x=10, y=20]", p.toString("%.0f", true));
150 
151         // epsilonEquals
152         assertTrue(p.epsilonEquals(p, 0.1));
153         assertTrue(p.epsilonEquals(p, 0.001));
154         assertTrue(p.epsilonEquals(p, 0.0));
155         Point2d p3 = p.translate(0.001, 0.0);
156         assertTrue(p.epsilonEquals(p3, 0.09));
157         assertTrue(p3.epsilonEquals(p, 0.09));
158         assertFalse(p.epsilonEquals(p3, 0.0009));
159         assertFalse(p3.epsilonEquals(p, 0.0009));
160         p3 = p.translate(0.0, 0.001);
161         assertTrue(p.epsilonEquals(p3, 0.09));
162         assertTrue(p3.epsilonEquals(p, 0.09));
163         assertFalse(p.epsilonEquals(p3, 0.0009));
164         assertFalse(p3.epsilonEquals(p, 0.0009));
165     }
166 
167     /**
168      * Test the Point2d operators.
169      */
170     @Test
171     public void testPoint2dOperators()
172     {
173         Point2d p = new Point2d(-0.1, -0.2);
174         assertEquals(0.1, p.abs().x, 1E-6);
175         assertEquals(0.2, p.abs().y, 1E-6);
176         p = p.neg();
177         assertEquals(0.1, p.x, 1E-6);
178         assertEquals(0.2, p.y, 1E-6);
179         p = p.scale(1.0);
180         assertEquals(0.1, p.x, 1E-6);
181         assertEquals(0.2, p.y, 1E-6);
182         p = p.scale(10.0);
183         assertEquals(1.0, p.x, 1E-6);
184         assertEquals(2.0, p.y, 1E-6);
185         p = p.translate(5.0, -1.0);
186         assertEquals(6.0, p.x, 1E-6);
187         assertEquals(1.0, p.y, 1E-6);
188         Point3d p3d = p.translate(1.0, 1.0, 1.0);
189         assertEquals(7.0, p3d.x, 1E-6);
190         assertEquals(2.0, p3d.y, 1E-6);
191         assertEquals(1.0, p3d.z, 1E-6);
192 
193         try
194         {
195             p.translate(Double.NaN, 2.0);
196             fail("NaN translation should have thrown an IllegalArgumentException");
197         }
198         catch (IllegalArgumentException iae)
199         {
200             // Ignore expected exception
201         }
202 
203         try
204         {
205             p.translate(1.0, Double.NaN);
206             fail("NaN translation should have thrown an IllegalArgumentException");
207         }
208         catch (IllegalArgumentException iae)
209         {
210             // Ignore expected exception
211         }
212 
213         // interpolate
214         Point2d p1 = new Point2d(1.0, 1.0);
215         Point2d p2 = new Point2d(5.0, 5.0);
216         assertEquals(p1, p1.interpolate(p2, 0.0));
217         assertEquals(p2, p2.interpolate(p1, 0.0));
218         assertEquals(p1, p1.interpolate(p1, 0.0));
219         assertEquals(new Point2d(3.0, 3.0), p1.interpolate(p2, 0.5));
220 
221         // distance
222         assertEquals(Math.sqrt(32.0), p1.distance(p2), 0.001);
223         assertEquals(32.0, p1.distanceSquared(p2), 0.001);
224         // FIXME
225         // assertEquals(Math.sqrt(32.0), p1.horizontalDistance(p2), 0.001);
226         // assertEquals(32.0, p1.horizontalDistanceSquared(p2), 0.001);
227         //
228         // // direction
229         // assertEquals(Math.toRadians(45.0), p2.horizontalDirection(), 0.001);
230         // assertEquals(Math.toRadians(45.0), p1.horizontalDirection(p2), 0.001);
231         // assertEquals(0.0, new Point2d(0.0, 0.0).horizontalDirection(), 0.001);
232 
233         // normalize
234         Point2d pn = p2.normalize();
235         assertEquals(1.0 / Math.sqrt(2.0), pn.x, 0.001);
236         assertEquals(1.0 / Math.sqrt(2.0), pn.y, 0.001);
237 
238         Try.testFail(new Try.Execution()
239         {
240             @Override
241             public void execute() throws Throwable
242             {
243                 new Point2d(0.0, 0.0).normalize();
244             }
245         }, "Should throw DRtE", DrawRuntimeException.class);
246 
247         Bounds2d bounds = p1.getBounds();
248         assertEquals("Bounds min x", p1.x, bounds.getMinX(), 0);
249         assertEquals("Bounds min y", p1.y, bounds.getMinY(), 0);
250         assertEquals("Bounds max x", p1.x, bounds.getMaxX(), 0);
251         assertEquals("Bounds max y", p1.y, bounds.getMaxY(), 0);
252     }
253 
254     /**
255      * Test the Point2d operators for NPE.
256      */
257     @Test
258     public void testPoint2dOperatorsNPE()
259     {
260         final Point2d p1 = new Point2d(1.0, 1.0);
261 
262         try
263         {
264             p1.translate(Double.NaN, 2.0);
265             fail("NaN translation should have thrown an IllegalArgumentException");
266         }
267         catch (IllegalArgumentException iae)
268         {
269             // Ignore expected exception
270         }
271 
272         try
273         {
274             p1.translate(1.0, Double.NaN);
275             fail("NaN translation should have thrown an IllegalArgumentException");
276         }
277         catch (IllegalArgumentException iae)
278         {
279             // Ignore expected exception
280         }
281 
282         try
283         {
284             p1.translate(Double.NaN, 2.0, 3.0);
285             fail("NaN translation should have thrown an IllegalArgumentException");
286         }
287         catch (IllegalArgumentException iae)
288         {
289             // Ignore expected exception
290         }
291 
292         try
293         {
294             p1.translate(1.0, Double.NaN, 3.0);
295             fail("NaN translation should have thrown an IllegalArgumentException");
296         }
297         catch (IllegalArgumentException iae)
298         {
299             // Ignore expected exception
300         }
301 
302         try
303         {
304             p1.translate(1.0, 2.0, Double.NaN);
305             fail("NaN translation should have thrown an IllegalArgumentException");
306         }
307         catch (IllegalArgumentException iae)
308         {
309             // Ignore expected exception
310         }
311 
312         Try.testFail(new Try.Execution()
313         {
314             @Override
315             public void execute() throws Throwable
316             {
317                 p1.interpolate(null, 0.5);
318             }
319         }, "Should throw NPE", NullPointerException.class);
320 
321         Try.testFail(new Try.Execution()
322         {
323             @Override
324             public void execute() throws Throwable
325             {
326                 p1.distance(null);
327             }
328         }, "Should throw NPE", NullPointerException.class);
329 
330         Try.testFail(new Try.Execution()
331         {
332             @Override
333             public void execute() throws Throwable
334             {
335                 p1.distanceSquared(null);
336             }
337         }, "Should throw NPE", NullPointerException.class);
338     }
339 
340     /**
341      * Test the intersectionOfLineSegments method.
342      */
343     @Test
344     public void testIntersectionOfLineSegments()
345     {
346         assertNull("horizontal line intersection with itself returns null",
347                 Point2d.intersectionOfLineSegments(new Point2d(1, 2), new Point2d(4, 2), new Point2d(1, 2), new Point2d(4, 2)));
348         assertNull("vertical line intersection with itself returns null", Point2d.intersectionOfLineSegments(new Point2d(1, 2),
349                 new Point2d(1, 10), new Point2d(1, 2), new Point2d(1, 10)));
350         assertEquals("Intersection is at (2,2)", new Point2d(2, 2), Point2d.intersectionOfLineSegments(new Point2d(1, 1),
351                 new Point2d(6, 6), new Point2d(4, 2), new Point2d(-2, 2)));
352         assertEquals("Intersection is at (2,2)", new Point2d(2, 2),
353                 Point2d.intersectionOfLineSegments(1, 1, 6, 6, 4, 2, -2, 2));
354         // Check all four ways that two non-parallel lines can miss each other
355         assertNull("line two passes before start of line one", Point2d.intersectionOfLineSegments(new Point2d(1, 1),
356                 new Point2d(5, 5), new Point2d(0, -3), new Point2d(10, 0)));
357         assertNull("line two passes before after end of line one", Point2d.intersectionOfLineSegments(new Point2d(1, 1),
358                 new Point2d(5, 5), new Point2d(0, 20), new Point2d(100, 30)));
359         assertNull("line one passes before start of line two", Point2d.intersectionOfLineSegments(new Point2d(1, 1),
360                 new Point2d(5, 5), new Point2d(5, 3), new Point2d(10, 2)));
361         assertNull("line one passes after end of line two", Point2d.intersectionOfLineSegments(new Point2d(1, 1),
362                 new Point2d(5, 5), new Point2d(-10, 3), new Point2d(0, 2)));
363         assertNull("line two passes before start of line one", Point2d.intersectionOfLineSegments(1, 1, 5, 5, 0, -3, 10, 0));
364         assertNull("line two passes before after end of line one",
365                 Point2d.intersectionOfLineSegments(1, 1, 5, 5, 0, 20, 100, 30));
366         assertNull("line one passes before start of line two", Point2d.intersectionOfLineSegments(1, 1, 5, 5, 5, 3, 10, 2));
367         assertNull("line one passes after end of line two", Point2d.intersectionOfLineSegments(1, 1, 5, 5, -10, 3, 0, 2));
368 
369         Point2d line1P1 = new Point2d(1, 2);
370         Point2d line1P2 = new Point2d(3, 2);
371         Point2d line2P1 = new Point2d(2, 0);
372         Point2d line2P2 = new Point2d(2, 4);
373         try
374         {
375             Point2d.intersectionOfLines(null, line1P2, line2P1, line2P2);
376             fail("Null parameter should have thrown a NullPointerException");
377         }
378         catch (NullPointerException npe)
379         {
380             // Ignore expected exception
381         }
382 
383         try
384         {
385             Point2d.intersectionOfLines(line1P1, null, line2P1, line2P2);
386             fail("Null parameter should have thrown a NullPointerException");
387         }
388         catch (NullPointerException npe)
389         {
390             // Ignore expected exception
391         }
392 
393         try
394         {
395             Point2d.intersectionOfLines(line1P1, line1P2, null, line2P2);
396             fail("Null parameter should have thrown a NullPointerException");
397         }
398         catch (NullPointerException npe)
399         {
400             // Ignore expected exception
401         }
402 
403         try
404         {
405             Point2d.intersectionOfLines(line1P1, line1P2, line2P1, null);
406             fail("Null parameter should have thrown a NullPointerException");
407         }
408         catch (NullPointerException npe)
409         {
410             // Ignore expected exception
411         }
412 
413         try
414         {
415             Point2d.intersectionOfLineSegments(null, line1P2, line2P1, line2P2);
416             fail("Null parameter should have thrown a NullPointerException");
417         }
418         catch (NullPointerException npe)
419         {
420             // Ignore expected exception
421         }
422 
423         try
424         {
425             Point2d.intersectionOfLineSegments(line1P1, null, line2P1, line2P2);
426             fail("Null parameter should have thrown a NullPointerException");
427         }
428         catch (NullPointerException npe)
429         {
430             // Ignore expected exception
431         }
432 
433         try
434         {
435             Point2d.intersectionOfLineSegments(line1P1, line1P2, null, line2P2);
436             fail("Null parameter should have thrown a NullPointerException");
437         }
438         catch (NullPointerException npe)
439         {
440             // Ignore expected exception
441         }
442 
443         try
444         {
445             Point2d.intersectionOfLineSegments(line1P1, line1P2, line2P1, null);
446             fail("Null parameter should have thrown a NullPointerException");
447         }
448         catch (NullPointerException npe)
449         {
450             // Ignore expected exception
451         }
452 
453     }
454 
455     /**
456      * Test the intersectionOfLines method.
457      */
458     @Test
459     public void testIntersectionOfLines()
460     {
461         assertNull("horizontal line intersection with itself returns null",
462                 Point2d.intersectionOfLines(new Point2d(1, 2), new Point2d(4, 2), new Point2d(1, 2), new Point2d(4, 2)));
463         assertNull("horizontal line intersection with itself returns null",
464                 Point2d.intersectionOfLines(1, 2, 4, 2, 1, 2, 4, 2));
465         assertNull("vertical line intersection with itself returns null", Point2d.intersectionOfLineSegments(new Point2d(1, 2),
466                 new Point2d(1, 10), new Point2d(1, 2), new Point2d(1, 10)));
467         assertNull("vertical line intersection with itself returns null",
468                 Point2d.intersectionOfLineSegments(1, 2, 1, 10, 1, 2, 1, 10));
469         assertEquals("Intersection is at (2,2)", new Point2d(2, 2),
470                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(6, 6), new Point2d(4, 2), new Point2d(-2, 2)));
471         // Check all four ways that two non-parallel lines can miss each other
472         assertEquals("line two passes before start of line one", new Point2d(-1.5, -1.5),
473                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(0, -3), new Point2d(10, -13)));
474         assertEquals("line two passes before after end of line one", new Point2d(20, 20),
475                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(0, 20), new Point2d(100, 20)));
476         assertEquals("line one passes before start of line two", new Point2d(4, 4),
477                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(7, 1), new Point2d(10, -2)));
478         assertEquals("line one passes after end of line two", new Point2d(-3.5, -3.5),
479                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(-10, 3), new Point2d(0, -7)));
480         // Test the various exact hits at begin or end point
481         assertEquals("begin of first is on second", new Point2d(1, 1),
482                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(2, 1), new Point2d(1, 0), new Point2d(1, 3)));
483         assertEquals("end of first is on second", new Point2d(1, 1),
484                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(1, 1), new Point2d(1, 0), new Point2d(1, 3)));
485         assertEquals("begin of second is on first", new Point2d(1, 1),
486                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, 1), new Point2d(1, 3)));
487         assertEquals("end of second is on first", new Point2d(1, 1),
488                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, -1), new Point2d(1, 1)));
489         // Test the various not quite exact hits at begin or end point
490         assertTrue("begin of first is just over second", new Point2d(1, 1).epsilonEquals(
491                 Point2d.intersectionOfLines(new Point2d(1.001, 1), new Point2d(2, 1), new Point2d(1, 0), new Point2d(1, 3)),
492                 0.0001));
493         assertTrue("end of first is just over second", new Point2d(1, 1).epsilonEquals(
494                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(0.999, 1), new Point2d(1, 0), new Point2d(1, 3)),
495                 0.0001));
496         assertTrue("begin of second is just over first", new Point2d(1, 1).epsilonEquals(
497                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, 1.001), new Point2d(1, 3)),
498                 0.0001));
499         assertTrue("end of second is just over first", new Point2d(1, 1).epsilonEquals(
500                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, -1), new Point2d(1, 0.999)),
501                 0.0001));
502         // Test the various close hits at begin or end point
503         assertNull("begin of first is just not on second", Point2d.intersectionOfLineSegments(new Point2d(1.001, 1),
504                 new Point2d(2, 1), new Point2d(1, 0), new Point2d(1, 3)));
505         assertNull("end of first is just not on second", Point2d.intersectionOfLineSegments(new Point2d(-1, 1),
506                 new Point2d(0.999, 1), new Point2d(1, 0), new Point2d(1, 3)));
507         assertNull("begin of second is just not on first", Point2d.intersectionOfLineSegments(new Point2d(-1, 1),
508                 new Point2d(2, 1), new Point2d(1, 1.001), new Point2d(1, 3)));
509         assertNull("end of second is just not on first", Point2d.intersectionOfLineSegments(new Point2d(-1, 1),
510                 new Point2d(2, 1), new Point2d(1, -1), new Point2d(1, 0.999)));
511     }
512 
513     /**
514      * Test the closestPointOnSegment and the closestPointOnLine methods.
515      * @throws DrawRuntimeException if that happens uncaught; this test has failed
516      */
517     @Test
518     public void testClosestPointOnSegmentAndLine() throws DrawRuntimeException
519     {
520         Point2d p1 = new Point2d(-2, 3);
521         for (Point2d p2 : new Point2d[] { new Point2d(7, 4)/* angled */, new Point2d(-3, 6) /* also angled */,
522                 new Point2d(-2, -5) /* vertical */, new Point2d(8, 3)/* horizontal */ })
523         {
524             PolyLine2d line = new PolyLine2d(p1, p2);
525             for (double x = -10; x <= 10; x += 0.5)
526             {
527                 for (double y = -10; y <= 10; y += 0.5)
528                 {
529                     Point2d p = new Point2d(x, y);
530                     // Figure out the correct result using a totally different method (binary search over the line segment)
531                     double fraction = 0.5;
532                     double step = 0.25;
533                     Point2d approximation = line.getLocationFraction(fraction);
534                     double distance = approximation.distance(p);
535                     // 10 iterations should get us to within one thousandth
536                     for (int iteration = 0; iteration < 10; iteration++)
537                     {
538                         // Try stepping up
539                         double upFraction = fraction + step;
540                         Point2d upApproximation = line.getLocationFraction(upFraction);
541                         double upDistance = upApproximation.distance(p);
542                         if (upDistance < distance)
543                         {
544                             distance = upDistance;
545                             fraction = upFraction;
546                             approximation = upApproximation;
547                         }
548                         else
549                         {
550                             // Try stepping down
551                             double downFraction = fraction - step;
552                             Point2d downApproximation = line.getLocationFraction(downFraction);
553                             double downDistance = downApproximation.distance(p);
554                             if (downDistance < distance)
555                             {
556                                 distance = downDistance;
557                                 fraction = downFraction;
558                                 approximation = downApproximation;
559                             }
560                         }
561                         step /= 2;
562                     }
563                     Point2d result = p.closestPointOnSegment(p1, p2);
564                     assertEquals("distance should be less than one thousandth of line length", 0,
565                             approximation.distance(result), line.getLength() / 1000);
566                     assertEquals("zero length line segment should always return start point", p1,
567                             p.closestPointOnSegment(p1, p1));
568                     result = p.closestPointOnSegment(p1.x, p1.y, p2.x, p2.y);
569                     assertEquals("distance should be less than one thousandth of line length", 0,
570                             approximation.distance(result), line.getLength() / 1000);
571 
572                     if (fraction > 0.001 && fraction < 0.999)
573                     {
574                         result = p.closestPointOnLine(p1, p2);
575                         assertEquals("distance should be less than one thousandth of line length", 0,
576                                 approximation.distance(result), line.getLength() / 1000);
577                         result = p.closestPointOnLine(p1, p2);
578                         assertEquals("distance should be less than one thousandth of line length", 0,
579                                 approximation.distance(result), line.getLength() / 1000);
580                         result = p.closestPointOnLine(p1.x, p1.y, p2.x, p2.y);
581                         assertEquals("distance should be less than one thousandth of line length", 0,
582                                 approximation.distance(result), line.getLength() / 1000);
583                     }
584                     else
585                     {
586                         // extrapolating
587                         double range = Math.max(Math.max(line.getLength(), p.distance(p1)), p.distance(p2));
588                         step = 5.0;
589                         fraction = 0.5;
590                         distance = range;
591                         // 20 iterations should get us to within one thousandth
592                         for (int iteration = 0; iteration < 20; iteration++)
593                         {
594                             // Try stepping up
595                             double upFraction = fraction + step;
596                             Point2d upApproximation = line.getLocationFractionExtended(upFraction);
597                             double upDistance = upApproximation.distance(p);
598                             if (upDistance < distance)
599                             {
600                                 distance = upDistance;
601                                 fraction = upFraction;
602                                 approximation = upApproximation;
603                             }
604                             else
605                             {
606                                 // Try stepping down
607                                 double downFraction = fraction - step;
608                                 Point2d downApproximation = line.getLocationFractionExtended(downFraction);
609                                 double downDistance = downApproximation.distance(p);
610                                 if (downDistance < distance)
611                                 {
612                                     distance = downDistance;
613                                     fraction = downFraction;
614                                     approximation = downApproximation;
615                                 }
616                             }
617                             step /= 2;
618                         }
619                         result = p.closestPointOnLine(p1, p2);
620                         assertEquals("distance should be less than one thousandth of range", 0, approximation.distance(result),
621                                 range / 1000);
622                         result = p.closestPointOnLine(p1, p2);
623                         assertEquals("distance should be less than one thousandth of range", 0, approximation.distance(result),
624                                 range / 1000);
625                         result = p.closestPointOnLine(p1.x, p1.y, p2.x, p2.y);
626                         assertEquals("distance should be less than one thousandth of range", 0, approximation.distance(result),
627                                 range / 1000);
628                         if (fraction < -0.001 || fraction > 1.001)
629                         {
630                             assertNull("projectOrthogonal should return null", new LineSegment2d(p1, p2).projectOrthogonal(p));
631                             assertEquals("projectOrthogonalExtended should return same result as closestPointOnLine", result,
632                                     new LineSegment2d(p1, p2).projectOrthogonalExtended(p));
633                         }
634                     }
635                 }
636             }
637         }
638 
639         try
640         {
641             p1.closestPointOnLine(null, new Point2d(5, 6));
642             fail("null should have thrown a NullPointerException");
643         }
644         catch (NullPointerException npe)
645         {
646             // Ignore expected exception
647         }
648 
649         try
650         {
651             p1.closestPointOnLine(new Point2d(5, 6), null);
652             fail("null should have thrown a NullPointerException");
653         }
654         catch (NullPointerException npe)
655         {
656             // Ignore expected exception
657         }
658 
659         try
660         {
661             p1.closestPointOnSegment(Double.NaN, 7, 8, 9);
662             fail("NaN value should have thrown a DrawRuntimeException");
663         }
664         catch (DrawRuntimeException dre)
665         {
666             // Ignore expected exception
667         }
668 
669         try
670         {
671             p1.closestPointOnSegment(6, Double.NaN, 8, 9);
672             fail("NaN value should have thrown a DrawRuntimeException");
673         }
674         catch (DrawRuntimeException dre)
675         {
676             // Ignore expected exception
677         }
678 
679         try
680         {
681             p1.closestPointOnSegment(6, 7, Double.NaN, 9);
682             fail("NaN value should have thrown a DrawRuntimeException");
683         }
684         catch (DrawRuntimeException dre)
685         {
686             // Ignore expected exception
687         }
688 
689         try
690         {
691             p1.closestPointOnSegment(6, 7, 8, Double.NaN);
692             fail("NaN value should have thrown a DrawRuntimeException");
693         }
694         catch (DrawRuntimeException dre)
695         {
696             // Ignore expected exception
697         }
698 
699         try
700         {
701             p1.closestPointOnLine(Double.NaN, 7, 8, 9);
702             fail("NaN value should have thrown a DrawRuntimeException");
703         }
704         catch (DrawRuntimeException dre)
705         {
706             // Ignore expected exception
707         }
708 
709         try
710         {
711             p1.closestPointOnLine(6, Double.NaN, 8, 9);
712             fail("NaN value should have thrown a DrawRuntimeException");
713         }
714         catch (DrawRuntimeException dre)
715         {
716             // Ignore expected exception
717         }
718 
719         try
720         {
721             p1.closestPointOnLine(6, 7, Double.NaN, 9);
722             fail("NaN value should have thrown a DrawRuntimeException");
723         }
724         catch (DrawRuntimeException dre)
725         {
726             // Ignore expected exception
727         }
728 
729         try
730         {
731             p1.closestPointOnLine(6, 7, 8, Double.NaN);
732             fail("NaN value should have thrown a DrawRuntimeException");
733         }
734         catch (DrawRuntimeException dre)
735         {
736             // Ignore expected exception
737         }
738 
739         try
740         {
741             p1.closestPointOnLine(6, 7, 6, 7);
742             fail("identical points should have thrown a DrawRuntimeException");
743         }
744         catch (DrawRuntimeException dre)
745         {
746             // Ignore expected exception
747         }
748 
749     }
750 
751     /**
752      * Test the circleIntersection method.
753      */
754     @Test
755     public void circleIntersectionTest()
756     {
757         for (int x1 = -5; x1 <= 5; x1++)
758         {
759             for (int y1 = -5; y1 <= 5; y1++)
760             {
761                 Point2d p1 = new Point2d(x1, y1);
762                 for (int r1 = 0; r1 < 5; r1++)
763                 {
764                     for (int x2 = -5; x2 <= 5; x2++)
765                     {
766                         for (int y2 = -5; y2 <= 5; y2++)
767                         {
768                             Point2d p2 = new Point2d(x2, y2);
769                             double distance = p1.distance(p2);
770                             for (int r2 = 0; r2 < 5; r2++)
771                             {
772                                 if (x1 == x2 && y1 == y2 && r1 == r2)
773                                 {
774                                     try
775                                     {
776                                         Point2d.circleIntersections(p1, r1, p2, r2);
777                                         fail("Identical circles should have thrown a DrawRuntimeException");
778                                     }
779                                     catch (DrawRuntimeException dre)
780                                     {
781                                         // Ignore expected exception
782                                     }
783                                 }
784                                 else
785                                 {
786                                     List<Point2d> result = Point2d.circleIntersections(p1, r1, p2, r2);
787                                     // System.out.print("p1=" + p1 + ", r1=" + r1 + ", p2=" + p2 + " r2=" + r2 + ", result=");
788                                     // for (Point2d p : result)
789                                     // {
790                                     // System.out
791                                     // .print(String.format("%s d1=%.3f d2=%.3f ", p, p.distance(p1), p.distance(p2)));
792                                     // }
793                                     // System.out.println("");
794                                     if (distance > r1 + r2 + 0.0001)
795                                     {
796                                         if (result.size() > 0)
797                                         {
798                                             Point2d.circleIntersections(p1, r1, p2, r2);
799                                         }
800                                         assertEquals("There are 0 intersections", 0, result.size());
801                                     }
802                                     if (distance < r1 + r2 - 0.0001 && distance > Math.abs(r2 - r1) + 0.0001)
803                                     {
804                                         if (result.size() != 2)
805                                         {
806                                             Point2d.circleIntersections(p1, r1, p2, r2);
807                                         }
808                                         assertEquals("There are 2 intersections", 2, result.size());
809                                     }
810                                     for (Point2d p : result)
811                                     {
812                                         if (Math.abs(r1 - p.distance(p1)) > 0.1 || Math.abs(r2 - p.distance(p2)) > 0.1)
813                                         {
814                                             Point2d.circleIntersections(p1, r1, p2, r2);
815                                         }
816                                         assertEquals("result is at r1 from p1", r1, p.distance(p1), 0.0001);
817                                         assertEquals("result is at r2 from p2", r2, p.distance(p2), 0.0001);
818                                     }
819                                 }
820                             }
821                         }
822                     }
823                 }
824             }
825         }
826         try
827         {
828             Point2d.circleIntersections(new Point2d(1, 2), -1, new Point2d(3, 4), 2);
829             fail("negative radius should have thrown a DrawRuntimeException");
830         }
831         catch (DrawRuntimeException dre)
832         {
833             // Ignore expected exception
834         }
835 
836         try
837         {
838             Point2d.circleIntersections(new Point2d(1, 2), 5, new Point2d(3, 4), -2);
839             fail("negative radius should have thrown a DrawRuntimeException");
840         }
841         catch (DrawRuntimeException dre)
842         {
843             // Ignore expected exception
844         }
845 
846         try
847         {
848             Point2d.circleIntersections(null, 5, new Point2d(3, 4), 2);
849             fail("null for center1 should have thrown a NullPointerException");
850         }
851         catch (NullPointerException npe)
852         {
853             // Ignore expected exception
854         }
855 
856         try
857         {
858             Point2d.circleIntersections(new Point2d(3, 4), 5, null, 2);
859             fail("null for center1 should have thrown a NullPointerException");
860         }
861         catch (NullPointerException npe)
862         {
863             // Ignore expected exception
864         }
865     }
866 
867     /**
868      * Test the direction method.
869      */
870     @Test
871     public void testDirection()
872     {
873         Point2d reference = new Point2d(5, 8);
874         assertEquals("East", 0, reference.directionTo(new Point2d(reference.x + 10, reference.y)), 0);
875         assertEquals("North", Math.PI / 2, reference.directionTo(new Point2d(reference.x, reference.y + 5)), 0.00001);
876         assertEquals("NorthEast", Math.PI / 4, reference.directionTo(new Point2d(reference.x + 2, reference.y + 2)), 0.00001);
877         assertEquals("West", Math.PI, reference.directionTo(new Point2d(reference.x - 1, reference.y)), 0);
878         assertEquals("South", -Math.PI / 2, reference.directionTo(new Point2d(reference.x, reference.y - 0.5)), 0.00001);
879         assertEquals("SouthWst", -3 * Math.PI / 4, reference.directionTo(new Point2d(reference.x - 0.2, reference.y - 0.2)),
880                 0.00001);
881     }
882 
883 }