1 package org.djutils.draw.point;
2
3 import java.awt.geom.Point2D;
4 import java.util.Arrays;
5 import java.util.Iterator;
6 import java.util.Locale;
7
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.exceptions.Throw;
13
14
15
16
17
18
19
20
21
22
23
24 public class Point3d implements Drawable3d, Point<Point3d, Space3d>
25 {
26
27 private static final long serialVersionUID = 20201201L;
28
29
30 @SuppressWarnings("checkstyle:visibilitymodifier")
31 public final double x;
32
33
34 @SuppressWarnings("checkstyle:visibilitymodifier")
35 public final double y;
36
37
38 @SuppressWarnings("checkstyle:visibilitymodifier")
39 public final double z;
40
41
42
43
44
45
46
47
48 public Point3d(final double x, final double y, final double z)
49 {
50 Throw.when(Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z), IllegalArgumentException.class,
51 "Coordinate must be a number (not NaN)");
52 this.x = x;
53 this.y = y;
54 this.z = z;
55 }
56
57
58
59
60
61
62
63 public Point3d(final double[] xyz) throws NullPointerException, IllegalArgumentException
64 {
65 this(checkLengthIsThree(Throw.whenNull(xyz, "xyz-point cannot be null"))[0], xyz[1], xyz[2]);
66 }
67
68
69
70
71
72
73
74
75 public Point3d(final Point2d point, final double z) throws NullPointerException, IllegalArgumentException
76 {
77 Throw.whenNull(point, "point cannot be null");
78 Throw.when(Double.isNaN(z), IllegalArgumentException.class, "Coordinate must be a number (not NaN)");
79 this.x = point.x;
80 this.y = point.y;
81 this.z = z;
82 }
83
84
85
86
87
88
89
90
91 public Point3d(final Point2D point, final double z) throws NullPointerException, IllegalArgumentException
92 {
93 Throw.whenNull(point, "point cannot be null");
94 Throw.when(Double.isNaN(point.getX()) || Double.isNaN(point.getY()), IllegalArgumentException.class,
95 "Coordinate must be a number (not NaN)");
96 Throw.when(Double.isNaN(z), IllegalArgumentException.class, "Coordinate must be a number (not NaN)");
97 this.x = point.getX();
98 this.y = point.getY();
99 this.z = z;
100 }
101
102
103
104
105
106
107
108 private static double[] checkLengthIsThree(final double[] xyz) throws IllegalArgumentException
109 {
110 Throw.when(xyz.length != 3, IllegalArgumentException.class, "Length of xy-array must be 2");
111 return xyz;
112 }
113
114
115 @Override
116 public final double getX()
117 {
118 return this.x;
119 }
120
121
122 @Override
123 public final double getY()
124 {
125 return this.y;
126 }
127
128
129
130
131
132 public final double getZ()
133 {
134 return this.z;
135 }
136
137
138 @Override
139 public double distanceSquared(final Point3d otherPoint) throws NullPointerException
140 {
141 Throw.whenNull(otherPoint, "point cannot be null");
142 double dx = this.x - otherPoint.x;
143 double dy = this.y - otherPoint.y;
144 double dz = this.z - otherPoint.z;
145 return dx * dx + dy * dy + dz * dz;
146 }
147
148
149 @Override
150 public double distance(final Point3d otherPoint) throws NullPointerException
151 {
152 Throw.whenNull(otherPoint, "point cannot be null");
153 return Math.sqrt(distanceSquared(otherPoint));
154 }
155
156
157 @Override
158 public int size()
159 {
160 return 1;
161 }
162
163
164 @Override
165 public Iterator<? extends Point3d> getPoints()
166 {
167 return Arrays.stream(new Point3d[] {this}).iterator();
168 }
169
170
171 @Override
172 public Point2d project() throws DrawRuntimeException
173 {
174 return new Point2d(this.x, this.y);
175 }
176
177
178
179
180
181
182
183
184 public Point3d translate(final double dx, final double dy) throws IllegalArgumentException
185 {
186 Throw.when(Double.isNaN(dx) || Double.isNaN(dy), IllegalArgumentException.class,
187 "Translation must be number (not NaN)");
188 return new Point3d(this.x + dx, this.y + dy, this.z);
189 }
190
191
192
193
194
195
196
197
198
199 public Point3d translate(final double dx, final double dy, final double dz) throws IllegalArgumentException
200 {
201 Throw.when(Double.isNaN(dx) || Double.isNaN(dy) || Double.isNaN(dz), IllegalArgumentException.class,
202 "dx, dy and dz must be numbers (not NaN)");
203 return new Point3d(this.x + dx, this.y + dy, this.z + dz);
204 }
205
206
207 @Override
208 public Point3d scale(final double factor) throws IllegalArgumentException
209 {
210 Throw.when(Double.isNaN(factor), IllegalArgumentException.class, "factor must be a number (not NaN)");
211 return new Point3d(this.x * factor, this.y * factor, this.z * factor);
212 }
213
214
215 @Override
216 public Point3d neg()
217 {
218 return scale(-1.0);
219 }
220
221
222 @Override
223 public Point3d abs()
224 {
225 return new Point3d(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z));
226 }
227
228
229 @Override
230 public Point3d normalize() throws DrawRuntimeException
231 {
232 double length = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
233 Throw.when(length == 0.0, DrawRuntimeException.class, "cannot normalize (0.0, 0.0, 0.0)");
234 return this.scale(1.0 / length);
235 }
236
237
238 @Override
239 public Point3dl#Point3d">Point3d interpolate(final Point3d point, final double fraction)
240 {
241 Throw.whenNull(point, "point cannot be null");
242 Throw.when(Double.isNaN(fraction), IllegalArgumentException.class, "fraction must be a number (not NaN)");
243 return new Point3d((1.0 - fraction) * this.x + fraction * point.x, (1.0 - fraction) * this.y + fraction * point.y,
244 (1.0 - fraction) * this.z + fraction * point.z);
245
246 }
247
248
249 @Override
250 public boolean epsilonEquals(final Point3d other, final double epsilon)
251 {
252 Throw.whenNull(other, "other point cannot be null");
253 if (Math.abs(this.x - other.x) > epsilon)
254 {
255 return false;
256 }
257 if (Math.abs(this.y - other.y) > epsilon)
258 {
259 return false;
260 }
261 if (Math.abs(this.z - other.z) > epsilon)
262 {
263 return false;
264 }
265 return true;
266 }
267
268
269 @Override
270 public Bounds3d getBounds()
271 {
272 return new Bounds3d(this);
273 }
274
275
276 @Override
277 public final Point3doint3d">Point3d>Point3d closestPointOnSegment(final Point3doint3d">Point3d segmentPoint1, final Point3d segmentPoint2)
278 {
279 Throw.whenNull(segmentPoint1, "segmentPoint1 may not be null");
280 Throw.whenNull(segmentPoint2, "segmentPoint2 may not be null");
281 return closestPointOnSegment(segmentPoint1.x, segmentPoint1.y, segmentPoint1.z, segmentPoint2.x, segmentPoint2.y,
282 segmentPoint2.z);
283 }
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302 @SuppressWarnings("checkstyle:parameternumber")
303 public Point3d closestPointOnLine(final double p1X, final double p1Y, final double p1Z, final double p2X, final double p2Y,
304 final double p2Z, final Boolean lowLimitHandling, final Boolean highLimitHandling) throws DrawRuntimeException
305 {
306 double fraction = fractionalPositionOnLine(p1X, p1Y, p1Z, p2X, p2Y, p2Z, lowLimitHandling, highLimitHandling);
307 if (Double.isNaN(fraction))
308 {
309 return null;
310 }
311 if (fraction == 1.0)
312 {
313 return new Point3d(p2X, p2Y, p2Z);
314 }
315 return new Point3d(p1X + fraction * (p2X - p1X), p1Y + fraction * (p2Y - p1Y), p1Z + fraction * (p2Z - p1Z));
316 }
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338 @SuppressWarnings("checkstyle:parameternumber")
339 public double fractionalPositionOnLine(final double p1X, final double p1Y, final double p1Z, final double p2X,
340 final double p2Y, final double p2Z, final Boolean lowLimitHandling, final Boolean highLimitHandling)
341 throws DrawRuntimeException
342 {
343 double dX = p2X - p1X;
344 double dY = p2Y - p1Y;
345 double dZ = p2Z - p1Z;
346 Throw.when(Double.isNaN(dX) || Double.isNaN(dY) || Double.isNaN(dZ), DrawRuntimeException.class,
347 "NaN values not permitted");
348 if (0 == dX && 0 == dY && 0 == dZ)
349 {
350 return 0.0;
351 }
352 double fraction = ((this.x - p1X) * dX + (this.y - p1Y) * dY + (this.z - p1Z) * dZ) / (dX * dX + dY * dY + dZ * dZ);
353 if (fraction < 0.0)
354 {
355 if (lowLimitHandling == null)
356 {
357 return Double.NaN;
358 }
359 if (lowLimitHandling)
360 {
361 fraction = 0.0;
362 }
363 }
364 else if (fraction > 1.0)
365 {
366 if (highLimitHandling == null)
367 {
368 return Double.NaN;
369 }
370 if (highLimitHandling)
371 {
372 fraction = 1.0;
373 }
374 }
375 return fraction;
376 }
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393 public final Point3d closestPointOnSegment(final double p1X, final double p1Y, final double p1Z, final double p2X,
394 final double p2Y, final double p2Z) throws DrawRuntimeException
395 {
396 return closestPointOnLine(p1X, p1Y, p1Z, p2X, p2Y, p2Z, true, true);
397 }
398
399
400 @Override
401 public final Point3dl#Point3d">Point3d3d">Point3d closestPointOnLine(final Point3dl#Point3d">Point3d linePoint1, final Point3d linePoint2)
402 throws NullPointerException, DrawRuntimeException
403 {
404 Throw.whenNull(linePoint1, "linePoint1 may not be null");
405 Throw.whenNull(linePoint2, "linePoint2 may not be null");
406 return closestPointOnLine(linePoint1.x, linePoint1.y, linePoint1.z, linePoint2.x, linePoint2.y, linePoint2.z);
407 }
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422 public final Point3d closestPointOnLine(final double p1X, final double p1Y, final double p1Z, final double p2X,
423 final double p2Y, final double p2Z) throws DrawRuntimeException
424 {
425 Throw.when(p1X == p2X && p1Y == p2Y && p1Z == p2Z, DrawRuntimeException.class, "degenerate line not allowed");
426 return closestPointOnLine(p1X, p1Y, p1Z, p2X, p2Y, p2Z, false, false);
427 }
428
429
430
431
432
433 final double horizontalDirection()
434 {
435 return Math.atan2(this.y, this.x);
436 }
437
438
439
440
441
442
443
444 final double horizontalDirection(final Point3d point) throws NullPointerException
445 {
446 Throw.whenNull(point, "point cannot be null");
447 return Math.atan2(point.y - this.y, point.x - this.x);
448 }
449
450
451
452
453
454
455
456 final double verticalDirection(final Point3d point) throws NullPointerException
457 {
458 Throw.whenNull(point, "point cannot be null");
459 return Math.atan2(Math.hypot(point.y - this.y, point.x - this.x), point.z - this.z);
460 }
461
462
463
464
465
466
467
468 final double horizontalDistanceSquared(final Point3d point)
469 {
470 Throw.whenNull(point, "point cannot be null");
471 double dx = this.x - point.x;
472 double dy = this.y - point.y;
473 return dx * dx + dy * dy;
474 }
475
476
477
478
479
480
481
482 final double horizontalDistance(final Point3d point)
483 {
484 return Math.sqrt(horizontalDistanceSquared(point));
485 }
486
487
488 @Override
489 @SuppressWarnings("checkstyle:designforextension")
490 public String toString()
491 {
492 return toString("%f");
493 }
494
495
496 @Override
497 public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
498 {
499 String format = String.format("%1$s[x=%2$s, y=%2$s, z=%2$s]", doNotIncludeClassName ? "" : "Point3d ", doubleFormat);
500 return String.format(Locale.US, format, this.x, this.y, this.z);
501 }
502
503
504 @Override
505 public int hashCode()
506 {
507 final int prime = 31;
508 int result = 1;
509 long temp;
510 temp = Double.doubleToLongBits(this.x);
511 result = prime * result + (int) (temp ^ (temp >>> 32));
512 temp = Double.doubleToLongBits(this.y);
513 result = prime * result + (int) (temp ^ (temp >>> 32));
514 temp = Double.doubleToLongBits(this.z);
515 result = prime * result + (int) (temp ^ (temp >>> 32));
516 return result;
517 }
518
519
520 @SuppressWarnings("checkstyle:needbraces")
521 @Override
522 public boolean equals(final Object obj)
523 {
524 if (this == obj)
525 return true;
526 if (obj == null)
527 return false;
528 if (getClass() != obj.getClass())
529 return false;
530 Point3d../../../../org/djutils/draw/point/Point3d.html#Point3d">Point3d other = (Point3d) obj;
531 if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x))
532 return false;
533 if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y))
534 return false;
535 if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z))
536 return false;
537 return true;
538 }
539
540 }