View Javadoc
1   package org.djutils.draw.point;
2   
3   import java.awt.geom.Point2D;
4   import java.util.Arrays;
5   import java.util.Iterator;
6   
7   import org.djutils.draw.DrawRuntimeException;
8   import org.djutils.draw.Drawable3d;
9   import org.djutils.draw.Space3d;
10  import org.djutils.draw.bounds.Bounds3d;
11  import org.djutils.draw.line.Ray3d;
12  import org.djutils.exceptions.Throw;
13  
14  /**
15   * A Point3d is an immutable point with an x, y, and z coordinate, stored with double precision. It differs from many Point
16   * implementations by being immutable.
17   * <p>
18   * Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
19   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
20   * </p>
21   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
22   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
23   */
24  public class Point3d implements Drawable3d, Point<Point3d, Space3d>
25  {
26      /** */
27      private static final long serialVersionUID = 20201201L;
28  
29      /** The x-coordinate. */
30      @SuppressWarnings("checkstyle:visibilitymodifier")
31      public final double x;
32  
33      /** The y-coordinate. */
34      @SuppressWarnings("checkstyle:visibilitymodifier")
35      public final double y;
36  
37      /** The z-coordinate. */
38      @SuppressWarnings("checkstyle:visibilitymodifier")
39      public final double z;
40  
41      /**
42       * Create a new Point with just an x, y and z coordinate, stored with double precision.
43       * @param x double; the x coordinate
44       * @param y double; the y coordinate
45       * @param z double; the z coordinate
46       * @throws IllegalArgumentException when x or y is NaN
47       */
48      public Point3d(final double x, final double y, final double z)
49      {
50          Throw.when(Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z), IllegalArgumentException.class,
51                  "Coordinate must be a number (not NaN)");
52          this.x = x;
53          this.y = y;
54          this.z = z;
55      }
56  
57      /**
58       * Create a new Point with just an x, y and z coordinate, stored with double precision.
59       * @param xyz double[]; the x, y and z coordinate
60       * @throws NullPointerException when xyz is null
61       * @throws IllegalArgumentException when the dimension of xyz is not 3, or a coordinate is NaN
62       */
63      public Point3d(final double[] xyz) throws NullPointerException, IllegalArgumentException
64      {
65          this(checkLengthIsThree(Throw.whenNull(xyz, "xyz-point cannot be null"))[0], xyz[1], xyz[2]);
66      }
67  
68      /**
69       * Create an immutable point with just three values, x, y and z, stored with double precision from a Point2d and z.
70       * @param point Point2d; a Point2d
71       * @param z double; the z coordinate
72       * @throws NullPointerException when point is null
73       * @throws IllegalArgumentException when z is NaN
74       */
75      public Point3d(final Point2d point, final double z) throws NullPointerException, IllegalArgumentException
76      {
77          Throw.whenNull(point, "point cannot be null");
78          Throw.when(Double.isNaN(z), IllegalArgumentException.class, "Coordinate must be a number (not NaN)");
79          this.x = point.x;
80          this.y = point.y;
81          this.z = z;
82      }
83  
84      /**
85       * Create an immutable point with just three values, x, y and z, stored with double precision from an AWT Point2D and z.
86       * @param point Point2D; an AWT Point2D
87       * @param z double; the z coordinate
88       * @throws NullPointerException when point is null
89       * @throws IllegalArgumentException when point has a NaN coordinate, or z is NaN
90       */
91      public Point3d(final Point2D point, final double z) throws NullPointerException, IllegalArgumentException
92      {
93          Throw.whenNull(point, "point cannot be null");
94          Throw.when(Double.isNaN(point.getX()) || Double.isNaN(point.getY()), IllegalArgumentException.class,
95                  "Coordinate must be a number (not NaN)");
96          Throw.when(Double.isNaN(z), IllegalArgumentException.class, "Coordinate must be a number (not NaN)");
97          this.x = point.getX();
98          this.y = point.getY();
99          this.z = z;
100     }
101 
102     /**
103      * Throw an IllegalArgumentException if the length of the provided array is not three.
104      * @param xyz double[]; the provided array
105      * @return double[]; the provided array
106      * @throws IllegalArgumentException when length of xyz is not three
107      */
108     private static double[] checkLengthIsThree(final double[] xyz) throws IllegalArgumentException
109     {
110         Throw.when(xyz.length != 3, IllegalArgumentException.class, "Length of xy-array must be 2");
111         return xyz;
112     }
113 
114     /** {@inheritDoc} */
115     @Override
116     public final double getX()
117     {
118         return this.x;
119     }
120 
121     /** {@inheritDoc} */
122     @Override
123     public final double getY()
124     {
125         return this.y;
126     }
127 
128     /**
129      * Return the z-coordinate.
130      * @return double; the z-coordinate
131      */
132     public final double getZ()
133     {
134         return this.z;
135     }
136 
137     /** {@inheritDoc} */
138     @Override
139     public double distanceSquared(final Point3d otherPoint) throws NullPointerException
140     {
141         Throw.whenNull(otherPoint, "point cannot be null");
142         double dx = getX() - otherPoint.x;
143         double dy = getY() - otherPoint.y;
144         double dz = getZ() - otherPoint.z;
145         return dx * dx + dy * dy + dz * dz;
146     }
147 
148     /** {@inheritDoc} */
149     @Override
150     public double distance(final Point3d otherPoint) throws NullPointerException
151     {
152         Throw.whenNull(otherPoint, "point cannot be null");
153         return Math.sqrt(distanceSquared(otherPoint));
154     }
155 
156     /** {@inheritDoc} */
157     @Override
158     public int size()
159     {
160         return 1;
161     }
162 
163     /** {@inheritDoc} */
164     @Override
165     public Iterator<? extends Point3d> getPoints()
166     {
167         return Arrays.stream(new Point3d[] {this}).iterator();
168     }
169 
170     /** {@inheritDoc} */
171     @Override
172     public Point2d project() throws DrawRuntimeException
173     {
174         return new Point2d(getX(), getY());
175     }
176 
177     /**
178      * Return a new Point with a translation by the provided dx and dy.
179      * @param dx double; the horizontal translation
180      * @param dy double; the vertical translation
181      * @return Point3D; a new point with the translated coordinates
182      * @throws IllegalArgumentException when dx, or dy is NaN
183      */
184     public Point3d translate(final double dx, final double dy) throws IllegalArgumentException
185     {
186         Throw.when(Double.isNaN(dx) || Double.isNaN(dy), IllegalArgumentException.class,
187                 "Translation must be number (not NaN)");
188         return new Point3d(getX() + dx, getY() + dy, getZ());
189     }
190 
191     /**
192      * Return a new Point3d with a translation by the provided dx, dy and dz.
193      * @param dx double; the x translation
194      * @param dy double; the y translation
195      * @param dz double; the z translation
196      * @return Point3d; a new point with the translated coordinates
197      * @throws IllegalArgumentException when dx, dy, or dz is NaN
198      */
199     public Point3d translate(final double dx, final double dy, final double dz) throws IllegalArgumentException
200     {
201         Throw.when(Double.isNaN(dx) || Double.isNaN(dy) || Double.isNaN(dz), IllegalArgumentException.class,
202                 "dx, dy and dz must be numbers (not NaN)");
203         return new Point3d(getX() + dx, getY() + dy, getZ() + dz);
204     }
205 
206     /** {@inheritDoc} */
207     @Override
208     public Point3d scale(final double factor) throws IllegalArgumentException
209     {
210         Throw.when(Double.isNaN(factor), IllegalArgumentException.class, "factor must be a number (not NaN)");
211         return new Point3d(getX() * factor, getY() * factor, getZ() * factor);
212     }
213 
214     /** {@inheritDoc} */
215     @Override
216     public Point3d neg()
217     {
218         return scale(-1.0);
219     }
220 
221     /** {@inheritDoc} */
222     @Override
223     public Point3d abs()
224     {
225         return new Point3d(Math.abs(getX()), Math.abs(getY()), Math.abs(getZ()));
226     }
227 
228     /** {@inheritDoc} */
229     @Override
230     public Point3d normalize() throws DrawRuntimeException
231     {
232         double length = Math.sqrt(getX() * getX() + getY() * getY() + getZ() * getZ());
233         Throw.when(length == 0.0, DrawRuntimeException.class, "cannot normalize (0.0, 0.0, 0.0)");
234         return this.scale(1.0 / length);
235     }
236 
237     /** {@inheritDoc} */
238     @Override
239     public Point3dl#Point3d">Point3d interpolate(final Point3d point, final double fraction)
240     {
241         Throw.whenNull(point, "point cannot be null");
242         Throw.when(Double.isNaN(fraction), IllegalArgumentException.class, "fraction must be a number (not NaN)");
243         return new Point3d((1.0 - fraction) * getX() + fraction * point.x, (1.0 - fraction) * getY() + fraction * point.y,
244                 (1.0 - fraction) * getZ() + fraction * point.z);
245 
246     }
247 
248     /** {@inheritDoc} */
249     @Override
250     public boolean epsilonEquals(final Point3d other, final double epsilon)
251     {
252         Throw.whenNull(other, "other point cannot be null");
253         if (Math.abs(getX() - other.x) > epsilon)
254         {
255             return false;
256         }
257         if (Math.abs(getY() - other.y) > epsilon)
258         {
259             return false;
260         }
261         if (Math.abs(getZ() - other.z) > epsilon)
262         {
263             return false;
264         }
265         return true;
266     }
267 
268     /** {@inheritDoc} */
269     @Override
270     public Bounds3d getBounds()
271     {
272         return new Bounds3d(this);
273     }
274 
275     /** {@inheritDoc} */
276     @Override
277     public final Point3doint3d">Point3d>Point3d closestPointOnSegment(final Point3doint3d">Point3d segmentPoint1, final Point3d segmentPoint2)
278     {
279         double dX = segmentPoint2.x - segmentPoint1.x;
280         double dY = segmentPoint2.y - segmentPoint1.y;
281         double dZ = segmentPoint2.z - segmentPoint1.z;
282         if (0 == dX && 0 == dY && 0 == dZ) // The points may be equal (unlike in Segment3d)
283         {
284             return segmentPoint1;
285         }
286         final double u = ((this.x - segmentPoint1.x) * dX + (this.y - segmentPoint1.y) * dY + (this.z - segmentPoint1.z) * dZ)
287                 / (dX * dX + dY * dY + dZ * dZ);
288         if (u < 0)
289         {
290             return segmentPoint1;
291         }
292         else if (u > 1)
293         {
294             return segmentPoint2;
295         }
296         else
297         {
298             return segmentPoint1.interpolate(segmentPoint2, u);
299         }
300     }
301 
302     /** {@inheritDoc} */
303     @Override
304     public final Point3dl#Point3d">Point3d3d">Point3d closestPointOnLine(final Point3dl#Point3d">Point3d linePoint1, final Point3d linePoint2) throws DrawRuntimeException
305     {
306         double dX = linePoint2.x - linePoint1.x;
307         double dY = linePoint2.y - linePoint1.y;
308         double dZ = linePoint2.z - linePoint1.z;
309         Throw.when(dX == 0 && dY == 0 && dZ == 0, DrawRuntimeException.class, "line points are at same location");
310         final double u = ((this.x - linePoint1.x) * dX + (this.y - linePoint1.y) * dY + (this.z - linePoint1.z) * dZ)
311                 / (dX * dX + dY * dY + dZ * dZ);
312         return linePoint1.interpolate(linePoint2, u);
313     }
314 
315     /**
316      * Closest point on a ray.
317      * @param ray Ray3d; a point through which the line passes in the direction
318      * @return Point3d; the point on the line that is closest to this
319      */
320     public final Point3d closestPointOnLine(final Ray3d ray)
321     {
322         double sinTheta = Math.sin(ray.theta);
323         double dX = Math.cos(ray.phi) * sinTheta;
324         double dY = Math.sin(ray.phi) * sinTheta;
325         double dZ = Math.cos(ray.theta);
326         final double u =
327                 ((this.x - ray.x) * dX + (this.y - ray.y) * dY + (this.z - ray.z) * dZ) / (dX * dX + dY * dY * dZ * dZ);
328         return ray.interpolate(new Point3d(ray.x + dX, ray.y + dY, ray.z + dZ), Math.max(0, u));
329     }
330 
331     /**
332      * Return the direction of the point in radians with respect to the origin, ignoring the z-coordinate.
333      * @return double; the direction of the projection of the point in the x-y plane with respect to the origin, in radians
334      */
335     final double horizontalDirection()
336     {
337         return Math.atan2(getY(), getX());
338     }
339 
340     /**
341      * Return the direction to another point, in radians, ignoring the z-coordinate.
342      * @param point Point3d; the other point
343      * @return double; the direction of the projection of the point in the x-y plane to another point, in radians
344      * @throws NullPointerException when <code>point</code> is null
345      */
346     final double horizontalDirection(final Point3d point) throws NullPointerException
347     {
348         Throw.whenNull(point, "point cannot be null");
349         return Math.atan2(point.y - getY(), point.x - getX());
350     }
351 
352     /**
353      * Return the squared distance between the coordinates of this point and the provided point, ignoring the z-coordinate.
354      * @param point Point3d; the other point
355      * @return double; the squared distance between this point and the other point, ignoring the z-coordinate
356      * @throws NullPointerException when point is null
357      */
358     final double horizontalDistanceSquared(final Point3d point)
359     {
360         Throw.whenNull(point, "point cannot be null");
361         double dx = getX() - point.x;
362         double dy = getY() - point.y;
363         return dx * dx + dy * dy;
364     }
365 
366     /**
367      * Return the Euclidean distance between this point and the provided point, ignoring the z-coordinate.
368      * @param point Point3d; the other point
369      * @return double; the Euclidean distance between this point and the other point, ignoring the z-coordinate
370      * @throws NullPointerException when point is null
371      */
372     final double horizontalDistance(final Point3d point)
373     {
374         return Math.sqrt(horizontalDistanceSquared(point));
375     }
376 
377     /** {@inheritDoc} */
378     @Override
379     @SuppressWarnings("checkstyle:designforextension")
380     public String toString()
381     {
382         return String.format("(%f,%f,%f)", getX(), getY(), getZ());
383     }
384 
385     /** {@inheritDoc} */
386     @Override
387     public String toString(final int fractionDigits)
388     {
389         int digits = fractionDigits < 0 ? 0 : fractionDigits;
390         String format = String.format("(%%.%1$df,%%.%1$df,%%.%1$df)", digits);
391         return String.format(format, getX(), getY(), getZ());
392     }
393 
394     /** {@inheritDoc} */
395     @Override
396     public int hashCode()
397     {
398         final int prime = 31;
399         int result = 1;
400         long temp;
401         temp = Double.doubleToLongBits(this.x);
402         result = prime * result + (int) (temp ^ (temp >>> 32));
403         temp = Double.doubleToLongBits(this.y);
404         result = prime * result + (int) (temp ^ (temp >>> 32));
405         temp = Double.doubleToLongBits(this.z);
406         result = prime * result + (int) (temp ^ (temp >>> 32));
407         return result;
408     }
409 
410     /** {@inheritDoc} */
411     @SuppressWarnings("checkstyle:needbraces")
412     @Override
413     public boolean equals(final Object obj)
414     {
415         if (this == obj)
416             return true;
417         if (obj == null)
418             return false;
419         if (getClass() != obj.getClass())
420             return false;
421         Point3d../../../../org/djutils/draw/point/Point3d.html#Point3d">Point3d other = (Point3d) obj;
422         if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x))
423             return false;
424         if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y))
425             return false;
426         if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z))
427             return false;
428         return true;
429     }
430 
431 }