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