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