View Javadoc
1   package org.djutils.draw.point;
2   
3   import java.util.Arrays;
4   import java.util.Iterator;
5   
6   import org.djutils.base.AngleUtil;
7   import org.djutils.draw.DrawRuntimeException;
8   import org.djutils.draw.Oriented3d;
9   import org.djutils.exceptions.Throw;
10  
11  /**
12   * A OrientedPoint3d is an immutable directed point with an x, y, and z coordinate, stored with double precision. It differs
13   * from many Point implementations by being immutable. The direction is a vector from the point, where its direction is
14   * specified by the rotation around the x, y, and z-axis. A number of constructors and methods are provided for cases where only
15   * the rotation around the z-axis is of importance.
16   * <p>
17   * Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
18   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
19   * </p>
20   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
21   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
22   */
23  public class OrientedPoint3d extends Point3d implements Oriented3d<OrientedPoint3d>
24  {
25      /** */
26      private static final long serialVersionUID = 20200828L;
27  
28      /** The direction as rotation around the x-axis. */
29      private final double dirX;
30  
31      /** The direction as rotation around the y-axis. */
32      private final double dirY;
33  
34      /** The direction as rotation around the z-axis. */
35      private final double dirZ;
36  
37      /**
38       * Create an immutable directed point with x, y, and z coordinates, stored with double precision, and direction 0,0,0.
39       * @param x double; the x coordinate
40       * @param y double; the y coordinate
41       * @param z double; the z coordinate
42       * @throws IllegalArgumentException when x, y, or z is NaN
43       */
44      public OrientedPoint3d(final double x, final double y, final double z) throws IllegalArgumentException
45      {
46          super(x, y, z);
47          this.dirX = 0.0;
48          this.dirY = 0.0;
49          this.dirZ = 0.0;
50      }
51  
52      /**
53       * Create an immutable directed point with x, y, and z coordinates, stored with double precision and direction.
54       * @param x double; the x coordinate
55       * @param y double; the y coordinate
56       * @param z double; the z coordinate
57       * @param dirX double; the direction as rotation around the x-axis with the point as the center
58       * @param dirY double; the direction as rotation around the y-axis with the point as the center
59       * @param dirZ double; the direction as rotation around the z-axis with the point as the center
60       * @throws IllegalArgumentException when x, y, z, dirX, dirY, or dirZ is NaN
61       */
62      public OrientedPoint3d(final double x, final double y, final double z, final double dirX, final double dirY,
63              final double dirZ) throws IllegalArgumentException
64      {
65          super(x, y, z);
66          Throw.when(Double.isNaN(dirX) || Double.isNaN(dirY) || Double.isNaN(dirZ), IllegalArgumentException.class,
67                  "Rotation must be a number (not NaN)");
68          this.dirX = dirX;
69          this.dirY = dirY;
70          this.dirZ = dirZ;
71      }
72  
73      /**
74       * Create an immutable directed point with x, y, and z coordinates, stored with double precision, and direction 0,0,0.
75       * @param xyz double[]; the x, y and z coordinates
76       * @throws NullPointerException when xyx is null
77       * @throws IllegalArgumentException when the length of the xyx array is not 3, or contains a NaN value, or dirX, dirY, or
78       *             dirZ is NaN
79       */
80      public OrientedPoint3d(final double[] xyz) throws NullPointerException, IllegalArgumentException
81      {
82          super(xyz);
83          this.dirX = 0.0;
84          this.dirY = 0.0;
85          this.dirZ = 0.0;
86      }
87  
88      /**
89       * Create an immutable directed point with x, y, and z coordinates, stored with double precision, and direction 0,0,0.
90       * @param xyz double[]; the x, y and z coordinates
91       * @param dirX double; the direction as rotation around the x-axis with the point as the center
92       * @param dirY double; the direction as rotation around the y-axis with the point as the center
93       * @param dirZ double; the direction as rotation around the z-axis with the point as the center
94       * @throws NullPointerException when xyx is null
95       * @throws IllegalArgumentException when the length of the xyx array is not 3, or contains a NaN value, or dirX, dirY, or
96       *             dirZ is NaN
97       */
98      public OrientedPoint3d(final double[] xyz, final double dirX, final double dirY, final double dirZ)
99              throws NullPointerException, IllegalArgumentException
100     {
101         super(xyz);
102         Throw.when(Double.isNaN(dirX) || Double.isNaN(dirY) || Double.isNaN(dirZ), IllegalArgumentException.class,
103                 "Direction must be a number (not NaN)");
104         this.dirX = dirX;
105         this.dirY = dirY;
106         this.dirZ = dirZ;
107     }
108 
109     /**
110      * Create an immutable directed point from another point, stored with double precision and specified direction.
111      * @param point Point3d; the point from which this OrientedPoint3d will be instantiated
112      * @param dirX double; the direction as rotation around the x-axis with the point as the center
113      * @param dirY double; the direction as rotation around the y-axis with the point as the center
114      * @param dirZ double; the direction as rotation around the z-axis with the point as the center
115      * @throws IllegalArgumentException when dirX, dirY, or dirZ is NaN
116      */
117     public OrientedPoint3d(final Point3d point, final double dirX, final double dirY, final double dirZ)
118             throws IllegalArgumentException
119     {
120         super(point.x, point.y, point.z);
121         Throw.when(Double.isNaN(dirX) || Double.isNaN(dirY) || Double.isNaN(dirZ), IllegalArgumentException.class,
122                 "Direction must be a number (not NaN)");
123         this.dirX = dirX;
124         this.dirY = dirY;
125         this.dirZ = dirZ;
126     }
127 
128     /**
129      * Create an immutable point with x, y, and z coordinates, stored with double precision, and direction.
130      * @param x double; the x coordinate
131      * @param y double; the y coordinate
132      * @param z double; the z coordinate
133      * @param direction double[]; the direction as rotations around the x,y,z-axes with the point as the center
134      * @throws NullPointerException when <code>rotation</code> is null
135      * @throws IllegalArgumentException when the length of the <code>direction</code> array is not 3
136      */
137     public OrientedPoint3d(final double x, final double y, final double z, final double[] direction)
138             throws NullPointerException, IllegalArgumentException
139     {
140         super(x, y, z);
141         Throw.whenNull(direction, "direction array cannot be null");
142         Throw.when(direction.length != 3, IllegalArgumentException.class, "length of direction array must be 3");
143         this.dirX = direction[0];
144         this.dirY = direction[1];
145         this.dirZ = direction[2];
146     }
147 
148     /**
149      * Create an immutable point with x, y, and z coordinates, stored with double precision, and direction.
150      * @param xyz double[]; the x, y and z coordinates
151      * @param direction double[]; the rotation around the x,y,z-axis with the point as the center
152      * @throws NullPointerException when xyx or direction is null
153      * @throws IllegalArgumentException when the length of the xyx array or the length of the direction array is not 3
154      */
155     public OrientedPoint3d(final double[] xyz, final double[] direction) throws NullPointerException, IllegalArgumentException
156     {
157         super(xyz);
158         Throw.whenNull(direction, "direction cannot be null");
159         Throw.when(direction.length != 3, IllegalArgumentException.class, "length of direction array must be 3");
160         this.dirX = direction[0];
161         this.dirY = direction[1];
162         this.dirZ = direction[2];
163     }
164 
165     /** {@inheritDoc} */
166     @Override
167     public OrientedPoint3d translate(final double dx, final double dy) throws IllegalArgumentException
168     {
169         Throw.when(Double.isNaN(dx) || Double.isNaN(dy), IllegalArgumentException.class, "translation may not be NaN");
170         return new OrientedPoint3d(getX() + dx, getY() + dy, getZ(), getDirX(), getDirY(), getDirZ());
171     }
172 
173     /** {@inheritDoc} */
174     @Override
175     public OrientedPoint3d translate(final double dx, final double dy, final double dz) throws IllegalArgumentException
176     {
177         Throw.when(Double.isNaN(dx) || Double.isNaN(dy) || Double.isNaN(dz), IllegalArgumentException.class,
178                 "Translation may not be NaN");
179         return new OrientedPoint3d(this.x + dx, this.y + dy, this.z + dz, this.dirX, this.dirY, this.dirZ);
180     }
181 
182     /** {@inheritDoc} */
183     @Override
184     public OrientedPoint3d scale(final double factor) throws IllegalArgumentException
185     {
186         return new OrientedPoint3d(this.x * factor, this.y * factor, this.z * factor, this.dirX, this.dirY, this.dirZ);
187     }
188 
189     /** {@inheritDoc} */
190     @Override
191     public OrientedPoint3d neg()
192     {
193         return new OrientedPoint3d(-this.x, -this.y, -this.z, AngleUtil.normalizeAroundZero(this.dirX + Math.PI),
194                 AngleUtil.normalizeAroundZero(this.dirY + Math.PI), AngleUtil.normalizeAroundZero(this.dirZ + Math.PI));
195     }
196 
197     /** {@inheritDoc} */
198     @Override
199     public OrientedPoint3d abs()
200     {
201         return new OrientedPoint3d(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z), this.dirX, this.dirY, this.dirZ);
202     }
203 
204     /** {@inheritDoc} */
205     @Override
206     public OrientedPoint3d normalize() throws DrawRuntimeException
207     {
208         double length = Math.sqrt(getX() * getX() + getY() * getY() + getZ() * getZ());
209         Throw.when(length == 0.0, DrawRuntimeException.class, "cannot normalize (0.0, 0.0, 0.0)");
210         return new OrientedPoint3d(this.x / length, this.y / length, this.z / length, this.dirX, this.dirY, this.dirZ);
211     }
212 
213     /**
214      * Interpolate towards another Point with a fraction. It is allowed for fraction to be less than zero or larger than 1. In
215      * that case the interpolation turns into an extrapolation. DirX, dirY and dirZ are interpolated using the
216      * interpolateShortest method.
217      * @param otherPoint OrientedPoint3d; the other point
218      * @param fraction double; the factor for interpolation towards the other point. When &lt;code&gt;fraction&lt;/code&gt; is
219      *            between 0 and 1, it is an interpolation, otherwise an extrapolation. If <code>fraction</code> is 0;
220      *            <code>this</code> Point is returned; if <code>fraction</code> is 1, the other <code>point</code> is returned
221      * @return OrientedPoint3d; a new OrientedPoint3d at the requested fraction
222      * @throws NullPointerException when otherPoint is null
223      * @throws IllegalArgumentException when fraction is NaN
224      */
225     public OrientedPoint3dl#OrientedPoint3d">OrientedPoint3d interpolate(final OrientedPoint3d otherPoint, final double fraction)
226             throws NullPointerException, IllegalArgumentException
227     {
228         Throw.whenNull(otherPoint, "point cannot be null");
229         Throw.when(Double.isNaN(fraction), IllegalArgumentException.class, "fraction must be a number (not NaN)");
230         return new OrientedPoint3d((1.0 - fraction) * getX() + fraction * otherPoint.x,
231                 (1.0 - fraction) * getY() + fraction * otherPoint.y, (1.0 - fraction) * getZ() + fraction * otherPoint.z,
232                 AngleUtil.interpolateShortest(getDirX(), otherPoint.getDirX(), fraction),
233                 AngleUtil.interpolateShortest(getDirY(), otherPoint.getDirY(), fraction),
234                 AngleUtil.interpolateShortest(getDirZ(), otherPoint.getDirZ(), fraction));
235     }
236 
237     /**
238      * Return a new OrientedPoint with an in-place rotation around the z-axis by the provided delta. The resulting rotation will
239      * be normalized between -&pi; and &pi;.
240      * @param rotateZ double; the rotation around the z-axis
241      * @return OrientedPoint3d; a new point with the same coordinates, dirX and dirY and modified dirZ
242      * @throws IllegalArgumentException when rotateZ is NaN
243      */
244     public OrientedPoint3d rotate(final double rotateZ) throws IllegalArgumentException
245     {
246         Throw.when(Double.isNaN(rotateZ), IllegalArgumentException.class, "deltaDirZ must be a number (not NaN)");
247         return new OrientedPoint3d(getX(), getY(), getZ(), getDirX(), getDirY(),
248                 AngleUtil.normalizeAroundZero(getDirZ() + rotateZ));
249     }
250 
251     /**
252      * Return a new OrientedPoint3d point with an in-place rotation by the provided rotateX, rotateY, and rotateZ. The resulting
253      * rotations will be normalized between -&pi; and &pi;.
254      * @param rotateX double; the rotation around the x-axis
255      * @param rotateY double; the rotation around the y-axis
256      * @param rotateZ double; the rotation around the z-axis
257      * @return OrientedPoint3d; a new point with the same coordinates and applied rotations
258      * @throws IllegalArgumentException when any of the rotations is NaN
259      */
260     public OrientedPoint3d rotate(final double rotateX, final double rotateY, final double rotateZ)
261             throws IllegalArgumentException
262     {
263         Throw.when(Double.isNaN(rotateX) || Double.isNaN(rotateY) || Double.isNaN(rotateZ), IllegalArgumentException.class,
264                 "Rotation must be a number (not NaN)");
265         return new OrientedPoint3d(getX(), getY(), getZ(), AngleUtil.normalizeAroundZero(getDirX() + rotateX),
266                 AngleUtil.normalizeAroundZero(getDirY() + rotateY), AngleUtil.normalizeAroundZero(getDirZ() + rotateZ));
267     }
268 
269     /** {@inheritDoc} */
270     @Override
271     public double getDirX()
272     {
273         return this.dirX;
274     }
275 
276     /** {@inheritDoc} */
277     @Override
278     public double getDirY()
279     {
280         return this.dirY;
281     }
282 
283     /** {@inheritDoc} */
284     @Override
285     public double getDirZ()
286     {
287         return this.dirZ;
288     }
289 
290     /** {@inheritDoc} */
291     @Override
292     public Iterator<OrientedPoint3d> getPoints()
293     {
294         return Arrays.stream(new OrientedPoint3d[] {this}).iterator();
295     }
296 
297     /** {@inheritDoc} */
298     @Override
299     public String toString()
300     {
301         return String.format("[(%f,%f,%f), rot=(%f,%f,%f)]", this.x, this.y, this.z, this.dirX, this.dirY, this.dirZ);
302     }
303 
304     /** {@inheritDoc} */
305     @Override
306     public String toString(final int fractionDigits)
307     {
308         int digits = fractionDigits < 0 ? 0 : fractionDigits;
309         String format = String.format("[(%%.%1$df,%%.%1$df,%%.%1$df), rot=(%%.%1$df,%%.%1$df,%%.%1$df)]", digits);
310         return String.format(format, this.x, this.y, this.z, this.dirX, this.dirY, this.dirZ);
311     }
312 
313     /** {@inheritDoc} */
314     @Override
315     public boolean epsilonEquals(final OrientedPoint3d other, final double epsilonCoordinate, final double epsilonRotation)
316             throws NullPointerException, IllegalArgumentException
317     {
318         Throw.whenNull(other, "other point cannot be null");
319         if (Math.abs(this.x - other.x) > epsilonCoordinate)
320         {
321             return false;
322         }
323         if (Math.abs(this.y - other.y) > epsilonCoordinate)
324         {
325             return false;
326         }
327         if (Math.abs(this.z - other.z) > epsilonCoordinate)
328         {
329             return false;
330         }
331         if (Math.abs(AngleUtil.normalizeAroundZero(this.dirX - other.dirX)) > epsilonRotation)
332         {
333             return false;
334         }
335         if (Math.abs(AngleUtil.normalizeAroundZero(this.dirY - other.dirY)) > epsilonRotation)
336         {
337             return false;
338         }
339         if (Math.abs(AngleUtil.normalizeAroundZero(this.dirZ - other.dirZ)) > epsilonRotation)
340         {
341             return false;
342         }
343         return true;
344     }
345 
346     /** {@inheritDoc} */
347     @Override
348     public int hashCode()
349     {
350         final int prime = 31;
351         int result = super.hashCode();
352         long temp;
353         temp = Double.doubleToLongBits(this.dirX);
354         result = prime * result + (int) (temp ^ (temp >>> 32));
355         temp = Double.doubleToLongBits(this.dirY);
356         result = prime * result + (int) (temp ^ (temp >>> 32));
357         temp = Double.doubleToLongBits(this.dirZ);
358         result = prime * result + (int) (temp ^ (temp >>> 32));
359         return result;
360     }
361 
362     /** {@inheritDoc} */
363     @Override
364     @SuppressWarnings("checkstyle:needbraces")
365     public boolean equals(final Object obj)
366     {
367         if (this == obj)
368             return true;
369         if (!super.equals(obj))
370             return false;
371         OrientedPoint3d/../org/djutils/draw/point/OrientedPoint3d.html#OrientedPoint3d">OrientedPoint3d other = (OrientedPoint3d) obj;
372         if (Double.doubleToLongBits(this.dirX) != Double.doubleToLongBits(other.dirX))
373             return false;
374         if (Double.doubleToLongBits(this.dirY) != Double.doubleToLongBits(other.dirY))
375             return false;
376         if (Double.doubleToLongBits(this.dirZ) != Double.doubleToLongBits(other.dirZ))
377             return false;
378         return true;
379     }
380 
381 }