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