1 package org.opentrafficsim.core.geometry;
2
3 import java.awt.geom.Point2D;
4 import java.io.Serializable;
5 import java.util.ArrayList;
6 import java.util.List;
7
8 import javax.media.j3d.BoundingSphere;
9 import javax.media.j3d.Bounds;
10 import javax.vecmath.Point3d;
11
12 import org.djunits.unit.LengthUnit;
13 import org.djunits.value.vdouble.scalar.Length;
14 import org.locationtech.jts.geom.Coordinate;
15 import org.locationtech.jts.geom.Point;
16
17 import nl.tudelft.simulation.dsol.animation.Locatable;
18 import nl.tudelft.simulation.language.d3.CartesianPoint;
19 import nl.tudelft.simulation.language.d3.DirectedPoint;
20
21 /**
22 * An OTSPoint3D implements a 3D-coordinate for OTS. X, y and z are stored as doubles, but it is assumed that the scale is in SI
23 * units, i.e. in meters. A distance between two points is therefore also in meters.
24 * <p>
25 * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
26 * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
27 * <p>
28 * $LastChangedDate: 2015-07-16 10:20:53 +0200 (Thu, 16 Jul 2015) $, @version $Revision: 1124 $, by $Author: pknoppers $,
29 * initial version Jul 22, 2015 <br>
30 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
31 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
32 * @author <a href="http://www.citg.tudelft.nl">Guus Tamminga</a>
33 */
34 public class OTSPoint3D implements Locatable, Serializable
35 {
36 /** */
37 private static final long serialVersionUID = 20150722L;
38
39 /** The internal representation of the point; x-coordinate. */
40 @SuppressWarnings("checkstyle:visibilitymodifier")
41 public final double x;
42
43 /** The internal representation of the point; y-coordinate. */
44 @SuppressWarnings("checkstyle:visibilitymodifier")
45 public final double y;
46
47 /** The internal representation of the point; z-coordinate. */
48 @SuppressWarnings("checkstyle:visibilitymodifier")
49 public final double z;
50
51 /**
52 * The x, y and z in the point are assumed to be in meters relative to an origin.
53 * @param x double; x-coordinate
54 * @param y double; y-coordinate
55 * @param z double; z-coordinate
56 */
57 public OTSPoint3D(final double x, final double y, final double z)
58 {
59 this.x = x;
60 this.y = y;
61 this.z = z;
62 }
63
64 /**
65 * @param xyz array with three elements; x, y and z are assumed to be in meters relative to an origin.
66 */
67 public OTSPoint3D(final double[] xyz)
68 {
69 this(xyz[0], xyz[1], (xyz.length > 2) ? xyz[2] : 0.0);
70 }
71
72 /**
73 * @param point OTSPoint3D; a point to "clone".
74 */
75 public OTSPoint3D(final OTSPoint3D point)
76 {
77 this(point.x, point.y, point.z);
78 }
79
80 /**
81 * @param point javax.vecmath 3D double point; the x, y and z in the point are assumed to be in meters relative to an
82 * origin.
83 */
84 public OTSPoint3D(final Point3d point)
85 {
86 this(point.x, point.y, point.z);
87 }
88
89 /**
90 * @param point javax.vecmath 3D double point; the x, y and z in the point are assumed to be in meters relative to an
91 * origin.
92 */
93 public OTSPoint3D(final CartesianPoint point)
94 {
95 this(point.x, point.y, point.z);
96 }
97
98 /**
99 * @param point javax.vecmath 3D double point; the x, y and z in the point are assumed to be in meters relative to an
100 * origin.
101 */
102 public OTSPoint3D(final DirectedPoint point)
103 {
104 this(point.x, point.y, point.z);
105 }
106
107 /**
108 * @param point2d java.awt 2D point, z-coordinate will be zero; the x and y in the point are assumed to be in meters
109 * relative to an origin.
110 */
111 public OTSPoint3D(final Point2D point2d)
112 {
113 this(point2d.getX(), point2d.getY(), 0.0);
114 }
115
116 /**
117 * @param coordinate geotools coordinate; the x, y and z in the coordinate are assumed to be in meters relative to an
118 * origin.
119 */
120 public OTSPoint3D(final Coordinate coordinate)
121 {
122 this(coordinate.x, coordinate.y, Double.isNaN(coordinate.z) ? 0.0 : coordinate.z);
123 }
124
125 /**
126 * @param point geotools point; z-coordinate will be zero; the x and y in the point are assumed to be in meters relative to
127 * an origin.
128 */
129 public OTSPoint3D(final Point point)
130 {
131 this(point.getX(), point.getY(), 0.0);
132 }
133
134 /**
135 * The x and y in the point are assumed to be in meters relative to an origin. z will be set to 0.
136 * @param x double; x-coordinate
137 * @param y double; y-coordinate
138 */
139 public OTSPoint3D(final double x, final double y)
140 {
141 this(x, y, 0.0);
142 }
143
144 /**
145 * Interpolate (or extrapolate) between (outside) two given points.
146 * @param ratio double; 0 selects the zeroValue point, 1 selects the oneValue point, 0.5 selects a point halfway, etc.
147 * @param zeroValue OTSPoint3D; the point that is returned when ratio equals 0
148 * @param oneValue OTSPoint3D; the point that is returned when ratio equals 1
149 * @return OTSPoint3D
150 */
151 public static OTSPoint3D interpolate(final double ratio, final OTSPoint3D zeroValue, final OTSPoint3D oneValue)
152 {
153 double complement = 1 - ratio;
154 return new OTSPoint3D(complement * zeroValue.x + ratio * oneValue.x, complement * zeroValue.y + ratio * oneValue.y,
155 complement * zeroValue.z + ratio * oneValue.z);
156 }
157
158 /**
159 * Compute the 2D intersection of two line segments. The Z-component of the lines is ignored. Both line segments are defined
160 * by two points (that should be distinct). This version suffers loss of precision when called with very large coordinate
161 * values.
162 * @param line1P1 OTSPoint3D; first point of line segment 1
163 * @param line1P2 OTSPoint3D; second point of line segment 1
164 * @param line2P1 OTSPoint3D; first point of line segment 2
165 * @param line2P2 OTSPoint3D; second point of line segment 2
166 * @return OTSPoint3D; the intersection of the two lines, or null if the lines are (almost) parallel, or do not intersect
167 */
168 @Deprecated
169 public static OTSPoint3D intersectionOfLineSegmentsDumb(final OTSPoint3D line1P1, final OTSPoint3D line1P2,
170 final OTSPoint3D line2P1, final OTSPoint3D line2P2)
171 {
172 double denominator =
173 (line2P2.y - line2P1.y) * (line1P2.x - line1P1.x) - (line2P2.x - line2P1.x) * (line1P2.y - line1P1.y);
174 if (denominator == 0f)
175 {
176 return null; // lines are parallel (they might even be on top of each other, but we don't check that)
177 }
178 double uA = ((line2P2.x - line2P1.x) * (line1P1.y - line2P1.y) - (line2P2.y - line2P1.y) * (line1P1.x - line2P1.x))
179 / denominator;
180 if ((uA < 0f) || (uA > 1f))
181 {
182 return null; // intersection outside line 1
183 }
184 double uB = ((line1P2.x - line1P1.x) * (line1P1.y - line2P1.y) - (line1P2.y - line1P1.y) * (line1P1.x - line2P1.x))
185 / denominator;
186 if (uB < 0 || uB > 1)
187 {
188 return null; // intersection outside line 2
189 }
190 return new OTSPoint3D(line1P1.x + uA * (line1P2.x - line1P1.x), line1P1.y + uA * (line1P2.y - line1P1.y), 0);
191 }
192
193 /**
194 * Compute the 2D intersection of two line segments. The Z-component of the lines is ignored. Both line segments are defined
195 * by two points (that should be distinct).
196 * @param line1P1 OTSPoint3D; first point of line segment 1
197 * @param line1P2 OTSPoint3D; second point of line segment 1
198 * @param line2P1 OTSPoint3D; first point of line segment 2
199 * @param line2P2 OTSPoint3D; second point of line segment 2
200 * @return OTSPoint3D; the intersection of the two lines, or null if the lines are (almost) parallel, or do not intersect
201 */
202 public static OTSPoint3D intersectionOfLineSegments(final OTSPoint3D line1P1, final OTSPoint3D line1P2,
203 final OTSPoint3D line2P1, final OTSPoint3D line2P2)
204 {
205 double l1p1x = line1P1.x;
206 double l1p1y = line1P1.y;
207 double l1p2x = line1P2.x - l1p1x;
208 double l1p2y = line1P2.y - l1p1y;
209 double l2p1x = line2P1.x - l1p1x;
210 double l2p1y = line2P1.y - l1p1y;
211 double l2p2x = line2P2.x - l1p1x;
212 double l2p2y = line2P2.y - l1p1y;
213 double denominator = (l2p2y - l2p1y) * l1p2x - (l2p2x - l2p1x) * l1p2y;
214 if (denominator == 0f)
215 {
216 return null; // lines are parallel (they might even be on top of each other, but we don't check that)
217 }
218 double uA = ((l2p2x - l2p1x) * (-l2p1y) - (l2p2y - l2p1y) * (-l2p1x)) / denominator;
219 // System.out.println("uA is " + uA);
220 if ((uA < 0f) || (uA > 1f))
221 {
222 return null; // intersection outside line 1
223 }
224 double uB = (l1p2y * l2p1x - l1p2x * l2p1y) / denominator;
225 // System.out.println("uB is " + uB);
226 if (uB < 0 || uB > 1)
227 {
228 return null; // intersection outside line 2
229 }
230 return new OTSPoint3D(line1P1.x + uA * l1p2x, line1P1.y + uA * l1p2y, 0);
231 }
232
233 /**
234 * Compute the 2D intersection of two infinite lines. The Z-component of the lines is ignored. Both lines are defined by two
235 * points (that should be distinct). This version suffers loss of precision when called with very large coordinate values.
236 * @param line1P1 OTSPoint3D; first point of line 1
237 * @param line1P2 OTSPoint3D; second point of line 1
238 * @param line2P1 OTSPoint3D; first point of line 2
239 * @param line2P2 OTSPoint3D; second point of line 2
240 * @return OTSPoint3D; the intersection of the two lines, or null if the lines are (almost) parallel
241 */
242 @Deprecated
243 public static OTSPoint3D intersectionOfLinesDumb(final OTSPoint3D line1P1, final OTSPoint3D line1P2,
244 final OTSPoint3D line2P1, final OTSPoint3D line2P2)
245 {
246 double determinant =
247 (line1P1.x - line1P2.x) * (line2P1.y - line2P2.y) - (line1P1.y - line1P2.y) * (line2P1.x - line2P2.x);
248 if (Math.abs(determinant) < 0.0000001)
249 {
250 return null;
251 }
252 return new OTSPoint3D(
253 ((line1P1.x * line1P2.y - line1P1.y * line1P2.x) * (line2P1.x - line2P2.x)
254 - (line1P1.x - line1P2.x) * (line2P1.x * line2P2.y - line2P1.y * line2P2.x)) / determinant,
255 ((line1P1.x * line1P2.y - line1P1.y * line1P2.x) * (line2P1.y - line2P2.y)
256 - (line1P1.y - line1P2.y) * (line2P1.x * line2P2.y - line2P1.y * line2P2.x)) / determinant);
257 }
258
259 /**
260 * Compute the 2D intersection of two infinite lines. The Z-component of the lines is ignored. Both lines are defined by two
261 * points (that should be distinct).
262 * @param line1P1 OTSPoint3D; first point of line 1
263 * @param line1P2 OTSPoint3D; second point of line 1
264 * @param line2P1 OTSPoint3D; first point of line 2
265 * @param line2P2 OTSPoint3D; second point of line 2
266 * @return OTSPoint3D; the intersection of the two lines, or null if the lines are (almost) parallel
267 */
268 public static OTSPoint3D intersectionOfLines(final OTSPoint3D line1P1, final OTSPoint3D line1P2, final OTSPoint3D line2P1,
269 final OTSPoint3D line2P2)
270 {
271 double l1p1x = line1P1.x;
272 double l1p1y = line1P1.y;
273 double l1p2x = line1P2.x - l1p1x;
274 double l1p2y = line1P2.y - l1p1y;
275 double l2p1x = line2P1.x - l1p1x;
276 double l2p1y = line2P1.y - l1p1y;
277 double l2p2x = line2P2.x - l1p1x;
278 double l2p2y = line2P2.y - l1p1y;
279 double determinant = (0 - l1p2x) * (l2p1y - l2p2y) - (0 - l1p2y) * (l2p1x - l2p2x);
280 if (Math.abs(determinant) < 0.0000001)
281 {
282 return null;
283 }
284 return new OTSPoint3D(l1p1x + (l1p2x * (l2p1x * l2p2y - l2p1y * l2p2x)) / determinant,
285 l1p1y + (l1p2y * (l2p1x * l2p2y - l2p1y * l2p2x)) / determinant);
286 }
287
288 /**
289 * Project a point on a line segment (2D - Z-component is ignored). If the the projected points lies outside the line
290 * segment, the nearest end point of the line segment is returned. Otherwise the returned point lies between the end points
291 * of the line segment. <br>
292 * Adapted from <a href="http://paulbourke.net/geometry/pointlineplane/DistancePoint.java">example code provided by Paul
293 * Bourke</a>.
294 * @param segmentPoint1 OTSPoint3D; start of line segment
295 * @param segmentPoint2 OTSPoint3D; end of line segment
296 * @return Point2D.Double; either <cite>lineP1</cite>, or <cite>lineP2</cite> or a new OTSPoint3D that lies somewhere in
297 * between those two. The Z-component of the result matches the Z-component of the line segment at that point
298 */
299 public final OTSPoint3D closestPointOnSegment(final OTSPoint3D segmentPoint1, final OTSPoint3D segmentPoint2)
300 {
301 double dX = segmentPoint2.x - segmentPoint1.x;
302 double dY = segmentPoint2.y - segmentPoint1.y;
303 if ((0 == dX) && (0 == dY))
304 {
305 return segmentPoint1;
306 }
307 final double u = ((this.x - segmentPoint1.x) * dX + (this.y - segmentPoint1.y) * dY) / (dX * dX + dY * dY);
308 if (u < 0)
309 {
310 return segmentPoint1;
311 }
312 else if (u > 1)
313 {
314 return segmentPoint2;
315 }
316 else
317 {
318 return interpolate(u, segmentPoint1, segmentPoint2);
319 }
320 }
321
322 /**
323 * Return the closest point on an OTSLine3D.
324 * @param line OTSLine3D; the line
325 * @param useHorizontalDistance boolean; if true; the horizontal distance is used to determine the closest point; if false;
326 * the 3D distance is used to determine the closest point
327 * @return OTSPoint3D; the Z component of the returned point matches the Z-component of the line at that point
328 */
329 private OTSPoint3D internalClosestPointOnLine(final OTSLine3D line, final boolean useHorizontalDistance)
330 {
331 OTSPoint3D prevPoint = null;
332 double distance = Double.MAX_VALUE;
333 OTSPoint3D result = null;
334 for (OTSPoint3D nextPoint : line.getPoints())
335 {
336 if (null != prevPoint)
337 {
338 OTSPoint3D closest = closestPointOnSegment(prevPoint, nextPoint);
339 double thisDistance = useHorizontalDistance ? horizontalDistanceSI(closest) : distanceSI(closest);
340 if (thisDistance < distance)
341 {
342 result = closest;
343 distance = thisDistance;
344 }
345 }
346 prevPoint = nextPoint;
347 }
348 return result;
349 }
350
351 /**
352 * Return the closest point on an OTSLine3D. This method takes the Z-component of this point and the line into account.
353 * @param line OTSLine3D; the line
354 * @return OTSPoint3D; the Z-component of the returned point matches the Z-component of the line at that point
355 */
356 public final OTSPoint3D closestPointOnLine(final OTSLine3D line)
357 {
358 return internalClosestPointOnLine(line, false);
359 }
360
361 /**
362 * Return the closest point on an OTSLine3D. This method ignores the Z-component of this point and the line when computing
363 * the distance.
364 * @param line OTSLine3D; the line
365 * @return OTSPoint3D; the Z-component of the returned point matches the Z-component of the line at that point
366 */
367 public final OTSPoint3D closestPointOnLine2D(final OTSLine3D line)
368 {
369 return internalClosestPointOnLine(line, true);
370 }
371
372 /**
373 * Return the point with a length of 1 to the origin.
374 * @return OTSPoint3D; the normalized point
375 */
376 public final OTSPoint3D normalize()
377 {
378 double length = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
379 return this.translate(length);
380 }
381
382 /**
383 * Return this point translated by a factor from the origin.
384 * @param factor double; the translation factor
385 * @return OTSPoint3D; the translated point
386 */
387 public final OTSPoint3D translate(final double factor)
388 {
389 return new OTSPoint3D(this.x / factor, this.y / factor, this.z / factor);
390 }
391
392 /**
393 * Return the possible center points of a circle when two points and a radius are given.
394 * @param point1 OTSPoint3D; the first point
395 * @param point2 OTSPoint3D; the second point
396 * @param radius double; the radius
397 * @return List<OTSPoint3D> a list of zero, one or two points
398 */
399 public static final List<OTSPoint3D> circleCenter(final OTSPoint3D point1, final OTSPoint3D point2, final double radius)
400 {
401 List<OTSPoint3D> center = new ArrayList<>();
402 OTSPoint3D m = interpolate(0.5, point1, point2);
403 double h = point1.distanceSI(m);
404 if (radius < h) // no intersection
405 {
406 return center;
407 }
408 if (radius == h) // intersection at m
409 {
410 center.add(m);
411 return center;
412 }
413 OTSPoint3D p = new OTSPoint3D(point2.y - point1.y, point1.x - point2.x).normalize();
414 double d = Math.sqrt(radius * radius - h * h); // distance of center from m
415 center.add(new OTSPoint3D(m.x + d * p.x, m.y + d * p.y, m.z));
416 center.add(new OTSPoint3D(m.x - d * p.x, m.y - d * p.y, m.z));
417 return center;
418 }
419
420 /**
421 * Return the possible intersections between two circles.
422 * @param center1 OTSPoint3D; the center of circle 1
423 * @param radius1 double; the radius of circle 1
424 * @param center2 OTSPoint3D; the center of circle 2
425 * @param radius2 double; the radius of circle 2
426 * @return List<OTSPoint3D> a list of zero, one or two points
427 */
428 public static final List<OTSPoint3D> circleIntersections(final OTSPoint3D center1, final double radius1,
429 final OTSPoint3D center2, final double radius2)
430 {
431 List<OTSPoint3D> center = new ArrayList<>();
432 OTSPoint3D m = interpolate(radius1 / (radius1 + radius2), center1, center2);
433 double h = center1.distanceSI(m);
434 if (radius1 < h) // no intersection
435 {
436 return center;
437 }
438 if (radius1 == h) // intersection at m
439 {
440 center.add(m);
441 return center;
442 }
443 OTSPoint3D p = new OTSPoint3D(center2.y - center1.y, center1.x - center2.x).normalize();
444 double d = Math.sqrt(radius1 * radius1 - h * h); // distance of center from m
445 center.add(new OTSPoint3D(m.x + d * p.x, m.y + d * p.y, m.z));
446 center.add(new OTSPoint3D(m.x - d * p.x, m.y - d * p.y, m.z));
447 return center;
448 }
449
450 /**
451 * @param point OTSPoint3D; the point to which the distance has to be calculated.
452 * @return the distance in 3D according to Pythagoras, expressed in the SI unit for length (meter)
453 */
454 public final double distanceSI(final OTSPoint3D point)
455 {
456 double dx = point.x - this.x;
457 double dy = point.y - this.y;
458 double dz = point.z - this.z;
459
460 return Math.sqrt(dx * dx + dy * dy + dz * dz);
461 }
462
463 /**
464 * @param point OTSPoint3D; the point to which the distance has to be calculated.
465 * @return the distance in 3D according to Pythagoras, expressed in the SI unit for length (meter)
466 */
467 public final double horizontalDistanceSI(final OTSPoint3D point)
468 {
469 double dx = point.x - this.x;
470 double dy = point.y - this.y;
471
472 return Math.sqrt(dx * dx + dy * dy);
473 }
474
475 /**
476 * @param point OTSPoint3D; the point to which the distance has to be calculated.
477 * @return the distance in 3D according to Pythagoras
478 */
479 public final Length horizontalDistance(final OTSPoint3D point)
480 {
481 return new Length(horizontalDistanceSI(point), LengthUnit.SI);
482 }
483
484 /**
485 * @param point OTSPoint3D; the point to which the distance has to be calculated.
486 * @return the distance in 3D according to Pythagoras
487 */
488 public final Length distance(final OTSPoint3D point)
489 {
490 return new Length(distanceSI(point), LengthUnit.SI);
491 }
492
493 /**
494 * @return the equivalent geotools Coordinate of this point.
495 */
496 public final Coordinate getCoordinate()
497 {
498 return new Coordinate(this.x, this.y, this.z);
499 }
500
501 /**
502 * @return the equivalent DSOL DirectedPoint of this point. Should the result be cached?
503 */
504 public final DirectedPoint getDirectedPoint()
505 {
506 return new DirectedPoint(this.x, this.y, this.z);
507 }
508
509 /**
510 * @return a Point2D with the x and y structure.
511 */
512 public final Point2D getPoint2D()
513 {
514 return new Point2D.Double(this.x, this.y);
515 }
516
517 /** {@inheritDoc} */
518 @Override
519 public final DirectedPoint getLocation()
520 {
521 return getDirectedPoint();
522 }
523
524 /**
525 * This method returns a sphere with a diameter of half a meter as the default bounds for a point. {@inheritDoc}
526 */
527 @Override
528 public final Bounds getBounds()
529 {
530 return new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 0.5);
531 }
532
533 /** {@inheritDoc} */
534 @Override
535 @SuppressWarnings("checkstyle:designforextension")
536 public String toString()
537 {
538 return String.format("(%.3f,%.3f,%.3f)", this.x, this.y, this.z);
539 }
540
541 /** {@inheritDoc} */
542 @Override
543 @SuppressWarnings("checkstyle:designforextension")
544 public int hashCode()
545 {
546 final int prime = 31;
547 int result = 1;
548 long temp;
549 temp = Double.doubleToLongBits(this.x);
550 result = prime * result + (int) (temp ^ (temp >>> 32));
551 temp = Double.doubleToLongBits(this.y);
552 result = prime * result + (int) (temp ^ (temp >>> 32));
553 temp = Double.doubleToLongBits(this.z);
554 result = prime * result + (int) (temp ^ (temp >>> 32));
555 return result;
556 }
557
558 /** {@inheritDoc} */
559 @Override
560 @SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
561 public boolean equals(final Object obj)
562 {
563 if (this == obj)
564 return true;
565 if (obj == null)
566 return false;
567 if (getClass() != obj.getClass())
568 return false;
569 OTSPoint3D other = (OTSPoint3D) obj;
570 if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x))
571 return false;
572 if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y))
573 return false;
574 if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z))
575 return false;
576 return true;
577 }
578
579 }