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.base.AngleUtil;
8 import org.djutils.draw.DrawRuntimeException;
9 import org.djutils.draw.Drawable3d;
10 import org.djutils.draw.Space3d;
11 import org.djutils.draw.bounds.Bounds3d;
12 import org.djutils.draw.point.Point3d;
13 import org.djutils.exceptions.Throw;
14
15
16
17
18
19
20
21
22
23
24 public class Ray3d extends Point3d implements Drawable3d, Ray<Ray3d, Point3d, Space3d>
25 {
26
27 private static final long serialVersionUID = 20210119L;
28
29
30 @SuppressWarnings("checkstyle:visibilitymodifier")
31 public final double phi;
32
33
34 @SuppressWarnings("checkstyle:visibilitymodifier")
35 public final double theta;
36
37
38
39
40
41
42
43
44
45
46 public Ray3d(final double x, final double y, final double z, final double phi, final double theta)
47 throws DrawRuntimeException
48 {
49 super(x, y, z);
50 Throw.when(Double.isNaN(phi) || Double.isNaN(theta), DrawRuntimeException.class, "phi and theta may not be NaN");
51 this.phi = phi;
52 this.theta = theta;
53 }
54
55
56
57
58
59
60
61
62
63 public Ray3d(final Point3d point, final double phi, final double theta) throws NullPointerException, DrawRuntimeException
64 {
65 this(Throw.whenNull(point, "point may not be null").x, point.y, point.z, phi, theta);
66 }
67
68
69
70
71
72
73
74
75
76
77
78 public Ray3d(final double x, final double y, final double z, final double throughX, final double throughY,
79 final double throughZ) throws DrawRuntimeException
80 {
81 super(x, y, z);
82 Throw.when(throughX == x && throughY == y && throughZ == z, DrawRuntimeException.class,
83 "the coordinates of the through point must differ from (x, y, z)");
84 this.phi = Math.atan2(throughY - y, throughX - x);
85 this.theta = Math.atan2(Math.hypot(throughX - x, throughY - y), throughZ - z);
86 }
87
88
89
90
91
92
93
94
95
96
97 public Ray3d(final Point3d point, final double throughX, final double throughY, final double throughZ)
98 throws NullPointerException, DrawRuntimeException
99 {
100 this(Throw.whenNull(point, "point may not be null").x, point.y, point.z, throughX, throughY, throughZ);
101 }
102
103
104
105
106
107
108
109
110
111
112 public Ray3d(final double x, final double y, final double z, final Point3d throughPoint)
113 throws NullPointerException, DrawRuntimeException
114 {
115 this(x, y, z, Throw.whenNull(throughPoint, "througPoint may not be null").x, throughPoint.y, throughPoint.z);
116 }
117
118
119
120
121
122
123
124
125 public Ray3d(final Point3dd.html#Point3d">Point3d point, final Point3d throughPoint) throws NullPointerException, DrawRuntimeException
126 {
127 this(Throw.whenNull(point, "point may not be null").x, point.y, point.z,
128 Throw.whenNull(throughPoint, "throughPoint may not be null").x, throughPoint.y, throughPoint.z);
129 }
130
131
132 @Override
133 public final double getPhi()
134 {
135 return this.phi;
136 }
137
138
139
140
141
142 public final double getTheta()
143 {
144 return this.theta;
145 }
146
147
148 @Override
149 public Point3d getEndPoint()
150 {
151 return new Point3d(this.x, this.y, this.z);
152 }
153
154
155 @Override
156 public int size()
157 {
158 return 2;
159 }
160
161
162 @Override
163 public Iterator<Point3d> getPoints()
164 {
165 double sinPhi = Math.sin(this.phi);
166 double cosPhi = Math.cos(this.phi);
167 double sinTheta = Math.sin(this.theta);
168 double cosTheta = Math.cos(this.theta);
169 Point3doint3d.html#Point3d">Point3d.html#Point3d">Point3d[] array = new Point3doint3d.html#Point3d">Point3d[] { new Point3d(this.x, this.y, this.z),
170 new Point3d(cosPhi * sinTheta == 0 ? this.x : cosPhi * sinTheta * Double.POSITIVE_INFINITY,
171 cosPhi * sinPhi == 0 ? this.y : cosPhi * sinPhi * Double.POSITIVE_INFINITY,
172 cosTheta == 0 ? this.z : cosTheta * Double.POSITIVE_INFINITY) };
173 return Arrays.stream(array).iterator();
174 }
175
176
177 @Override
178 public Bounds3d getBounds()
179 {
180 double sinPhi = Math.sin(this.phi);
181 double cosPhi = Math.cos(this.phi);
182 double sinTheta = Math.sin(this.theta);
183 double cosTheta = Math.cos(this.theta);
184 return new Bounds3d(cosPhi * sinTheta >= 0 ? this.x : Double.NEGATIVE_INFINITY,
185 cosPhi * sinTheta <= 0 ? this.x : Double.POSITIVE_INFINITY,
186 sinPhi * sinTheta >= 0 ? this.y : Double.NEGATIVE_INFINITY,
187 sinPhi * sinTheta <= 0 ? this.y : Double.POSITIVE_INFINITY, cosTheta >= 0 ? this.z : Double.NEGATIVE_INFINITY,
188 cosTheta <= 0 ? this.z : Double.POSITIVE_INFINITY);
189 }
190
191
192 @Override
193 public Ray3d neg()
194 {
195 return new Ray3d(-this.x, -this.y, -this.z, this.phi + Math.PI, this.theta + Math.PI);
196 }
197
198
199 @Override
200 public Ray3d flip()
201 {
202 return new Ray3d(this.x, this.y, this.z, this.phi + Math.PI, Math.PI - this.theta);
203 }
204
205
206 @Override
207 public Ray3d getLocationExtended(final double position) throws DrawRuntimeException
208 {
209 Throw.when(Double.isNaN(position) || Double.isInfinite(position), DrawRuntimeException.class,
210 "position must be finite");
211 double sinTheta = Math.sin(this.theta);
212 double dX = Math.cos(this.phi) * sinTheta;
213 double dY = Math.sin(this.phi) * sinTheta;
214 double dZ = Math.cos(this.theta);
215 return new Ray3d(this.x + dX * position, this.y + dY * position, this.z + dZ * position, this.phi, this.theta);
216 }
217
218
219 @Override
220 public Point3dt3d">Point3d closestPointOnRay(final Point3d point) throws NullPointerException
221 {
222 Throw.whenNull(point, "point may not be null");
223 double sinTheta = Math.sin(this.theta);
224 return point.closestPointOnLine(this.x, this.y, this.z, this.x + Math.cos(this.phi) * sinTheta,
225 this.y + Math.sin(this.phi) * sinTheta, this.z + Math.cos(this.theta), true, false);
226 }
227
228
229 @Override
230 public Point3dt3d">Point3d projectOrthogonal(final Point3d point) throws NullPointerException
231 {
232 Throw.whenNull(point, "point may not be null");
233 double sinTheta = Math.sin(this.theta);
234 return point.closestPointOnLine(this.x, this.y, this.z, this.x + Math.cos(this.phi) * sinTheta,
235 this.y + Math.sin(this.phi) * sinTheta, this.z + Math.cos(this.theta), null, false);
236 }
237
238
239 @Override
240 public Point3dnt3d projectOrthogonalExtended(final Point3d point)
241 {
242 Throw.whenNull(point, "point may not be null");
243 double sinTheta = Math.sin(this.theta);
244 return point.closestPointOnLine(getX(), getY(), getZ(), getX() + Math.cos(this.phi) * sinTheta,
245 getY() + Math.sin(this.phi) * sinTheta, getZ() + Math.cos(this.theta), false, false);
246 }
247
248
249 @Override
250 public double projectOrthogonalFractional(final Point3d point) throws NullPointerException
251 {
252 Throw.whenNull(point, "point may not be null");
253 double sinTheta = Math.sin(this.theta);
254 return point.fractionalPositionOnLine(this.x, this.y, this.z, this.x + Math.cos(this.phi) * sinTheta,
255 this.y + Math.sin(this.phi) * sinTheta, this.z + Math.cos(this.theta), null, false);
256 }
257
258
259 @Override
260 public double projectOrthogonalFractionalExtended(final Point3d point) throws NullPointerException
261 {
262 Throw.whenNull(point, "point may not be null");
263 double sinTheta = Math.sin(this.theta);
264 return point.fractionalPositionOnLine(getX(), getY(), getZ(), getX() + Math.cos(this.phi) * sinTheta,
265 getY() + Math.sin(this.phi) * sinTheta, getZ() + Math.cos(this.theta), false, false);
266 }
267
268
269 @Override
270 public boolean epsilonEquals(final Ray3d other, final double epsilonCoordinate, final double epsilonRotation)
271 throws NullPointerException, IllegalArgumentException
272 {
273 Throw.whenNull(other, "other point may not be null");
274 Throw.when(
275 Double.isNaN(epsilonCoordinate) || epsilonCoordinate < 0 || Double.isNaN(epsilonRotation)
276 || epsilonRotation < 0,
277 IllegalArgumentException.class, "epsilon values may not be negative and may not be NaN");
278 if (Math.abs(this.x - other.x) > epsilonCoordinate)
279 {
280 return false;
281 }
282 if (Math.abs(this.y - other.y) > epsilonCoordinate)
283 {
284 return false;
285 }
286 if (Math.abs(this.z - other.z) > epsilonCoordinate)
287 {
288 return false;
289 }
290 if ((Math.abs(AngleUtil.normalizeAroundZero(this.phi - other.phi)) > epsilonRotation
291 || Math.abs(AngleUtil.normalizeAroundZero(this.theta - other.theta)) > epsilonRotation)
292 && (Math.abs(AngleUtil.normalizeAroundZero(Math.PI + this.phi - other.phi)) > epsilonRotation
293 || Math.abs(AngleUtil.normalizeAroundZero(Math.PI - this.theta - other.theta)) > epsilonRotation))
294 {
295 return false;
296 }
297 return true;
298
299 }
300
301
302 @Override
303 public String toString()
304 {
305 return toString("%f", false);
306 }
307
308
309 @Override
310 public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
311 {
312 String format = String.format("%1$s[x=%2$s, y=%2$s, z=%2$s, phi=%2$s, theta=%2$s]", doNotIncludeClassName ? "" : "Ray3d ",
313 doubleFormat);
314 return String.format(Locale.US, format, this.x, this.y, this.z, this.phi, this.theta);
315 }
316
317
318 @Override
319 public int hashCode()
320 {
321 final int prime = 31;
322 int result = super.hashCode();
323 long temp;
324 temp = Double.doubleToLongBits(this.phi);
325 result = prime * result + (int) (temp ^ (temp >>> 32));
326 temp = Double.doubleToLongBits(this.theta);
327 result = prime * result + (int) (temp ^ (temp >>> 32));
328 return result;
329 }
330
331
332 @Override
333 @SuppressWarnings("checkstyle:needbraces")
334 public boolean equals(final Object obj)
335 {
336 if (this == obj)
337 return true;
338 if (!super.equals(obj))
339 return false;
340 if (getClass() != obj.getClass())
341 return false;
342 Ray3d="../../../../org/djutils/draw/line/Ray3d.html#Ray3d">Ray3d other = (Ray3d) obj;
343 if (Double.doubleToLongBits(this.phi) != Double.doubleToLongBits(other.phi))
344 return false;
345 if (Double.doubleToLongBits(this.theta) != Double.doubleToLongBits(other.theta))
346 return false;
347 return true;
348 }
349
350 }