1 package org.djutils.draw.point;
2
3 import java.util.Arrays;
4 import java.util.Iterator;
5 import java.util.Locale;
6 import java.util.Objects;
7
8 import org.djutils.base.AngleUtil;
9 import org.djutils.draw.Directed3d;
10 import org.djutils.draw.DrawRuntimeException;
11 import org.djutils.exceptions.Throw;
12
13
14
15
16
17
18
19
20
21
22
23
24 public class DirectedPoint3d extends Point3d implements Directed3d<DirectedPoint3d>
25 {
26
27 private static final long serialVersionUID = 20200828L;
28
29
30 @SuppressWarnings("checkstyle:visibilitymodifier")
31 public final double dirY;
32
33
34 @SuppressWarnings("checkstyle:visibilitymodifier")
35 public final double dirZ;
36
37
38
39
40
41
42
43
44
45
46 public DirectedPoint3d(final double x, final double y, final double z, final double dirY, final double dirZ)
47 throws IllegalArgumentException
48 {
49 super(x, y, z);
50 Throw.when(Double.isNaN(dirY) || Double.isNaN(dirZ), IllegalArgumentException.class,
51 "dirY and dirZ must be numbers (not NaN)");
52 this.dirZ = dirZ;
53 this.dirY = dirY;
54 }
55
56
57
58
59
60
61
62
63
64
65 public DirectedPoint3d(final double[] xyz, final double dirY, final double dirZ)
66 throws NullPointerException, IllegalArgumentException
67 {
68 super(xyz);
69 Throw.when(Double.isNaN(dirY) || Double.isNaN(dirZ), IllegalArgumentException.class,
70 "dirY and dirZ must be numbers (not NaN)");
71 this.dirY = dirY;
72 this.dirZ = dirZ;
73 }
74
75
76
77
78
79
80
81
82 public DirectedPoint3d(final Point3d point, final double dirY, final double dirZ) throws IllegalArgumentException
83 {
84 this(point.x, point.y, point.z, dirY, dirZ);
85 }
86
87
88
89
90
91
92
93
94
95
96
97
98 public DirectedPoint3d(final double x, final double y, final double z, final double throughX, final double throughY,
99 final double throughZ) throws DrawRuntimeException
100 {
101 this(x, y, z, buildDirectionVector(throughX - x, throughY - y, throughZ - z));
102 }
103
104
105
106
107
108
109
110
111 private static double[] buildDirectionVector(final double dX, final double dY, final double dZ)
112 {
113 Throw.when(0 == dX && 0 == dY && 0 == dZ, IllegalArgumentException.class, "Through point may not be equal to point");
114 return new double[] {Math.atan2(Math.hypot(dX, dY), dZ), Math.atan2(dY, dX)};
115 }
116
117
118
119
120
121
122
123
124
125
126 public DirectedPoint3d(final Point3d point, final double throughX, final double throughY, final double throughZ)
127 throws DrawRuntimeException
128 {
129 this(Throw.whenNull(point, "point").x, point.y, point.z, throughX, throughY, throughZ);
130 }
131
132
133
134
135
136
137
138
139 private static double checkOrientationVector(final double[] orientation)
140 throws NullPointerException, IllegalArgumentException
141 {
142 Throw.when(orientation.length != 2, IllegalArgumentException.class, "length of orientation array must be 2");
143 return orientation[0];
144 }
145
146
147
148
149
150
151
152
153
154
155
156
157
158 public DirectedPoint3d(final double x, final double y, final double z, final double[] orientation)
159 throws NullPointerException, IllegalArgumentException
160 {
161 this(x, y, z, checkOrientationVector(orientation), orientation[1]);
162 }
163
164
165
166
167
168
169
170
171
172
173 public DirectedPoint3d(final double[] xyz, final double[] orientation) throws NullPointerException, IllegalArgumentException
174 {
175 this(xyz, checkOrientationVector(orientation), orientation[1]);
176 }
177
178
179
180
181
182
183
184
185
186
187 public DirectedPoint3d(final double x, final double y, final double z, final Point3d throughPoint)
188 throws NullPointerException, DrawRuntimeException
189 {
190 this(x, y, z, Throw.whenNull(throughPoint, "througPoint").x, throughPoint.y, throughPoint.z);
191 }
192
193
194
195
196
197
198
199
200 public DirectedPoint3d(final Point3d point, final Point3d throughPoint) throws NullPointerException, DrawRuntimeException
201 {
202 this(Throw.whenNull(point, "point").x, point.y, point.z, Throw.whenNull(throughPoint, "throughPoint").x, throughPoint.y,
203 throughPoint.z);
204 }
205
206 @Override
207 public DirectedPoint3d translate(final double dx, final double dy) throws IllegalArgumentException
208 {
209 Throw.when(Double.isNaN(dx) || Double.isNaN(dy), IllegalArgumentException.class, "translation may not contain NaN");
210 return new DirectedPoint3d(this.x + dx, this.y + dy, this.z, this.dirY, this.dirZ);
211 }
212
213 @Override
214 public DirectedPoint3d translate(final double dx, final double dy, final double dz) throws IllegalArgumentException
215 {
216 Throw.when(Double.isNaN(dx) || Double.isNaN(dy) || Double.isNaN(dz), IllegalArgumentException.class,
217 "Translation may not contain NaN");
218 return new DirectedPoint3d(this.x + dx, this.y + dy, this.z + dz, this.dirY, this.dirZ);
219 }
220
221 @Override
222 public DirectedPoint3d scale(final double factor) throws IllegalArgumentException
223 {
224 return new DirectedPoint3d(this.x * factor, this.y * factor, this.z * factor, this.dirY, this.dirZ);
225 }
226
227 @Override
228 public DirectedPoint3d neg()
229 {
230 return new DirectedPoint3d(-this.x, -this.y, -this.z, AngleUtil.normalizeAroundZero(this.dirY + Math.PI),
231 AngleUtil.normalizeAroundZero(this.dirZ + Math.PI));
232 }
233
234 @Override
235 public DirectedPoint3d abs()
236 {
237 return new DirectedPoint3d(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z), this.dirY, this.dirZ);
238 }
239
240 @Override
241 public DirectedPoint3d normalize() throws DrawRuntimeException
242 {
243 double length = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
244 Throw.when(length == 0.0, DrawRuntimeException.class, "cannot normalize (0.0, 0.0, 0.0)");
245 return new DirectedPoint3d(this.x / length, this.y / length, this.z / length, this.dirY, this.dirZ);
246 }
247
248
249
250
251
252
253
254
255
256
257
258
259
260 public DirectedPoint3d interpolate(final DirectedPoint3d otherPoint, final double fraction)
261 throws NullPointerException, IllegalArgumentException
262 {
263 Throw.whenNull(otherPoint, "otherPoint");
264 Throw.when(Double.isNaN(fraction), IllegalArgumentException.class, "fraction must be a number (not NaN)");
265 if (0.0 == fraction)
266 {
267 return this;
268 }
269 if (1.0 == fraction)
270 {
271 return otherPoint;
272 }
273 return new DirectedPoint3d((1.0 - fraction) * this.x + fraction * otherPoint.x,
274 (1.0 - fraction) * this.y + fraction * otherPoint.y, (1.0 - fraction) * this.z + fraction * otherPoint.z,
275 AngleUtil.interpolateShortest(this.dirY, otherPoint.dirY, fraction),
276 AngleUtil.interpolateShortest(this.dirZ, otherPoint.dirZ, fraction));
277 }
278
279
280
281
282
283
284
285
286 public DirectedPoint3d rotate(final double rotateZ) throws IllegalArgumentException
287 {
288 Throw.when(Double.isNaN(rotateZ), IllegalArgumentException.class, "rotateZ must be a number (not NaN)");
289 return new DirectedPoint3d(this.x, this.y, this.z, this.dirY, AngleUtil.normalizeAroundZero(this.dirZ + rotateZ));
290 }
291
292
293
294
295
296
297
298
299
300 public DirectedPoint3d rotate(final double rotateY, final double rotateZ) throws IllegalArgumentException
301 {
302 Throw.when(Double.isNaN(rotateY) || Double.isNaN(rotateZ), IllegalArgumentException.class,
303 "rotateY and rotateZ must be a numbers (not NaN)");
304 return new DirectedPoint3d(this.x, this.y, this.z, AngleUtil.normalizeAroundZero(this.dirY + rotateY),
305 AngleUtil.normalizeAroundZero(this.dirZ + rotateZ));
306 }
307
308 @Override
309 public double getDirZ()
310 {
311 return this.dirZ;
312 }
313
314 @Override
315 public double getDirY()
316 {
317 return this.dirY;
318 }
319
320 @Override
321 public Iterator<? extends DirectedPoint3d> getPoints()
322 {
323 return Arrays.stream(new DirectedPoint3d[] {this}).iterator();
324 }
325
326 @Override
327 public String toString()
328 {
329 return toString("%f", false);
330 }
331
332 @Override
333 public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
334 {
335 String format = String.format("%1$s[x=%2$s, y=%2$s, z=%2%s, dirY=%2$s, dirZ=%2$s]",
336 doNotIncludeClassName ? "" : "DirectedPoint3d ", doubleFormat);
337 return String.format(Locale.US, format, this.x, this.y, this.z, this.dirY, this.dirZ);
338 }
339
340 @Override
341 public boolean epsilonEquals(final DirectedPoint3d other, final double epsilonCoordinate, final double epsilonRotation)
342 throws NullPointerException, IllegalArgumentException
343 {
344 Throw.whenNull(other, "other");
345 Throw.when(epsilonCoordinate < 0 || epsilonRotation < 0, IllegalArgumentException.class,
346 "epsilonCoordinate and epsilonRotation may not be negative");
347 Throw.when(Double.isNaN(epsilonCoordinate) || Double.isNaN(epsilonRotation), IllegalArgumentException.class,
348 "epsilonCoordinate and epsilonRotation may not be NaN");
349 if (Math.abs(this.x - other.x) > epsilonCoordinate)
350 {
351 return false;
352 }
353 if (Math.abs(this.y - other.y) > epsilonCoordinate)
354 {
355 return false;
356 }
357 if (Math.abs(this.z - other.z) > epsilonCoordinate)
358 {
359 return false;
360 }
361 if (Math.abs(AngleUtil.normalizeAroundZero(this.dirZ - other.dirZ)) > epsilonRotation)
362 {
363 return false;
364 }
365 if (Math.abs(AngleUtil.normalizeAroundZero(this.dirY - other.dirY)) > epsilonRotation)
366 {
367 return false;
368 }
369 return true;
370 }
371
372 @Override
373 public int hashCode()
374 {
375 final int prime = 31;
376 int result = super.hashCode();
377 result = prime * result + Objects.hash(this.dirZ, this.dirY);
378 return result;
379 }
380
381 @Override
382 @SuppressWarnings("checkstyle:needbraces")
383 public boolean equals(final Object obj)
384 {
385 if (this == obj)
386 return true;
387 if (!super.equals(obj))
388 return false;
389 if (getClass() != obj.getClass())
390 return false;
391 DirectedPoint3d other = (DirectedPoint3d) obj;
392 return Double.doubleToLongBits(this.dirZ) == Double.doubleToLongBits(other.dirZ)
393 && Double.doubleToLongBits(this.dirY) == Double.doubleToLongBits(other.dirY);
394 }
395
396 }