1 package org.djutils.draw.point;
2
3 import java.awt.geom.Point2D;
4 import java.util.Arrays;
5 import java.util.Iterator;
6
7 import org.djutils.draw.DrawRuntimeException;
8 import org.djutils.draw.Drawable3d;
9 import org.djutils.draw.Space3d;
10 import org.djutils.draw.bounds.Bounds3d;
11 import org.djutils.draw.line.Ray3d;
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 = getX() - otherPoint.x;
143 double dy = getY() - otherPoint.y;
144 double dz = getZ() - 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(getX(), getY());
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(getX() + dx, getY() + dy, getZ());
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(getX() + dx, getY() + dy, getZ() + 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(getX() * factor, getY() * factor, getZ() * 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(getX()), Math.abs(getY()), Math.abs(getZ()));
226 }
227
228
229 @Override
230 public Point3d normalize() throws DrawRuntimeException
231 {
232 double length = Math.sqrt(getX() * getX() + getY() * getY() + getZ() * getZ());
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) * getX() + fraction * point.x, (1.0 - fraction) * getY() + fraction * point.y,
244 (1.0 - fraction) * getZ() + 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(getX() - other.x) > epsilon)
254 {
255 return false;
256 }
257 if (Math.abs(getY() - other.y) > epsilon)
258 {
259 return false;
260 }
261 if (Math.abs(getZ() - 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 double dX = segmentPoint2.x - segmentPoint1.x;
280 double dY = segmentPoint2.y - segmentPoint1.y;
281 double dZ = segmentPoint2.z - segmentPoint1.z;
282 if (0 == dX && 0 == dY && 0 == dZ)
283 {
284 return segmentPoint1;
285 }
286 final double u = ((this.x - segmentPoint1.x) * dX + (this.y - segmentPoint1.y) * dY + (this.z - segmentPoint1.z) * dZ)
287 / (dX * dX + dY * dY + dZ * dZ);
288 if (u < 0)
289 {
290 return segmentPoint1;
291 }
292 else if (u > 1)
293 {
294 return segmentPoint2;
295 }
296 else
297 {
298 return segmentPoint1.interpolate(segmentPoint2, u);
299 }
300 }
301
302
303 @Override
304 public final Point3dl#Point3d">Point3d3d">Point3d closestPointOnLine(final Point3dl#Point3d">Point3d linePoint1, final Point3d linePoint2) throws DrawRuntimeException
305 {
306 double dX = linePoint2.x - linePoint1.x;
307 double dY = linePoint2.y - linePoint1.y;
308 double dZ = linePoint2.z - linePoint1.z;
309 Throw.when(dX == 0 && dY == 0 && dZ == 0, DrawRuntimeException.class, "line points are at same location");
310 final double u = ((this.x - linePoint1.x) * dX + (this.y - linePoint1.y) * dY + (this.z - linePoint1.z) * dZ)
311 / (dX * dX + dY * dY + dZ * dZ);
312 return linePoint1.interpolate(linePoint2, u);
313 }
314
315
316
317
318
319
320 public final Point3d closestPointOnLine(final Ray3d ray)
321 {
322 double sinTheta = Math.sin(ray.theta);
323 double dX = Math.cos(ray.phi) * sinTheta;
324 double dY = Math.sin(ray.phi) * sinTheta;
325 double dZ = Math.cos(ray.theta);
326 final double u =
327 ((this.x - ray.x) * dX + (this.y - ray.y) * dY + (this.z - ray.z) * dZ) / (dX * dX + dY * dY * dZ * dZ);
328 return ray.interpolate(new Point3d(ray.x + dX, ray.y + dY, ray.z + dZ), Math.max(0, u));
329 }
330
331
332
333
334
335 final double horizontalDirection()
336 {
337 return Math.atan2(getY(), getX());
338 }
339
340
341
342
343
344
345
346 final double horizontalDirection(final Point3d point) throws NullPointerException
347 {
348 Throw.whenNull(point, "point cannot be null");
349 return Math.atan2(point.y - getY(), point.x - getX());
350 }
351
352
353
354
355
356
357
358 final double horizontalDistanceSquared(final Point3d point)
359 {
360 Throw.whenNull(point, "point cannot be null");
361 double dx = getX() - point.x;
362 double dy = getY() - point.y;
363 return dx * dx + dy * dy;
364 }
365
366
367
368
369
370
371
372 final double horizontalDistance(final Point3d point)
373 {
374 return Math.sqrt(horizontalDistanceSquared(point));
375 }
376
377
378 @Override
379 @SuppressWarnings("checkstyle:designforextension")
380 public String toString()
381 {
382 return String.format("(%f,%f,%f)", getX(), getY(), getZ());
383 }
384
385
386 @Override
387 public String toString(final int fractionDigits)
388 {
389 int digits = fractionDigits < 0 ? 0 : fractionDigits;
390 String format = String.format("(%%.%1$df,%%.%1$df,%%.%1$df)", digits);
391 return String.format(format, getX(), getY(), getZ());
392 }
393
394
395 @Override
396 public int hashCode()
397 {
398 final int prime = 31;
399 int result = 1;
400 long temp;
401 temp = Double.doubleToLongBits(this.x);
402 result = prime * result + (int) (temp ^ (temp >>> 32));
403 temp = Double.doubleToLongBits(this.y);
404 result = prime * result + (int) (temp ^ (temp >>> 32));
405 temp = Double.doubleToLongBits(this.z);
406 result = prime * result + (int) (temp ^ (temp >>> 32));
407 return result;
408 }
409
410
411 @SuppressWarnings("checkstyle:needbraces")
412 @Override
413 public boolean equals(final Object obj)
414 {
415 if (this == obj)
416 return true;
417 if (obj == null)
418 return false;
419 if (getClass() != obj.getClass())
420 return false;
421 Point3d../../../../org/djutils/draw/point/Point3d.html#Point3d">Point3d other = (Point3d) obj;
422 if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x))
423 return false;
424 if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y))
425 return false;
426 if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z))
427 return false;
428 return true;
429 }
430
431 }