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"))[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");
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");
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 @Override
114 public final double getX()
115 {
116 return this.x;
117 }
118
119 @Override
120 public final double getY()
121 {
122 return this.y;
123 }
124
125
126
127
128
129 public final double getZ()
130 {
131 return this.z;
132 }
133
134 @Override
135 public double distanceSquared(final Point3d otherPoint) throws NullPointerException
136 {
137 Throw.whenNull(otherPoint, "otherPoint");
138 double dx = this.x - otherPoint.x;
139 double dy = this.y - otherPoint.y;
140 double dz = this.z - otherPoint.z;
141 return dx * dx + dy * dy + dz * dz;
142 }
143
144 @Override
145 public double distance(final Point3d otherPoint) throws NullPointerException
146 {
147 Throw.whenNull(otherPoint, "otherPoint");
148 return Math.sqrt(distanceSquared(otherPoint));
149 }
150
151 @Override
152 public int size()
153 {
154 return 1;
155 }
156
157 @Override
158 public Iterator<? extends Point3d> getPoints()
159 {
160 return Arrays.stream(new Point3d[] {this}).iterator();
161 }
162
163 @Override
164 public Point2d project() throws DrawRuntimeException
165 {
166 return new Point2d(this.x, this.y);
167 }
168
169
170
171
172
173
174
175
176 public Point3d translate(final double dx, final double dy) throws IllegalArgumentException
177 {
178 Throw.when(Double.isNaN(dx) || Double.isNaN(dy), IllegalArgumentException.class,
179 "Translation must be number (not NaN)");
180 return new Point3d(this.x + dx, this.y + dy, this.z);
181 }
182
183
184
185
186
187
188
189
190
191 public Point3d translate(final double dx, final double dy, final double dz) throws IllegalArgumentException
192 {
193 Throw.when(Double.isNaN(dx) || Double.isNaN(dy) || Double.isNaN(dz), IllegalArgumentException.class,
194 "dx, dy and dz must be numbers (not NaN)");
195 return new Point3d(this.x + dx, this.y + dy, this.z + dz);
196 }
197
198 @Override
199 public Point3d scale(final double factor) throws IllegalArgumentException
200 {
201 Throw.when(Double.isNaN(factor), IllegalArgumentException.class, "factor must be a number (not NaN)");
202 return new Point3d(this.x * factor, this.y * factor, this.z * factor);
203 }
204
205 @Override
206 public Point3d neg()
207 {
208 return scale(-1.0);
209 }
210
211 @Override
212 public Point3d abs()
213 {
214 return new Point3d(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z));
215 }
216
217 @Override
218 public Point3d normalize() throws DrawRuntimeException
219 {
220 double length = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
221 Throw.when(length == 0.0, DrawRuntimeException.class, "cannot normalize (0.0, 0.0, 0.0)");
222 return this.scale(1.0 / length);
223 }
224
225 @Override
226 public Point3d interpolate(final Point3d point, final double fraction)
227 {
228 Throw.whenNull(point, "point");
229 Throw.when(Double.isNaN(fraction), IllegalArgumentException.class, "fraction must be a number (not NaN)");
230 return new Point3d((1.0 - fraction) * this.x + fraction * point.x, (1.0 - fraction) * this.y + fraction * point.y,
231 (1.0 - fraction) * this.z + fraction * point.z);
232
233 }
234
235 @Override
236 public boolean epsilonEquals(final Point3d otherPoint, final double epsilon)
237 {
238 Throw.whenNull(otherPoint, "otherPoint");
239 if (Math.abs(this.x - otherPoint.x) > epsilon)
240 {
241 return false;
242 }
243 if (Math.abs(this.y - otherPoint.y) > epsilon)
244 {
245 return false;
246 }
247 if (Math.abs(this.z - otherPoint.z) > epsilon)
248 {
249 return false;
250 }
251 return true;
252 }
253
254 @Override
255 public Bounds3d getBounds()
256 {
257 return new Bounds3d(this);
258 }
259
260 @Override
261 public final Point3d closestPointOnSegment(final Point3d segmentPoint1, final Point3d segmentPoint2)
262 {
263 Throw.whenNull(segmentPoint1, "segmentPoint1");
264 Throw.whenNull(segmentPoint2, "segmentPoint2");
265 return closestPointOnSegment(segmentPoint1.x, segmentPoint1.y, segmentPoint1.z, segmentPoint2.x, segmentPoint2.y,
266 segmentPoint2.z);
267 }
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286 @SuppressWarnings("checkstyle:parameternumber")
287 public Point3d closestPointOnLine(final double p1X, final double p1Y, final double p1Z, final double p2X, final double p2Y,
288 final double p2Z, final Boolean lowLimitHandling, final Boolean highLimitHandling) throws DrawRuntimeException
289 {
290 double fraction = fractionalPositionOnLine(p1X, p1Y, p1Z, p2X, p2Y, p2Z, lowLimitHandling, highLimitHandling);
291 if (Double.isNaN(fraction))
292 {
293 return null;
294 }
295 if (fraction == 1.0)
296 {
297 return new Point3d(p2X, p2Y, p2Z);
298 }
299 return new Point3d(p1X + fraction * (p2X - p1X), p1Y + fraction * (p2Y - p1Y), p1Z + fraction * (p2Z - p1Z));
300 }
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322 @SuppressWarnings("checkstyle:parameternumber")
323 public double fractionalPositionOnLine(final double p1X, final double p1Y, final double p1Z, final double p2X,
324 final double p2Y, final double p2Z, final Boolean lowLimitHandling, final Boolean highLimitHandling)
325 throws DrawRuntimeException
326 {
327 double dX = p2X - p1X;
328 double dY = p2Y - p1Y;
329 double dZ = p2Z - p1Z;
330 Throw.when(Double.isNaN(dX) || Double.isNaN(dY) || Double.isNaN(dZ), DrawRuntimeException.class,
331 "NaN values not permitted");
332 if (0 == dX && 0 == dY && 0 == dZ)
333 {
334 return 0.0;
335 }
336 double fraction = ((this.x - p1X) * dX + (this.y - p1Y) * dY + (this.z - p1Z) * dZ) / (dX * dX + dY * dY + dZ * dZ);
337 if (fraction < 0.0)
338 {
339 if (lowLimitHandling == null)
340 {
341 return Double.NaN;
342 }
343 if (lowLimitHandling)
344 {
345 fraction = 0.0;
346 }
347 }
348 else if (fraction > 1.0)
349 {
350 if (highLimitHandling == null)
351 {
352 return Double.NaN;
353 }
354 if (highLimitHandling)
355 {
356 fraction = 1.0;
357 }
358 }
359 return fraction;
360 }
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377 public final Point3d closestPointOnSegment(final double p1X, final double p1Y, final double p1Z, final double p2X,
378 final double p2Y, final double p2Z) throws DrawRuntimeException
379 {
380 return closestPointOnLine(p1X, p1Y, p1Z, p2X, p2Y, p2Z, true, true);
381 }
382
383 @Override
384 public final Point3d closestPointOnLine(final Point3d linePoint1, final Point3d linePoint2)
385 throws NullPointerException, DrawRuntimeException
386 {
387 Throw.whenNull(linePoint1, "linePoint1");
388 Throw.whenNull(linePoint2, "linePoint2");
389 return closestPointOnLine(linePoint1.x, linePoint1.y, linePoint1.z, linePoint2.x, linePoint2.y, linePoint2.z);
390 }
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405 public final Point3d closestPointOnLine(final double p1X, final double p1Y, final double p1Z, final double p2X,
406 final double p2Y, final double p2Z) throws DrawRuntimeException
407 {
408 Throw.when(p1X == p2X && p1Y == p2Y && p1Z == p2Z, DrawRuntimeException.class, "degenerate line not allowed");
409 return closestPointOnLine(p1X, p1Y, p1Z, p2X, p2Y, p2Z, false, false);
410 }
411
412
413
414
415
416 final double horizontalDirection()
417 {
418 return Math.atan2(this.y, this.x);
419 }
420
421
422
423
424
425
426
427 final double horizontalDirection(final Point3d otherPoint) throws NullPointerException
428 {
429 Throw.whenNull(otherPoint, "otherPoint");
430 return Math.atan2(otherPoint.y - this.y, otherPoint.x - this.x);
431 }
432
433
434
435
436
437
438
439 final double verticalDirection(final Point3d otherPoint) throws NullPointerException
440 {
441 Throw.whenNull(otherPoint, "otherPoint");
442 return Math.atan2(Math.hypot(otherPoint.y - this.y, otherPoint.x - this.x), otherPoint.z - this.z);
443 }
444
445
446
447
448
449
450
451 final double horizontalDistanceSquared(final Point3d otherPoint)
452 {
453 Throw.whenNull(otherPoint, "otherPoint");
454 double dx = this.x - otherPoint.x;
455 double dy = this.y - otherPoint.y;
456 return dx * dx + dy * dy;
457 }
458
459
460
461
462
463
464
465 final double horizontalDistance(final Point3d otherPoint)
466 {
467 return Math.sqrt(horizontalDistanceSquared(otherPoint));
468 }
469
470 @Override
471 @SuppressWarnings("checkstyle:designforextension")
472 public String toString()
473 {
474 return toString("%f");
475 }
476
477 @Override
478 public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
479 {
480 String format = String.format("%1$s[x=%2$s, y=%2$s, z=%2$s]", doNotIncludeClassName ? "" : "Point3d ", doubleFormat);
481 return String.format(Locale.US, format, this.x, this.y, this.z);
482 }
483
484 @Override
485 public int hashCode()
486 {
487 final int prime = 31;
488 int result = 1;
489 long temp;
490 temp = Double.doubleToLongBits(this.x);
491 result = prime * result + (int) (temp ^ (temp >>> 32));
492 temp = Double.doubleToLongBits(this.y);
493 result = prime * result + (int) (temp ^ (temp >>> 32));
494 temp = Double.doubleToLongBits(this.z);
495 result = prime * result + (int) (temp ^ (temp >>> 32));
496 return result;
497 }
498
499 @SuppressWarnings("checkstyle:needbraces")
500 @Override
501 public boolean equals(final Object obj)
502 {
503 if (this == obj)
504 return true;
505 if (obj == null)
506 return false;
507 if (getClass() != obj.getClass())
508 return false;
509 Point3d other = (Point3d) obj;
510 if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x))
511 return false;
512 if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y))
513 return false;
514 if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z))
515 return false;
516 return true;
517 }
518
519 }