View Javadoc
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&lt;OTSPoint3D&gt; 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&lt;OTSPoint3D&gt; 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 }