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