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