View Javadoc
1   package org.djutils.draw.point;
2   
3   import java.util.ArrayList;
4   import java.util.Arrays;
5   import java.util.Iterator;
6   import java.util.List;
7   import java.util.Locale;
8   
9   import org.djutils.draw.DrawRuntimeException;
10  import org.djutils.draw.Drawable2d;
11  import org.djutils.draw.bounds.Bounds2d;
12  import org.djutils.draw.line.LineSegment2d;
13  import org.djutils.exceptions.Throw;
14  
15  /**
16   * A Point2d is an immutable Point with an x and y coordinate, stored with double precision. It differs from many Point
17   * implementations by being immutable.
18   * <p>
19   * Copyright (c) 2020-2025 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
20   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
21   * </p>
22   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
23   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
24   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
25   */
26  public class Point2d implements Drawable2d, Point<Point2d>
27  {
28      /** */
29      private static final long serialVersionUID = 20201201L;
30  
31      /** The x-coordinate. */
32      @SuppressWarnings("checkstyle:visibilitymodifier")
33      public final double x;
34  
35      /** The y-coordinate. */
36      @SuppressWarnings("checkstyle:visibilitymodifier")
37      public final double y;
38  
39      /**
40       * Create a new Point2d from x and y coordinates provided as double arguments.
41       * @param x the x coordinate
42       * @param y the y coordinate
43       * @throws IllegalArgumentException when <code>x</code>, or <code>y</code> is <code>NaN</code>
44       */
45      public Point2d(final double x, final double y)
46      {
47          Throw.whenNaN(x, "x");
48          Throw.whenNaN(y, "y");
49          this.x = x;
50          this.y = y;
51      }
52  
53      /**
54       * Create a new Point2d from a x and y coordinates provided as values in a double array.
55       * @param xy the x and y coordinates
56       * @throws NullPointerException when <code>xy</code> is <code>null</code>
57       * @throws IllegalArgumentException when the length of <code>xy</code> is not 2
58       * @throws ArithmeticException when <code>xy</code> contains a <code>NaN</code> value
59       */
60      public Point2d(final double[] xy)
61      {
62          this(checkLengthIsTwo(Throw.whenNull(xy, "xy"))[0], xy[1]);
63      }
64  
65      /**
66       * Create an new Point2d from x and y obtained from a java.awt.geom.Point2D.
67       * @param point a java.awt.geom.Point2D
68       * @throws NullPointerException when <code>point</code> is <code>null</code>
69       * @throws IllegalArgumentException when <code>point</code> has a <code>NaN</code> coordinate
70       */
71      public Point2d(final java.awt.geom.Point2D point) throws NullPointerException, IllegalArgumentException
72      {
73          Throw.whenNull(point, "point");
74          Throw.whenNaN(point.getX(), "point.getX()");
75          Throw.whenNaN(point.getY(), "point.getY()");
76          this.x = point.getX();
77          this.y = point.getY();
78      }
79  
80      /**
81       * Throw an IllegalArgumentException if the length of the provided array is not two.
82       * @param xy the provided array
83       * @return the provided array
84       * @throws IllegalArgumentException when length of <code>xy</code> is not two
85       */
86      private static double[] checkLengthIsTwo(final double[] xy)
87      {
88          Throw.when(xy.length != 2, IllegalArgumentException.class, "Length of xy-array must be 2");
89          return xy;
90      }
91  
92      @Override
93      public final double getX()
94      {
95          return this.x;
96      }
97  
98      @Override
99      public final double getY()
100     {
101         return this.y;
102     }
103 
104     @Override
105     public double distance(final Point2d otherPoint)
106     {
107         Throw.whenNull(otherPoint, "otherPoint");
108         return Math.hypot(otherPoint.x - this.x, otherPoint.y - this.y);
109     }
110 
111     @Override
112     public double distanceSquared(final Point2d otherPoint) throws NullPointerException
113     {
114         Throw.whenNull(otherPoint, "otherPoint");
115         double dx = this.x - otherPoint.x;
116         double dy = this.y - otherPoint.y;
117         return dx * dx + dy * dy;
118     }
119 
120     @Override
121     public int size()
122     {
123         return 1;
124     }
125 
126     @Override
127     public Iterator<Point2d> iterator()
128     {
129         return Arrays.stream(new Point2d[] {this}).iterator();
130     }
131 
132     /**
133      * Return a new Point2d with a translation by the provided dx and dy.
134      * @param dX the x translation
135      * @param dY the y translation
136      * @return a new point with the translated coordinates
137      * @throws IllegalArgumentException when <code>dX</code>, or <code>dY</code> is <code>NaN</code>
138      */
139     public Point2d translate(final double dX, final double dY)
140     {
141         Throw.whenNaN(dX, "dX");
142         Throw.whenNaN(dY, "dY");
143         return new Point2d(this.x + dX, this.y + dY);
144     }
145 
146     /**
147      * Return a new Point3d with a translation by the provided dx, dy and dz. If this is an OrientedPoint2d, then the result is
148      * an OrientedPoint3d with rotX copied from this and rotY and rotZ are set to 0.0.
149      * @param dX the x translation
150      * @param dY the y translation
151      * @param dZ the z translation
152      * @return a new point with the translated coordinates
153      * @throws IllegalArgumentException when <code>dX</code>, <code>dY</code>, or <code>dZ</code> is <code>NaN</code>
154      */
155     public Point3d translate(final double dX, final double dY, final double dZ)
156     {
157         Throw.whenNaN(dX, "dX");
158         Throw.whenNaN(dY, "dY");
159         Throw.whenNaN(dZ, "dZ");
160         return new Point3d(this.x + dX, this.y + dY, dZ);
161     }
162 
163     @Override
164     public Point2d scale(final double factor)
165     {
166         Throw.whenNaN(factor, "factor");
167         return new Point2d(this.x * factor, this.y * factor);
168     }
169 
170     @Override
171     public Point2d neg()
172     {
173         return scale(-1.0);
174     }
175 
176     @Override
177     public Point2d abs()
178     {
179         return new Point2d(Math.abs(this.x), Math.abs(this.y));
180     }
181 
182     @Override
183     public Point2d normalize() throws DrawRuntimeException
184     {
185         double length = Math.sqrt(this.x * this.x + this.y * this.y);
186         Throw.when(length == 0.0, DrawRuntimeException.class, "cannot normalize (0.0, 0.0)");
187         return this.scale(1.0 / length);
188     }
189 
190     @Override
191     public Point2d interpolate(final Point2d otherPoint, final double fraction)
192     {
193         Throw.whenNull(otherPoint, "otherPoint");
194         Throw.whenNaN(fraction, "fraction");
195         return new Point2d((1.0 - fraction) * this.x + fraction * otherPoint.x,
196                 (1.0 - fraction) * this.y + fraction * otherPoint.y);
197     }
198 
199     @Override
200     public boolean epsilonEquals(final Point2d otherPoint, final double epsilon)
201     {
202         Throw.whenNull(otherPoint, "otherPoint");
203         Throw.whenNaN(epsilon, "epsilon");
204         Throw.when(epsilon < 0.0, IllegalArgumentException.class, "epsilon may not be negative");
205         if (Math.abs(this.x - otherPoint.x) > epsilon)
206         {
207             return false;
208         }
209         if (Math.abs(this.y - otherPoint.y) > epsilon)
210         {
211             return false;
212         }
213         return true;
214     }
215 
216     @Override
217     public Bounds2d getBounds()
218     {
219         return new Bounds2d(this);
220     }
221 
222     /**
223      * Compute the 2D intersection of two lines. Both lines are defined by two points (that should be distinct).
224      * @param line1P1X x-coordinate of start point of line 1
225      * @param line1P1Y y-coordinate of start point of line 1
226      * @param line1P2X x-coordinate of end point of line 1
227      * @param line1P2Y y-coordinate of end point of line 1
228      * @param lowLimitLine1 if<code>true</code>; the intersection may not lie before the start point of line 1
229      * @param highLimitLine1 if<code>true</code>; the intersection may not lie beyond the end point of line 1
230      * @param line2P1X x-coordinate of start point of line 2
231      * @param line2P1Y y-coordinate of start point of line 2
232      * @param line2P2X x-coordinate of end point of line 2
233      * @param line2P2Y y-coordinate of end point of line 2
234      * @param lowLimitLine2 if<code>true</code>; the intersection may not lie before the start point of line 2
235      * @param highLimitLine2 if<code>true</code>; the intersection may not lie beyond the end point of line 2
236      * @return the intersection of the two lines, or <code>null</code> if the lines are (almost) parallel, or the
237      *         intersection point lies outside the permitted range
238      * @throws ArithmeticException when any of the parameters is <code>NaN</code>
239      */
240     @SuppressWarnings("checkstyle:parameternumber")
241     public static Point2d intersectionOfLines(final double line1P1X, final double line1P1Y, final double line1P2X,
242             final double line1P2Y, final boolean lowLimitLine1, final boolean highLimitLine1, final double line2P1X,
243             final double line2P1Y, final double line2P2X, final double line2P2Y, final boolean lowLimitLine2,
244             final boolean highLimitLine2)
245     {
246         double line1DX = line1P2X - line1P1X;
247         double line1DY = line1P2Y - line1P1Y;
248         double l2p1x = line2P1X - line1P1X;
249         double l2p1y = line2P1Y - line1P1Y;
250         double l2p2x = line2P2X - line1P1X;
251         double l2p2y = line2P2Y - line1P1Y;
252         double denominator = (l2p2y - l2p1y) * line1DX - (l2p2x - l2p1x) * line1DY;
253         Throw.whenNaN(denominator, "none of the parameters may be NaN");
254         if (denominator == 0.0)
255         {
256             return null; // lines are parallel (they might even be on top of each other, but we don't check that)
257         }
258         double uA = ((l2p2x - l2p1x) * (-l2p1y) - (l2p2y - l2p1y) * (-l2p1x)) / denominator;
259         // System.out.println("uA is " + uA);
260         if (uA < 0.0 && lowLimitLine1 || uA > 1.0 && highLimitLine1)
261         {
262             return null; // intersection outside line 1
263         }
264         double uB = (line1DY * l2p1x - line1DX * l2p1y) / denominator;
265         // System.out.println("uB is " + uB);
266         if (uB < 0.0 && lowLimitLine2 || uB > 1.0 && highLimitLine2)
267         {
268             return null; // intersection outside line 2
269         }
270         if (uA == 1.0) // maximize precision
271         {
272             return new Point2d(line1P2X, line1P2Y);
273         }
274         if (uB == 0.0)
275         {
276             return new Point2d(line2P1X, line2P1Y);
277         }
278         if (uB == 1.0)
279         {
280             return new Point2d(line2P2X, line2P2Y);
281         }
282         return new Point2d(line1P1X + uA * line1DX, line1P1Y + uA * line1DY);
283     }
284 
285     /**
286      * Compute the 2D intersection of two lines. Both lines are defined by two points (that should be distinct). The lines are
287      * considered to be infinitely long; so unless the lines are parallel; there is an intersection.
288      * @param l1P1X x-coordinate of start point of line segment 1
289      * @param l1P1Y y-coordinate of start point of line segment 1
290      * @param l1P2X x-coordinate of end point of line segment 1
291      * @param l1P2Y y-coordinate of end point of line segment 1
292      * @param l2P1X x-coordinate of start point of line segment 2
293      * @param l2P1Y y-coordinate of start point of line segment 2
294      * @param l2P2X x-coordinate of end point of line segment 2
295      * @param l2P2Y y-coordinate of end point of line segment 2
296      * @return the intersection of the two lines, or <code>null</code> if the lines are (almost) parallel
297      * @throws ArithmeticException when any of the parameters is <code>NaN</code>
298      */
299     @SuppressWarnings("checkstyle:parameternumber")
300     public static Point2d intersectionOfLines(final double l1P1X, final double l1P1Y, final double l1P2X, final double l1P2Y,
301             final double l2P1X, final double l2P1Y, final double l2P2X, final double l2P2Y)
302     {
303         return intersectionOfLines(l1P1X, l1P1Y, l1P2X, l1P2Y, false, false, l2P1X, l2P1Y, l2P2X, l2P2Y, false, false);
304     }
305 
306     /**
307      * Compute the 2D intersection of two lines. Both lines are defined by two points (that should be distinct). The lines are
308      * considered to be infinitely long; so unless the lines are parallel; there is an intersection.
309      * @param line1P1 first point of line 1
310      * @param line1P2 second point of line 1
311      * @param line2P1 first point of line 2
312      * @param line2P2 second point of line 2
313      * @return the intersection of the two lines, or <code>null</code> if the lines are (almost) parallel
314      * @throws NullPointerException when any of the points is <code>null</code>
315      */
316     public static Point2d intersectionOfLines(final Point2d line1P1, final Point2d line1P2, final Point2d line2P1,
317             final Point2d line2P2)
318     {
319         Throw.whenNull(line1P1, "line1P1");
320         Throw.whenNull(line1P2, "line1P2");
321         Throw.whenNull(line2P1, "line2P1");
322         Throw.whenNull(line2P2, "line2P2");
323         return intersectionOfLines(line1P1.x, line1P1.y, line1P2.x, line1P2.y, false, false, line2P1.x, line2P1.y, line2P2.x,
324                 line2P2.y, false, false);
325     }
326 
327     /**
328      * Compute the 2D intersection of two line segments. Both line segments are defined by two points (that should be distinct).
329      * @param line1P1 first point of line segment 1
330      * @param line1P2 second point of line segment 1
331      * @param line2P1 first point of line segment 2
332      * @param line2P2 second point of line segment 2
333      * @return the intersection of the two line segments, or <code>null</code> if the lines are parallel (within
334      *         rounding error), or do not intersect
335      * @throws NullPointerException when any of the points is <code>null</code>
336      * @throws DrawRuntimeException when any of the line segments is ill-defined (begin point equals end point), or the two line
337      *             segments are parallel or overlapping
338      */
339     public static Point2d intersectionOfLineSegments(final Point2d line1P1, final Point2d line1P2, final Point2d line2P1,
340             final Point2d line2P2)
341     {
342         Throw.whenNull(line1P1, "line1P1");
343         Throw.whenNull(line1P2, "line1P2");
344         Throw.whenNull(line2P1, "line2P1");
345         Throw.whenNull(line2P2, "line2P2");
346         return intersectionOfLines(line1P1.x, line1P1.y, line1P2.x, line1P2.y, true, true, line2P1.x, line2P1.y, line2P2.x,
347                 line2P2.y, true, true);
348     }
349 
350     /**
351      * Compute the 2D intersection of two line segments. Both line segments are defined by two points (that should be distinct).
352      * @param line1P1X x coordinate of start point of first line segment
353      * @param line1P1Y y coordinate of start point of first line segment
354      * @param line1P2X x coordinate of end point of first line segment
355      * @param line1P2Y y coordinate of end point of first line segment
356      * @param line2P1X x coordinate of start point of second line segment
357      * @param line2P1Y y coordinate of start point of second line segment
358      * @param line2P2X x coordinate of end point of second line segment
359      * @param line2P2Y y coordinate of end point of second line segment
360      * @return the intersection of the two line segments, or <code>null</code> if the lines are parallel (within
361      *         rounding error), or do not intersect
362      * @throws ArithmeticException when any of the values is <code>NaN</code>
363      */
364     @SuppressWarnings("checkstyle:parameternumber")
365     public static Point2d intersectionOfLineSegments(final double line1P1X, final double line1P1Y, final double line1P2X,
366             final double line1P2Y, final double line2P1X, final double line2P1Y, final double line2P2X, final double line2P2Y)
367     {
368         return intersectionOfLines(line1P1X, line1P1Y, line1P2X, line1P2Y, true, true, line2P1X, line2P1Y, line2P2X, line2P2Y,
369                 true, true);
370     }
371 
372     /**
373      * Compute the 2D intersection of two line segments.
374      * @param segment1 the first line segment
375      * @param segment2 the other line segment
376      * @return the intersection of the line segments, or <code>null</code> if the line segments do not intersect
377      */
378     public static Point2d intersectionOfLineSegments(final LineSegment2d segment1, final LineSegment2d segment2)
379     {
380         return intersectionOfLineSegments(segment1.startX, segment1.startY, segment1.endX, segment1.endY, segment2.startX,
381                 segment2.startY, segment2.endX, segment2.endY);
382     }
383 
384     @Override
385     public Point2d closestPointOnSegment(final Point2d segmentPoint1, final Point2d segmentPoint2)
386     {
387         Throw.whenNull(segmentPoint1, "segmentPoint1");
388         Throw.whenNull(segmentPoint2, "segmentPoint2");
389         return closestPointOnSegment(segmentPoint1.x, segmentPoint1.y, segmentPoint2.x, segmentPoint2.y);
390     }
391 
392     /**
393      * Compute the closest point on a line with optional limiting of the result on either end.
394      * @param p1X the x coordinate of the first point on the line
395      * @param p1Y the y coordinate of the first point on the line
396      * @param p2X the x coordinate of the second point on the line
397      * @param p2Y the y coordinate of the second point on the line
398      * @param lowLimitHandling controls handling of results that lie before the first point of the line. If
399      *            <code>null</code>; this method returns <code>null</code>; else if <code>true</code>; this method returns
400      *            (p1X,p1Y); else (lowLimitHandling is <code>false</code>); this method will return the closest point on the
401      *            line
402      * @param highLimitHandling controls the handling of results that lie beyond the second point of the line. If
403      *            <code>null</code>; this method returns <code>null</code>; else if <code>true</code>; this method returns
404      *            (p2X,p2Y); else (highLimitHandling is <code>false</code>); this method will return the closest point on the
405      *            line
406      * @return the closest point on the line after applying the indicated limit handling; so the result can be
407      *         <code>null</code>
408      * @throws ArithmeticException when any of the arguments is <code>NaN</code>
409      * @throws DrawRuntimeException when the line is ill-defined (begin point coincides with end point)
410      */
411     public Point2d closestPointOnLine(final double p1X, final double p1Y, final double p2X, final double p2Y,
412             final Boolean lowLimitHandling, final Boolean highLimitHandling)
413     {
414         double fraction = fractionalPositionOnLine(p1X, p1Y, p2X, p2Y, lowLimitHandling, highLimitHandling);
415         if (Double.isNaN(fraction))
416         {
417             return null;
418         }
419         if (fraction == 1.0)
420         {
421             return new Point2d(p2X, p2Y); // Maximize precision in case fraction == 1.0
422         }
423         return new Point2d(p1X + fraction * (p2X - p1X), p1Y + fraction * (p2Y - p1Y));
424     }
425 
426     /**
427      * Compute the fractional position of the closest point on a line with optional limiting of the result on either end. If the
428      * line has length 0; this method returns 0.0.
429      * @param p1X the x coordinate of the first point on the line
430      * @param p1Y the y coordinate of the first point on the line
431      * @param p2X the x coordinate of the second point on the line
432      * @param p2Y the y coordinate of the second point on the line
433      * @param lowLimitHandling controls handling of results that lie before the first point of the line. If
434      *            <code>null</code>; this method returns <code>NaN</code>; else if <code>true</code>; this method returns 0.0;
435      *            else (lowLimitHandling is <code>false</code>); this results &lt; 0.0 are returned
436      * @param highLimitHandling controls the handling of results that lie beyond the second point of the line. If
437      *            <code>null</code>; this method returns <code>NaN</code>; else if <code>true</code>; this method returns 1.0;
438      *            else (highLimitHandling is <code>false</code>); results &gt; 1.0 are returned
439      * @return the fractional position of the closest point on the line. Results within the range 0.0 .. 1.0 are always
440      *         returned as is.. A result &lt; 0.0 is subject to lowLimitHandling. A result &gt; 1.0 is subject to
441      *         highLimitHandling
442      * @throws ArithmeticException when any of the arguments is NaN
443      * @throws DrawRuntimeException when the line is ill-defined (begin point coincides with end point)
444      */
445     public double fractionalPositionOnLine(final double p1X, final double p1Y, final double p2X, final double p2Y,
446             final Boolean lowLimitHandling, final Boolean highLimitHandling)
447     {
448         double dX = p2X - p1X;
449         double dY = p2Y - p1Y;
450         Throw.whenNaN(dX, "dX");
451         Throw.whenNaN(dY, "dY");
452         if (0 == dX && 0 == dY)
453         {
454             return 0.0;
455         }
456         double fraction = ((this.x - p1X) * dX + (this.y - p1Y) * dY) / (dX * dX + dY * dY);
457         if (fraction < 0.0)
458         {
459             if (lowLimitHandling == null)
460             {
461                 return Double.NaN;
462             }
463             if (lowLimitHandling)
464             {
465                 fraction = 0.0;
466             }
467         }
468         else if (fraction > 1.0)
469         {
470             if (highLimitHandling == null)
471             {
472                 return Double.NaN;
473             }
474             if (highLimitHandling)
475             {
476                 fraction = 1.0;
477             }
478         }
479         return fraction;
480     }
481 
482     /**
483      * Project a point on a line segment. If the the projected points lies outside the line segment, the nearest end point of
484      * the line segment is returned. Otherwise the returned point lies between the end points of the line segment. <br>
485      * Adapted from <a href="http://paulbourke.net/geometry/pointlineplane/DistancePoint.java">example code provided by Paul
486      * Bourke</a>.
487      * @param p1X the x coordinate of the start point of the line segment
488      * @param p1Y the y coordinate of the start point of the line segment
489      * @param p2X the x coordinate of the end point of the line segment
490      * @param p2Y the y coordinate of the end point of the line segment
491      * @return either <code>segmentPoint1</code>, or <code>segmentPoint2</code> or a new Point2d that lies somewhere in
492      *         between those two.
493      * @throws DrawRuntimeException when the line is ill-defined (begin point coincides with end point)
494      */
495     public final Point2d closestPointOnSegment(final double p1X, final double p1Y, final double p2X, final double p2Y)
496     {
497         return closestPointOnLine(p1X, p1Y, p2X, p2Y, true, true);
498     }
499 
500     @Override
501     public Point2d closestPointOnLine(final Point2d linePoint1, final Point2d linePoint2)
502             throws NullPointerException, DrawRuntimeException
503     {
504         Throw.whenNull(linePoint1, "linePoint1");
505         Throw.whenNull(linePoint2, "linePoint2");
506         return closestPointOnLine(linePoint1.x, linePoint1.y, linePoint2.x, linePoint2.y);
507     }
508 
509     /**
510      * Project a point on a line. <br>
511      * Adapted from <a href="http://paulbourke.net/geometry/pointlineplane/DistancePoint.java">example code provided by Paul
512      * Bourke</a>.
513      * @param p1X the x coordinate of a point of the line segment
514      * @param p1Y the y coordinate of a point of the line segment
515      * @param p2X the x coordinate of another point of the line segment
516      * @param p2Y the y coordinate of another point of the line segment
517      * @return a point on the line that goes through the points
518      * @throws IllegalArgumentException when the points on the line are identical
519      */
520     public final Point2d closestPointOnLine(final double p1X, final double p1Y, final double p2X, final double p2Y)
521     {
522         Throw.when(p1X == p2X && p1Y == p2Y, IllegalArgumentException.class, "degenerate line not allowed");
523         return closestPointOnLine(p1X, p1Y, p2X, p2Y, false, false);
524     }
525 
526     /**
527      * Return the zero, one or two intersections between two circles. The circles must be different. Derived from pseudo code by
528      * <a href="http://paulbourke.net/geometry/circlesphere/">Paul Bourke</a> and C implementation by
529      * <a href="http://paulbourke.net/geometry/circlesphere/tvoght.c">Tim Voght </a>.
530      * @param center1 the center of circle 1
531      * @param radius1 the radius of circle 1
532      * @param center2 the center of circle 2
533      * @param radius2 the radius of circle 2
534      * @return List&lt;Point2d&gt; a list of zero, one or two points
535      * @throws NullPointerException when <code>center1</code> or <code>center2</code> is <code>null</code>
536      * @throws IllegalArgumentException when the two circles are identical, or <code>radius1 &lt; 0.0</code>, or
537      *             <code>radius2 &lt; 0.0</code>
538      */
539     public static final List<Point2d> circleIntersections(final Point2d center1, final double radius1, final Point2d center2,
540             final double radius2) throws NullPointerException, IllegalArgumentException
541     {
542         Throw.whenNull(center1, "center1");
543         Throw.whenNull(center2, "center2");
544         Throw.when(radius1 < 0 || radius2 < 0, IllegalArgumentException.class, "radius may not be less than 0");
545         Throw.when(center1.equals(center2) && radius1 == radius2, IllegalArgumentException.class, "circles must be different");
546         List<Point2d> result = new ArrayList<>();
547         // dX,dY is the vector from center1 to center2
548         double dX = center2.x - center1.x;
549         double dY = center2.y - center1.y;
550         double distance = Math.hypot(dX, dY);
551         if (distance > radius1 + radius2 || distance < Math.abs(radius1 - radius2))
552         {
553             return result; // empty list
554         }
555         double a = (radius1 * radius1 - radius2 * radius2 + distance * distance) / (2 * distance);
556         // x2,y2 is the point where the line through the circle intersections crosses the line through the circle centers
557         double x2 = center1.x + (dX * a / distance);
558         double y2 = center1.y + (dY * a / distance);
559         // h is distance from x2,y2 to each of the solutions
560         double h = Math.sqrt(radius1 * radius1 - a * a);
561         // rX, rY is vector from x2,y2 to the first solution
562         double rX = -dY * (h / distance);
563         double rY = dX * (h / distance);
564         result.add(new Point2d(x2 + rX, y2 + rY));
565         if (h > 0)
566         {
567             // Two distinct solutions; add the second one
568             result.add(new Point2d(x2 - rX, y2 - rY));
569         }
570         return result;
571     }
572 
573     /**
574      * Return the direction to another Point2d.
575      * @param otherPoint the other point
576      * @return the direction to the other point in Radians (towards infinite X is 0; towards infinite Y is &pi; / 2;
577      *         etc.). If the points are identical; this method returns <code>NaN</code>.
578      */
579     public double directionTo(final Point2d otherPoint)
580     {
581         return Math.atan2(otherPoint.y - this.y, otherPoint.x - this.x);
582     }
583 
584     /**
585      * Return the coordinates as a java.awt.geom.Point2D.Double object.
586      * @return java.awt.geom.Point2D; the coordinates as a java.awt.geom.Point2D.Double object
587      */
588     public java.awt.geom.Point2D toPoint2D()
589     {
590         return new java.awt.geom.Point2D.Double(this.x, this.y);
591     }
592 
593     @Override
594     public String toString()
595     {
596         return toString("%f");
597     }
598 
599     @Override
600     public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
601     {
602         String format = String.format("%1$s[x=%2$s, y=%2$s]", doNotIncludeClassName ? "" : "Point2d ", doubleFormat);
603         return String.format(Locale.US, format, this.x, this.y);
604     }
605 
606     @Override
607     public int hashCode()
608     {
609         final int prime = 31;
610         int result = 1;
611         long temp;
612         temp = Double.doubleToLongBits(this.x);
613         result = prime * result + (int) (temp ^ (temp >>> 32));
614         temp = Double.doubleToLongBits(this.y);
615         result = prime * result + (int) (temp ^ (temp >>> 32));
616         return result;
617     }
618 
619     @SuppressWarnings("checkstyle:needbraces")
620     @Override
621     public boolean equals(final Object obj)
622     {
623         if (this == obj)
624             return true;
625         if (obj == null)
626             return false;
627         if (getClass() != obj.getClass())
628             return false;
629         Point2d other = (Point2d) obj;
630         if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x))
631             return false;
632         if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y))
633             return false;
634         return true;
635     }
636 
637 }