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