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-2023 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         assertEquals("Intersection is at (2,2)", new Point2d(2, 2),
355                 Point2d.intersectionOfLineSegments(new LineSegment2d(1, 1, 6, 6), new LineSegment2d(4, 2, -2, 2)));
356         // Check all four ways that two non-parallel lines can miss each other
357         assertNull("line two passes before start of line one", Point2d.intersectionOfLineSegments(new Point2d(1, 1),
358                 new Point2d(5, 5), new Point2d(0, -3), new Point2d(10, 0)));
359         assertNull("line two passes before after end of line one", Point2d.intersectionOfLineSegments(new Point2d(1, 1),
360                 new Point2d(5, 5), new Point2d(0, 20), new Point2d(100, 30)));
361         assertNull("line one passes before start of line two", Point2d.intersectionOfLineSegments(new Point2d(1, 1),
362                 new Point2d(5, 5), new Point2d(5, 3), new Point2d(10, 2)));
363         assertNull("line one passes after end of line two", Point2d.intersectionOfLineSegments(new Point2d(1, 1),
364                 new Point2d(5, 5), new Point2d(-10, 3), new Point2d(0, 2)));
365         assertNull("line two passes before start of line one", Point2d.intersectionOfLineSegments(1, 1, 5, 5, 0, -3, 10, 0));
366         assertNull("line two passes before start of line one",
367                 Point2d.intersectionOfLineSegments(new LineSegment2d(1, 15, 5, 5), new LineSegment2d(0, -3, 10, 0)));
368         assertNull("line two passes before after end of line one",
369                 Point2d.intersectionOfLineSegments(1, 1, 5, 5, 0, 20, 100, 30));
370         assertNull("line one passes before start of line two", Point2d.intersectionOfLineSegments(1, 1, 5, 5, 5, 3, 10, 2));
371         assertNull("line one passes before start of line two",
372                 Point2d.intersectionOfLineSegments(new LineSegment2d(1, 1, 5, 5), new LineSegment2d(5, 3, 10, 2)));
373         assertNull("line one passes after end of line two", Point2d.intersectionOfLineSegments(1, 1, 5, 5, -10, 3, 0, 2));
374         assertNull("line one passes after end of line two",
375                 Point2d.intersectionOfLineSegments(new LineSegment2d(1, 1, 5, 5), new LineSegment2d(-10, 3, 0, 2)));
376 
377         Point2d line1P1 = new Point2d(1, 2);
378         Point2d line1P2 = new Point2d(3, 2);
379         Point2d line2P1 = new Point2d(2, 0);
380         Point2d line2P2 = new Point2d(2, 4);
381         try
382         {
383             Point2d.intersectionOfLines(null, line1P2, line2P1, line2P2);
384             fail("Null parameter should have thrown a NullPointerException");
385         }
386         catch (NullPointerException npe)
387         {
388             // Ignore expected exception
389         }
390 
391         try
392         {
393             Point2d.intersectionOfLines(line1P1, null, line2P1, line2P2);
394             fail("Null parameter should have thrown a NullPointerException");
395         }
396         catch (NullPointerException npe)
397         {
398             // Ignore expected exception
399         }
400 
401         try
402         {
403             Point2d.intersectionOfLines(line1P1, line1P2, null, line2P2);
404             fail("Null parameter should have thrown a NullPointerException");
405         }
406         catch (NullPointerException npe)
407         {
408             // Ignore expected exception
409         }
410 
411         try
412         {
413             Point2d.intersectionOfLines(line1P1, line1P2, line2P1, null);
414             fail("Null parameter should have thrown a NullPointerException");
415         }
416         catch (NullPointerException npe)
417         {
418             // Ignore expected exception
419         }
420 
421         try
422         {
423             Point2d.intersectionOfLineSegments(null, line1P2, line2P1, line2P2);
424             fail("Null parameter should have thrown a NullPointerException");
425         }
426         catch (NullPointerException npe)
427         {
428             // Ignore expected exception
429         }
430 
431         try
432         {
433             Point2d.intersectionOfLineSegments(line1P1, null, line2P1, line2P2);
434             fail("Null parameter should have thrown a NullPointerException");
435         }
436         catch (NullPointerException npe)
437         {
438             // Ignore expected exception
439         }
440 
441         try
442         {
443             Point2d.intersectionOfLineSegments(line1P1, line1P2, null, line2P2);
444             fail("Null parameter should have thrown a NullPointerException");
445         }
446         catch (NullPointerException npe)
447         {
448             // Ignore expected exception
449         }
450 
451         try
452         {
453             Point2d.intersectionOfLineSegments(line1P1, line1P2, line2P1, null);
454             fail("Null parameter should have thrown a NullPointerException");
455         }
456         catch (NullPointerException npe)
457         {
458             // Ignore expected exception
459         }
460 
461     }
462 
463     /**
464      * Test the intersectionOfLines method.
465      */
466     @Test
467     public void testIntersectionOfLines()
468     {
469         assertNull("horizontal line intersection with itself returns null",
470                 Point2d.intersectionOfLines(new Point2d(1, 2), new Point2d(4, 2), new Point2d(1, 2), new Point2d(4, 2)));
471         assertNull("horizontal line intersection with itself returns null",
472                 Point2d.intersectionOfLines(1, 2, 4, 2, 1, 2, 4, 2));
473         assertNull("vertical line intersection with itself returns null", Point2d.intersectionOfLineSegments(new Point2d(1, 2),
474                 new Point2d(1, 10), new Point2d(1, 2), new Point2d(1, 10)));
475         assertNull("vertical line intersection with itself returns null",
476                 Point2d.intersectionOfLineSegments(1, 2, 1, 10, 1, 2, 1, 10));
477         assertNull("vertical line intersection with itself returns null",
478                 Point2d.intersectionOfLineSegments(new LineSegment2d(1, 2, 1, 10), new LineSegment2d(1, 2, 1, 10)));
479         assertEquals("Intersection is at (2,2)", new Point2d(2, 2),
480                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(6, 6), new Point2d(4, 2), new Point2d(-2, 2)));
481         // Check all four ways that two non-parallel lines can miss each other
482         assertEquals("line two passes before start of line one", new Point2d(-1.5, -1.5),
483                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(0, -3), new Point2d(10, -13)));
484         assertEquals("line two passes before after end of line one", new Point2d(20, 20),
485                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(0, 20), new Point2d(100, 20)));
486         assertEquals("line one passes before start of line two", new Point2d(4, 4),
487                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(7, 1), new Point2d(10, -2)));
488         assertEquals("line one passes after end of line two", new Point2d(-3.5, -3.5),
489                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(5, 5), new Point2d(-10, 3), new Point2d(0, -7)));
490         // Test the various exact hits at begin or end point
491         assertEquals("begin of first is on second", new Point2d(1, 1),
492                 Point2d.intersectionOfLines(new Point2d(1, 1), new Point2d(2, 1), new Point2d(1, 0), new Point2d(1, 3)));
493         assertEquals("end of first is on second", new Point2d(1, 1),
494                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(1, 1), new Point2d(1, 0), new Point2d(1, 3)));
495         assertEquals("begin of second is on first", new Point2d(1, 1),
496                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, 1), new Point2d(1, 3)));
497         assertEquals("end of second is on first", new Point2d(1, 1),
498                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, -1), new Point2d(1, 1)));
499         // Test the various not quite exact hits at begin or end point
500         assertTrue("begin of first is just over second", new Point2d(1, 1).epsilonEquals(
501                 Point2d.intersectionOfLines(new Point2d(1.001, 1), new Point2d(2, 1), new Point2d(1, 0), new Point2d(1, 3)),
502                 0.0001));
503         assertTrue("end of first is just over second", new Point2d(1, 1).epsilonEquals(
504                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(0.999, 1), new Point2d(1, 0), new Point2d(1, 3)),
505                 0.0001));
506         assertTrue("begin of second is just over first", new Point2d(1, 1).epsilonEquals(
507                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, 1.001), new Point2d(1, 3)),
508                 0.0001));
509         assertTrue("end of second is just over first", new Point2d(1, 1).epsilonEquals(
510                 Point2d.intersectionOfLines(new Point2d(-1, 1), new Point2d(2, 1), new Point2d(1, -1), new Point2d(1, 0.999)),
511                 0.0001));
512         // Test the various close hits at begin or end point
513         assertNull("begin of first is just not on second", Point2d.intersectionOfLineSegments(new Point2d(1.001, 1),
514                 new Point2d(2, 1), new Point2d(1, 0), new Point2d(1, 3)));
515         assertNull("end of first is just not on second", Point2d.intersectionOfLineSegments(new Point2d(-1, 1),
516                 new Point2d(0.999, 1), new Point2d(1, 0), new Point2d(1, 3)));
517         assertNull("begin of second is just not on first", Point2d.intersectionOfLineSegments(new Point2d(-1, 1),
518                 new Point2d(2, 1), new Point2d(1, 1.001), new Point2d(1, 3)));
519         assertNull("end of second is just not on first", Point2d.intersectionOfLineSegments(new Point2d(-1, 1),
520                 new Point2d(2, 1), new Point2d(1, -1), new Point2d(1, 0.999)));
521     }
522 
523     /**
524      * Test the closestPointOnSegment and the closestPointOnLine methods.
525      * @throws DrawRuntimeException if that happens uncaught; this test has failed
526      */
527     @Test
528     public void testClosestPointOnSegmentAndLine() throws DrawRuntimeException
529     {
530         Point2d p1 = new Point2d(-2, 3);
531         for (Point2d p2 : new Point2d[] {new Point2d(7, 4)/* angled */, new Point2d(-3, 6) /* also angled */,
532                 new Point2d(-2, -5) /* vertical */, new Point2d(8, 3)/* horizontal */ })
533         {
534             PolyLine2d line = new PolyLine2d(p1, p2);
535             for (double x = -10; x <= 10; x += 0.5)
536             {
537                 for (double y = -10; y <= 10; y += 0.5)
538                 {
539                     Point2d p = new Point2d(x, y);
540                     // Figure out the correct result using a totally different method (binary search over the line segment)
541                     double fraction = 0.5;
542                     double step = 0.25;
543                     Point2d approximation = line.getLocationFraction(fraction);
544                     double distance = approximation.distance(p);
545                     // 10 iterations should get us to within one thousandth
546                     for (int iteration = 0; iteration < 10; iteration++)
547                     {
548                         // Try stepping up
549                         double upFraction = fraction + step;
550                         Point2d upApproximation = line.getLocationFraction(upFraction);
551                         double upDistance = upApproximation.distance(p);
552                         if (upDistance < distance)
553                         {
554                             distance = upDistance;
555                             fraction = upFraction;
556                             approximation = upApproximation;
557                         }
558                         else
559                         {
560                             // Try stepping down
561                             double downFraction = fraction - step;
562                             Point2d downApproximation = line.getLocationFraction(downFraction);
563                             double downDistance = downApproximation.distance(p);
564                             if (downDistance < distance)
565                             {
566                                 distance = downDistance;
567                                 fraction = downFraction;
568                                 approximation = downApproximation;
569                             }
570                         }
571                         step /= 2;
572                     }
573                     Point2d result = p.closestPointOnSegment(p1, p2);
574                     assertEquals("distance should be less than one thousandth of line length", 0,
575                             approximation.distance(result), line.getLength() / 1000);
576                     assertEquals("zero length line segment should always return start point", p1,
577                             p.closestPointOnSegment(p1, p1));
578                     result = p.closestPointOnSegment(p1.x, p1.y, p2.x, p2.y);
579                     assertEquals("distance should be less than one thousandth of line length", 0,
580                             approximation.distance(result), line.getLength() / 1000);
581 
582                     if (fraction > 0.001 && fraction < 0.999)
583                     {
584                         result = p.closestPointOnLine(p1, p2);
585                         assertEquals("distance should be less than one thousandth of line length", 0,
586                                 approximation.distance(result), line.getLength() / 1000);
587                         result = p.closestPointOnLine(p1, p2);
588                         assertEquals("distance should be less than one thousandth of line length", 0,
589                                 approximation.distance(result), line.getLength() / 1000);
590                         result = p.closestPointOnLine(p1.x, p1.y, p2.x, p2.y);
591                         assertEquals("distance should be less than one thousandth of line length", 0,
592                                 approximation.distance(result), line.getLength() / 1000);
593                     }
594                     else
595                     {
596                         // extrapolating
597                         double range = Math.max(Math.max(line.getLength(), p.distance(p1)), p.distance(p2));
598                         step = 5.0;
599                         fraction = 0.5;
600                         distance = range;
601                         // 20 iterations should get us to within one thousandth
602                         for (int iteration = 0; iteration < 20; iteration++)
603                         {
604                             // Try stepping up
605                             double upFraction = fraction + step;
606                             Point2d upApproximation = line.getLocationFractionExtended(upFraction);
607                             double upDistance = upApproximation.distance(p);
608                             if (upDistance < distance)
609                             {
610                                 distance = upDistance;
611                                 fraction = upFraction;
612                                 approximation = upApproximation;
613                             }
614                             else
615                             {
616                                 // Try stepping down
617                                 double downFraction = fraction - step;
618                                 Point2d downApproximation = line.getLocationFractionExtended(downFraction);
619                                 double downDistance = downApproximation.distance(p);
620                                 if (downDistance < distance)
621                                 {
622                                     distance = downDistance;
623                                     fraction = downFraction;
624                                     approximation = downApproximation;
625                                 }
626                             }
627                             step /= 2;
628                         }
629                         result = p.closestPointOnLine(p1, p2);
630                         assertEquals("distance should be less than one thousandth of range", 0, approximation.distance(result),
631                                 range / 1000);
632                         result = p.closestPointOnLine(p1, p2);
633                         assertEquals("distance should be less than one thousandth of range", 0, approximation.distance(result),
634                                 range / 1000);
635                         result = p.closestPointOnLine(p1.x, p1.y, p2.x, p2.y);
636                         assertEquals("distance should be less than one thousandth of range", 0, approximation.distance(result),
637                                 range / 1000);
638                         if (fraction < -0.001 || fraction > 1.001)
639                         {
640                             assertNull("projectOrthogonal should return null", new LineSegment2d(p1, p2).projectOrthogonal(p));
641                             assertEquals("projectOrthogonalExtended should return same result as closestPointOnLine", result,
642                                     new LineSegment2d(p1, p2).projectOrthogonalExtended(p));
643                         }
644                     }
645                 }
646             }
647         }
648 
649         try
650         {
651             p1.closestPointOnLine(null, new Point2d(5, 6));
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.closestPointOnLine(new Point2d(5, 6), null);
662             fail("null should have thrown a NullPointerException");
663         }
664         catch (NullPointerException npe)
665         {
666             // Ignore expected exception
667         }
668 
669         try
670         {
671             p1.closestPointOnSegment(Double.NaN, 7, 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, Double.NaN, 8, 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, Double.NaN, 9);
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.closestPointOnSegment(6, 7, 8, Double.NaN);
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(Double.NaN, 7, 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, Double.NaN, 8, 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, Double.NaN, 9);
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, 8, Double.NaN);
742             fail("NaN value should have thrown a DrawRuntimeException");
743         }
744         catch (DrawRuntimeException dre)
745         {
746             // Ignore expected exception
747         }
748 
749         try
750         {
751             p1.closestPointOnLine(6, 7, 6, 7);
752             fail("identical points should have thrown a DrawRuntimeException");
753         }
754         catch (DrawRuntimeException dre)
755         {
756             // Ignore expected exception
757         }
758 
759     }
760 
761     /**
762      * Test the circleIntersection method.
763      */
764     @Test
765     public void circleIntersectionTest()
766     {
767         for (int x1 = -5; x1 <= 5; x1++)
768         {
769             for (int y1 = -5; y1 <= 5; y1++)
770             {
771                 Point2d p1 = new Point2d(x1, y1);
772                 for (int r1 = 0; r1 < 5; r1++)
773                 {
774                     for (int x2 = -5; x2 <= 5; x2++)
775                     {
776                         for (int y2 = -5; y2 <= 5; y2++)
777                         {
778                             Point2d p2 = new Point2d(x2, y2);
779                             double distance = p1.distance(p2);
780                             for (int r2 = 0; r2 < 5; r2++)
781                             {
782                                 if (x1 == x2 && y1 == y2 && r1 == r2)
783                                 {
784                                     try
785                                     {
786                                         Point2d.circleIntersections(p1, r1, p2, r2);
787                                         fail("Identical circles should have thrown a DrawRuntimeException");
788                                     }
789                                     catch (DrawRuntimeException dre)
790                                     {
791                                         // Ignore expected exception
792                                     }
793                                 }
794                                 else
795                                 {
796                                     List<Point2d> result = Point2d.circleIntersections(p1, r1, p2, r2);
797                                     // System.out.print("p1=" + p1 + ", r1=" + r1 + ", p2=" + p2 + " r2=" + r2 + ", result=");
798                                     // for (Point2d p : result)
799                                     // {
800                                     // System.out
801                                     // .print(String.format("%s d1=%.3f d2=%.3f ", p, p.distance(p1), p.distance(p2)));
802                                     // }
803                                     // System.out.println("");
804                                     if (distance > r1 + r2 + 0.0001)
805                                     {
806                                         if (result.size() > 0)
807                                         {
808                                             Point2d.circleIntersections(p1, r1, p2, r2);
809                                         }
810                                         assertEquals("There are 0 intersections", 0, result.size());
811                                     }
812                                     if (distance < r1 + r2 - 0.0001 && distance > Math.abs(r2 - r1) + 0.0001)
813                                     {
814                                         if (result.size() != 2)
815                                         {
816                                             Point2d.circleIntersections(p1, r1, p2, r2);
817                                         }
818                                         assertEquals("There are 2 intersections", 2, result.size());
819                                     }
820                                     for (Point2d p : result)
821                                     {
822                                         if (Math.abs(r1 - p.distance(p1)) > 0.1 || Math.abs(r2 - p.distance(p2)) > 0.1)
823                                         {
824                                             Point2d.circleIntersections(p1, r1, p2, r2);
825                                         }
826                                         assertEquals("result is at r1 from p1", r1, p.distance(p1), 0.0001);
827                                         assertEquals("result is at r2 from p2", r2, p.distance(p2), 0.0001);
828                                     }
829                                 }
830                             }
831                         }
832                     }
833                 }
834             }
835         }
836         try
837         {
838             Point2d.circleIntersections(new Point2d(1, 2), -1, 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(new Point2d(1, 2), 5, new Point2d(3, 4), -2);
849             fail("negative radius should have thrown a DrawRuntimeException");
850         }
851         catch (DrawRuntimeException dre)
852         {
853             // Ignore expected exception
854         }
855 
856         try
857         {
858             Point2d.circleIntersections(null, 5, new Point2d(3, 4), 2);
859             fail("null for center1 should have thrown a NullPointerException");
860         }
861         catch (NullPointerException npe)
862         {
863             // Ignore expected exception
864         }
865 
866         try
867         {
868             Point2d.circleIntersections(new Point2d(3, 4), 5, null, 2);
869             fail("null for center1 should have thrown a NullPointerException");
870         }
871         catch (NullPointerException npe)
872         {
873             // Ignore expected exception
874         }
875     }
876 
877     /**
878      * Test the direction method.
879      */
880     @Test
881     public void testDirection()
882     {
883         Point2d reference = new Point2d(5, 8);
884         assertEquals("East", 0, reference.directionTo(new Point2d(reference.x + 10, reference.y)), 0);
885         assertEquals("North", Math.PI / 2, reference.directionTo(new Point2d(reference.x, reference.y + 5)), 0.00001);
886         assertEquals("NorthEast", Math.PI / 4, reference.directionTo(new Point2d(reference.x + 2, reference.y + 2)), 0.00001);
887         assertEquals("West", Math.PI, reference.directionTo(new Point2d(reference.x - 1, reference.y)), 0);
888         assertEquals("South", -Math.PI / 2, reference.directionTo(new Point2d(reference.x, reference.y - 0.5)), 0.00001);
889         assertEquals("SouthWst", -3 * Math.PI / 4, reference.directionTo(new Point2d(reference.x - 0.2, reference.y - 0.2)),
890                 0.00001);
891     }
892 
893 }