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 }