View Javadoc
1   package org.djutils.draw.line;
2   
3   import java.util.Arrays;
4   import java.util.Iterator;
5   import java.util.Locale;
6   
7   import org.djutils.draw.Direction3d;
8   import org.djutils.draw.Drawable3d;
9   import org.djutils.draw.bounds.Bounds3d;
10  import org.djutils.draw.point.DirectedPoint3d;
11  import org.djutils.draw.point.Point3d;
12  import org.djutils.exceptions.Throw;
13  import org.djutils.math.AngleUtil;
14  
15  /**
16   * Ray3d is a half-line in 3d; it has one end point with non-infinite coordinates; the other end point is infinitely far away.
17   * <p>
18   * Copyright (c) 2020-2025 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://github.com/peter-knoppers">Peter Knoppers</a>
23   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
24   */
25  public class Ray3d extends DirectedPoint3d implements Drawable3d, Ray<Ray3d, DirectedPoint3d, Point3d>
26  {
27      /** */
28      private static final long serialVersionUID = 20210119L;
29  
30      /**
31       * Construct a new Ray3d.
32       * @param x the x coordinate of the finite end point of the ray
33       * @param y the y coordinate of the finite end point of the ray
34       * @param z the z coordinate of the finite end point of the ray
35       * @param dirY the angle from the positive Z axis direction in radians (the complement of the slope)
36       * @param dirZ the angle from the positive X axis direction in radians
37       * @throws IllegalArgumentException when <code>dirY</code>, or <code>dirZ</code> is <code>NaN</code> (should be impossible)
38       */
39      public Ray3d(final double x, final double y, final double z, final double dirY, final double dirZ)
40      {
41          super(x, y, z, dirY, dirZ);
42      }
43  
44      /**
45       * Create a new Ray3d with x, y, and z coordinates and orientation specified using a double array of three elements
46       * (containing dirX,dirY,dirZ in that order).
47       * @param x the x coordinate
48       * @param y the y coordinate
49       * @param z the z coordinate
50       * @param directionVector the two direction angles in a double array containing dirY and dirZ in that order. DirY
51       *            is the rotation from the positive z-axis to the direction. DirZ is the angle from the positive x-axis to the
52       *            projection of the direction in the x-y-plane.
53       * @throws NullPointerException when <code>direction</code> is <code>null</code>
54       * @throws ArithmeticException when the <code>directionVector</code> array contains a <code>NaN</code> value
55       * @throws IllegalArgumentException when the length of the <code>directionVector</code> array is not 2
56       */
57      public Ray3d(final double x, final double y, final double z, final double[] directionVector)
58      {
59          super(x, y, z, directionVector);
60      }
61  
62      /**
63       * Construct a new Ray3d.
64       * @param x the x coordinate of the finite end point of the ray
65       * @param y the y coordinate of the finite end point of the ray
66       * @param z the z coordinate of the finite end point of the ray
67       * @param dir the direction
68       * @throws IllegalArgumentException when <code>dirY</code>, or <code>dirZ</code> is <code>NaN</code> (should be impossible)
69       */
70      public Ray3d(final double x, final double y, final double z, final Direction3d dir)
71      {
72          super(x, y, z, dir);
73      }
74  
75      /**
76       * Construct a new Ray3d.
77       * @param x the x coordinate of the finite end point of the ray
78       * @param y the y coordinate of the finite end point of the ray
79       * @param z the z coordinate of the finite end point of the ray
80       * @param throughX the x coordinate of another point on the ray
81       * @param throughY the y coordinate of another point on the ray
82       * @param throughZ the z coordinate of another point on the ray
83       * @throws ArithmeticException when <code>throughX</code>, or <code>throughY</code>, or <code>throughZ</code> is
84       *             <code>NaN</code>
85       * @throws IllegalArgumentException when <code>throughX> == x</code> and <code>throughY == y</code> and
86       *             <code>throughZ == z</code>
87       */
88      public Ray3d(final double x, final double y, final double z, final double throughX, final double throughY,
89              final double throughZ)
90      {
91          super(x, y, z, throughX, throughY, throughZ);
92      }
93  
94      /**
95       * Construct a new Ray3d.
96       * @param x the x coordinate of the finite end point of the ray
97       * @param y the y coordinate of the finite end point of the ray
98       * @param z the z coordinate of the finite end point of the ray
99       * @param throughPoint another point on the ray
100      * @throws NullPointerException when <code>throughPoint</code> is <code>null</code>
101      * @throws ArithmeticException when <code>x</code>, <code>y</code>, or <code>z</code> is <code>NaN</code>
102      * @throws IllegalArgumentException when <code>throughPoint</code> is exactly at <code>(x, y)</code>
103      */
104     public Ray3d(final double x, final double y, final double z, final Point3d throughPoint)
105     {
106         super(x, y, z, throughPoint);
107     }
108 
109     /**
110      * Create a new Ray3d from x, y, and z coordinates packed in a double array of three elements and direction dirY,dirZ.
111      * @param xyz the <code>x</code>, <code>y</code> and <code>z</code> coordinates in that order
112      * @param dirY the angle from the positive Z axis direction in radians (the complement of the slope)
113      * @param dirZ the angle from the positive X axis direction in radians
114      * @throws NullPointerException when <code>xyx</code> is <code>null</code>
115      * @throws IllegalArgumentException when the length of the <code>xyz</code> array is not 3, or contains a <code>NaN</code>
116      *             value, or <code>dirY</code>, or <code>dirZ</code> is <code>NaN</code>
117      */
118     public Ray3d(final double[] xyz, final double dirY, final double dirZ)
119     {
120         super(xyz, dirY, dirZ);
121     }
122 
123     /**
124      * Create a new Ray3d from x, y, and z coordinates packed in a double array of three elements and direction dirY,dirZ.
125      * @param xyz the <code>x</code>, <code>y</code> and <code>z</code> coordinates in that order
126      * @param directionVector the two direction angles <code>dirY</code> and <code>dirZ</code> in that order
127      * @throws NullPointerException when <code>xyz</code>, or <code>directionVector</code> is <code>null</code>
128      * @throws ArithmeticException when <code>xyz</code>, or <code>directionVector</code> contains a <code>NaN</code> value
129      * @throws IllegalArgumentException when the length of the <code>xyx</code> is not 3 or the length of the
130      *             <code>directionVector</code> is not 2
131      */
132     public Ray3d(final double[] xyz, final double[] directionVector)
133     {
134         super(xyz, directionVector);
135     }
136 
137     /**
138      * Create a new Rayt3d from x, y, and z coordinates packed in a double array of three elements and a direction specified
139      * using a double array of two elements.
140      * @param xyz the <code>x</code>, <code>y</code> and <code>z</code> coordinates in that order
141      * @param dir the direction
142      * @throws NullPointerException when <code>xyx</code> array or <code>dir</code> is <code>null</code>
143      * @throws ArithmeticException when the <code>xyz</code> arraycontains a <code>NaN</code> value
144      * @throws IllegalArgumentException when the length of the <code>xyx</code> array is not 3
145      */
146     public Ray3d(final double[] xyz, final Direction3d dir)
147     {
148         super(xyz, dir);
149     }
150 
151     /**
152      * Construct a new Ray3d.
153      * @param point the finite end point of the ray
154      * @param dirY the angle from the positive Z axis direction in radians (the complement of the slope)
155      * @param dirZ the angle from the positive X axis direction in radians
156      * @throws NullPointerException when <code>point</code> is <code>null</code>
157      * @throws IllegalArgumentException when <code>dirY</code>, or <code>dirZ</code> is <code>NaN</code>
158      */
159     public Ray3d(final Point3d point, final double dirY, final double dirZ)
160     {
161         super(point, dirY, dirZ);
162     }
163 
164     /**
165      * Construct a new Ray3d.
166      * @param point the finite end point of the ray
167      * @param dir the direction
168      * @throws NullPointerException when <code>point</code> is <code>null</code>, or <code>dir</code> is <code>null</code>
169      */
170     public Ray3d(final Point3d point, final Direction3d dir)
171     {
172         super(point, dir);
173     }
174 
175     /**
176      * Construct a new Ray3d.
177      * @param point the finite end point of the ray
178      * @param throughX the x coordinate of another point on the ray
179      * @param throughY the y coordinate of another point on the ray
180      * @param throughZ the z coordinate of another point on the ray
181      * @throws NullPointerException when <code>point</code> is <code>null</code>
182      * @throws ArithmeticException when <code>throughX</code>, or <code>throughY</code>, or <code>throughZ</code> is
183      *             <code>NaN</code>
184      * @throws IllegalArgumentException when <code>throughX == x</code> and <code>throughY == y</code> and
185      *             <code>throughZ == z</code>
186      */
187     public Ray3d(final Point3d point, final double throughX, final double throughY, final double throughZ)
188     {
189         super(point, throughX, throughY, throughZ);
190     }
191 
192     /**
193      * Construct a new Ray3d.
194      * @param point the finite end point of the ray
195      * @param throughPoint another point on the ray
196      * @throws NullPointerException when <code>point</code> is <code>null</code> or <code>throughPoint</code> is
197      *             <code>null</code>
198      * @throws IllegalArgumentException when <code>throughPoint</code> is exactly at <code>point</code>
199      */
200     public Ray3d(final Point3d point, final Point3d throughPoint) throws NullPointerException, IllegalArgumentException
201     {
202         super(point, throughPoint);
203     }
204 
205     /**
206      * Construct a new Ray3d.
207      * @param directedPoint point and direction of the new Ray3d
208      * @throws NullPointerException when <code>directedPoint</code> is <code>null</code>
209      */
210     public Ray3d(final DirectedPoint3d directedPoint)
211     {
212         this(directedPoint, directedPoint.dirY, directedPoint.dirZ);
213     }
214 
215     @Override
216     public final double getDirY()
217     {
218         return this.dirY;
219     }
220 
221     @Override
222     public final double getDirZ()
223     {
224         return this.dirZ;
225     }
226 
227     @Override
228     public DirectedPoint3d getEndPoint()
229     {
230         return this;
231     }
232 
233     @Override
234     public int size()
235     {
236         return 2;
237     }
238 
239     @Override
240     public Iterator<Point3d> iterator()
241     {
242         double sinDirZ = Math.sin(this.dirZ);
243         double cosDirZ = Math.cos(this.dirZ);
244         double sinDirY = Math.sin(this.dirY);
245         double cosDirY = Math.cos(this.dirY);
246         Point3d[] array = new Point3d[] {this,
247                 new Point3d(cosDirZ * sinDirY == 0 ? this.x : cosDirZ * sinDirY * Double.POSITIVE_INFINITY,
248                         cosDirZ * sinDirZ == 0 ? this.y : cosDirZ * sinDirZ * Double.POSITIVE_INFINITY,
249                         cosDirY == 0 ? this.z : cosDirY * Double.POSITIVE_INFINITY)};
250         return Arrays.stream(array).iterator();
251     }
252 
253     @Override
254     public Bounds3d getBounds()
255     {
256         double sinDirZ = Math.sin(this.dirZ);
257         double cosDirZ = Math.cos(this.dirZ);
258         double sinDirY = Math.sin(this.dirY);
259         double cosDirY = Math.cos(this.dirY);
260         return new Bounds3d(cosDirZ * sinDirY >= 0 ? this.x : Double.NEGATIVE_INFINITY,
261                 cosDirZ * sinDirY <= 0 ? this.x : Double.POSITIVE_INFINITY,
262                 sinDirZ * sinDirY >= 0 ? this.y : Double.NEGATIVE_INFINITY,
263                 sinDirZ * sinDirY <= 0 ? this.y : Double.POSITIVE_INFINITY, cosDirY >= 0 ? this.z : Double.NEGATIVE_INFINITY,
264                 cosDirY <= 0 ? this.z : Double.POSITIVE_INFINITY);
265     }
266 
267     @Override
268     public Ray3d neg()
269     {
270         return new Ray3d(-this.x, -this.y, -this.z, AngleUtil.normalizeAroundZero(this.dirY + Math.PI),
271                 AngleUtil.normalizeAroundZero(this.dirZ + Math.PI));
272     }
273 
274     @Override
275     public Ray3d flip()
276     {
277         return new Ray3d(this.x, this.y, this.z, AngleUtil.normalizeAroundZero(Math.PI - this.dirY),
278                 AngleUtil.normalizeAroundZero(this.dirZ + Math.PI));
279     }
280 
281     @Override
282     public Ray3d getLocationExtended(final double position) throws ArithmeticException, IllegalArgumentException
283     {
284         Throw.whenNaN(position, "position");
285         Throw.when(Double.isInfinite(position), IllegalArgumentException.class, "position must be finite");
286         double sinDirY = Math.sin(this.dirY);
287         double dX = Math.cos(this.dirZ) * sinDirY;
288         double dY = Math.sin(this.dirZ) * sinDirY;
289         double dZ = Math.cos(this.dirY);
290         return new Ray3d(this.x + dX * position, this.y + dY * position, this.z + dZ * position, this.dirY, this.dirZ);
291     }
292 
293     @Override
294     public Point3d closestPointOnRay(final Point3d point) throws NullPointerException
295     {
296         Throw.whenNull(point, "point");
297         double sinDirY = Math.sin(this.dirY);
298         return point.closestPointOnLine(this.x, this.y, this.z, this.x + Math.cos(this.dirZ) * sinDirY,
299                 this.y + Math.sin(this.dirZ) * sinDirY, this.z + Math.cos(this.dirY), true, false);
300     }
301 
302     @Override
303     public Point3d projectOrthogonal(final Point3d point) throws NullPointerException
304     {
305         Throw.whenNull(point, "point");
306         double sinDirY = Math.sin(this.dirY);
307         return point.closestPointOnLine(this.x, this.y, this.z, this.x + Math.cos(this.dirZ) * sinDirY,
308                 this.y + Math.sin(this.dirZ) * sinDirY, this.z + Math.cos(this.dirY), null, false);
309     }
310 
311     @Override
312     public Point3d projectOrthogonalExtended(final Point3d point)
313     {
314         Throw.whenNull(point, "point");
315         double sinDirY = Math.sin(this.dirY);
316         return point.closestPointOnLine(getX(), getY(), getZ(), getX() + Math.cos(this.dirZ) * sinDirY,
317                 getY() + Math.sin(this.dirZ) * sinDirY, getZ() + Math.cos(this.dirY), false, false);
318     }
319 
320     @Override
321     public double projectOrthogonalFractional(final Point3d point) throws NullPointerException
322     {
323         Throw.whenNull(point, "point");
324         double sinDirY = Math.sin(this.dirY);
325         return point.fractionalPositionOnLine(this.x, this.y, this.z, this.x + Math.cos(this.dirZ) * sinDirY,
326                 this.y + Math.sin(this.dirZ) * sinDirY, this.z + Math.cos(this.dirY), null, false);
327     }
328 
329     @Override
330     public double projectOrthogonalFractionalExtended(final Point3d point) throws NullPointerException
331     {
332         Throw.whenNull(point, "point");
333         double sinDirY = Math.sin(this.dirY);
334         return point.fractionalPositionOnLine(getX(), getY(), getZ(), getX() + Math.cos(this.dirZ) * sinDirY,
335                 getY() + Math.sin(this.dirZ) * sinDirY, getZ() + Math.cos(this.dirY), false, false);
336     }
337 
338     @Override
339     public String toString()
340     {
341         return toString("%f", false);
342     }
343 
344     @Override
345     public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
346     {
347         String format = String.format("%1$s[x=%2$s, y=%2$s, z=%2$s, dirY=%2$s, dirZ=%2$s]",
348                 doNotIncludeClassName ? "" : "Ray3d ", doubleFormat);
349         return String.format(Locale.US, format, this.x, this.y, this.z, this.dirY, this.dirZ);
350     }
351 
352     @Override
353     public int hashCode()
354     {
355         return super.hashCode();
356     }
357 
358     @Override
359     @SuppressWarnings("checkstyle:needbraces")
360     public boolean equals(final Object obj)
361     {
362         if (this == obj)
363             return true;
364         if (!super.equals(obj))
365             return false;
366         if (getClass() != obj.getClass())
367             return false;
368         return true;
369     }
370 
371 }