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