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