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