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