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 getAbsoluteBounds()
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 }