1 package org.opentrafficsim.core.geometry;
2
3 import java.awt.geom.Point2D;
4 import java.io.Serializable;
5
6 import javax.media.j3d.BoundingSphere;
7 import javax.media.j3d.Bounds;
8 import javax.vecmath.Point3d;
9
10 import nl.tudelft.simulation.dsol.animation.Locatable;
11 import nl.tudelft.simulation.language.d3.CartesianPoint;
12 import nl.tudelft.simulation.language.d3.DirectedPoint;
13
14 import org.djunits.unit.LengthUnit;
15 import org.djunits.value.vdouble.scalar.Length;
16
17 import com.vividsolutions.jts.geom.Coordinate;
18 import com.vividsolutions.jts.geom.Point;
19
20 /**
21 * 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
22 * units, i.e. in meters. A distance between two points is therefore also in meters.
23 * <p>
24 * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
25 * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
26 * <p>
27 * $LastChangedDate: 2015-07-16 10:20:53 +0200 (Thu, 16 Jul 2015) $, @version $Revision: 1124 $, by $Author: pknoppers $,
28 * initial version Jul 22, 2015 <br>
29 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
30 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
31 * @author <a href="http://www.citg.tudelft.nl">Guus Tamminga</a>
32 */
33 public class OTSPoint3D implements Locatable, Serializable
34 {
35 /** */
36 private static final long serialVersionUID = 20150722L;
37
38 /** The internal representation of the point; x-coordinate. */
39 @SuppressWarnings("checkstyle:visibilitymodifier")
40 public final double x;
41
42 /** The internal representation of the point; y-coordinate. */
43 @SuppressWarnings("checkstyle:visibilitymodifier")
44 public final double y;
45
46 /** The internal representation of the point; z-coordinate. */
47 @SuppressWarnings("checkstyle:visibilitymodifier")
48 public final double z;
49
50 /**
51 * The x, y and z in the point are assumed to be in meters relative to an origin.
52 * @param x x-coordinate
53 * @param y y-coordinate
54 * @param z z-coordinate
55 */
56 public OTSPoint3D(final double x, final double y, final double z)
57 {
58 this.x = x;
59 this.y = y;
60 this.z = z;
61 }
62
63 /**
64 * @param xyz array with three elements; x, y and z are assumed to be in meters relative to an origin.
65 */
66 public OTSPoint3D(final double[] xyz)
67 {
68 this(xyz[0], xyz[1], (xyz.length > 2) ? xyz[2] : 0.0);
69 }
70
71 /**
72 * @param point a point to "clone".
73 */
74 public OTSPoint3D(final OTSPoint3D point)
75 {
76 this(point.x, point.y, point.z);
77 }
78
79 /**
80 * @param point javax.vecmath 3D double point; the x, y and z in the point are assumed to be in meters relative to an
81 * origin.
82 */
83 public OTSPoint3D(final Point3d point)
84 {
85 this(point.x, point.y, point.z);
86 }
87
88 /**
89 * @param point javax.vecmath 3D double point; the x, y and z in the point are assumed to be in meters relative to an
90 * origin.
91 */
92 public OTSPoint3D(final CartesianPoint point)
93 {
94 this(point.x, point.y, point.z);
95 }
96
97 /**
98 * @param point javax.vecmath 3D double point; the x, y and z in the point are assumed to be in meters relative to an
99 * origin.
100 */
101 public OTSPoint3D(final DirectedPoint point)
102 {
103 this(point.x, point.y, point.z);
104 }
105
106 /**
107 * @param point2d java.awt 2D point, z-coordinate will be zero; the x and y in the point are assumed to be in meters
108 * relative to an origin.
109 */
110 public OTSPoint3D(final Point2D point2d)
111 {
112 this(point2d.getX(), point2d.getY(), 0.0);
113 }
114
115 /**
116 * @param coordinate geotools coordinate; the x, y and z in the coordinate are assumed to be in meters relative to an
117 * origin.
118 */
119 public OTSPoint3D(final Coordinate coordinate)
120 {
121 this(coordinate.x, coordinate.y, Double.isNaN(coordinate.z) ? 0.0 : coordinate.z);
122 }
123
124 /**
125 * @param point geotools point; z-coordinate will be zero; the x and y in the point are assumed to be in meters relative to
126 * an origin.
127 */
128 public OTSPoint3D(final Point point)
129 {
130 this(point.getX(), point.getY(), 0.0);
131 }
132
133 /**
134 * The x and y in the point are assumed to be in meters relative to an origin. z will be set to 0.
135 * @param x x-coordinate
136 * @param y y-coordinate
137 */
138 public OTSPoint3D(final double x, final double y)
139 {
140 this(x, y, 0.0);
141 }
142
143 /**
144 * Interpolate (or extrapolate) between (outside) two given points.
145 * @param ratio double; 0 selects the zeroValue point, 1 selects the oneValue point, 0.5 selects a point halfway, etc.
146 * @param zeroValue OTSPoint3D; the point that is returned when ratio equals 0
147 * @param oneValue OTSPoint3D; the point that is returned when ratio equals 1
148 * @return OTSPoint3D
149 */
150 public static OTSPoint3D interpolate(final double ratio, final OTSPoint3D zeroValue, final OTSPoint3D oneValue)
151 {
152 double complement = 1 - ratio;
153 return new OTSPoint3D(complement * zeroValue.x + ratio * oneValue.x, complement * zeroValue.y + ratio * oneValue.y,
154 complement * zeroValue.z + ratio * oneValue.z);
155 }
156
157 /**
158 * Compute the 2D intersection of two line segments. The Z-component of the lines is ignored. Both line segments are defined
159 * by two points (that should be distinct).
160 * @param line1P1 OTSPoint3D; first point of line segment 1
161 * @param line1P2 OTSPoint3D; second point of line segment 1
162 * @param line2P1 OTSPoint3D; first point of line segment 2
163 * @param line2P2 OTSPoint3D; second point of line segment 2
164 * @return OTSPoint3D; the intersection of the two lines, or null if the lines are (almost) parallel, or do not intersect
165 */
166 public static OTSPoint3D intersectionOfLineSegments(final OTSPoint3D line1P1, final OTSPoint3D line1P2,
167 final OTSPoint3D line2P1, final OTSPoint3D line2P2)
168 {
169 double denominator =
170 (line2P2.y - line2P1.y) * (line1P2.x - line1P1.x) - (line2P2.x - line2P1.x) * (line1P2.y - line1P1.y);
171 if (denominator == 0f)
172 {
173 return null; // lines are parallel (they might even be on top of each other, but we don't check that)
174 }
175 double uA =
176 ((line2P2.x - line2P1.x) * (line1P1.y - line2P1.y) - (line2P2.y - line2P1.y) * (line1P1.x - line2P1.x))
177 / denominator;
178 if ((uA < 0f) || (uA > 1f))
179 {
180 return null; // intersection outside line 1
181 }
182 double uB =
183 ((line1P2.x - line1P1.x) * (line1P1.y - line2P1.y) - (line1P2.y - line1P1.y) * (line1P1.x - line2P1.x))
184 / denominator;
185 if (uB < 0 || uB > 1)
186 {
187 return null; // intersection outside line 2
188 }
189 return new OTSPoint3D(line1P1.x + uA * (line1P2.x - line1P1.x), line1P1.y + uA * (line1P2.y - line1P1.y), 0);
190 }
191
192 /**
193 * Compute the 2D intersection of two infinite lines. The Z-component of the lines is ignored. Both lines are defined by two
194 * points (that should be distinct).
195 * @param line1P1 OTSPoint3D; first point of line 1
196 * @param line1P2 OTSPoint3D; second point of line 1
197 * @param line2P1 OTSPoint3D; first point of line 2
198 * @param line2P2 OTSPoint3D; second point of line 2
199 * @return OTSPoint3D; the intersection of the two lines, or null if the lines are (almost) parallel
200 */
201 public static OTSPoint3D intersectionOfLines(final OTSPoint3D line1P1, final OTSPoint3D line1P2, final OTSPoint3D line2P1,
202 final OTSPoint3D line2P2)
203 {
204 double determinant =
205 (line1P1.x - line1P2.x) * (line2P1.y - line2P2.y) - (line1P1.y - line1P2.y) * (line2P1.x - line2P2.x);
206 if (Math.abs(determinant) < 0.0000001)
207 {
208 return null;
209 }
210 return new OTSPoint3D(
211 ((line1P1.x * line1P2.y - line1P1.y * line1P2.x) * (line2P1.x - line2P2.x) - (line1P1.x - line1P2.x)
212 * (line2P1.x * line2P2.y - line2P1.y * line2P2.x))
213 / determinant,
214 ((line1P1.x * line1P2.y - line1P1.y * line1P2.x) * (line2P1.y - line2P2.y) - (line1P1.y - line1P2.y)
215 * (line2P1.x * line2P2.y - line2P1.y * line2P2.x))
216 / determinant);
217 }
218
219 /**
220 * Compute the distance to a line segment (2D; Z-component is ignored). If the projection of this point onto the line lies
221 * outside the line segment; the distance to nearest end point of the line segment is returned. Otherwise the distance to
222 * the line segment is returned. <br>
223 * Adapted from <a href="http://paulbourke.net/geometry/pointlineplane/DistancePoint.java"> example code provided by Paul
224 * Bourke</a>.
225 * @param segmentPoint1 OTSPoint3D; start of line segment
226 * @param segmentPoint2 OTSPoint3D; end of line segment
227 * @return double; the distance of this point to (one of the end points of the line segment)
228 */
229 // public final double horizontalDistanceToLineSegment(final OTSPoint3D segmentPoint1, final OTSPoint3D segmentPoint2)
230 // {
231 // return closestPointOnSegment(segmentPoint1, segmentPoint2).horizontalDistanceSI(this);
232 // }
233
234 /**
235 * Project a point on a line segment (2D - Z-component is ignored). If the the projected points lies outside the line
236 * segment, the nearest end point of the line segment is returned. Otherwise the returned point lies between the end points
237 * of the line segment. <br>
238 * Adapted from <a href="http://paulbourke.net/geometry/pointlineplane/DistancePoint.java">example code provided by Paul
239 * Bourke</a>.
240 * @param segmentPoint1 OTSPoint3D; start of line segment
241 * @param segmentPoint2 OTSPoint3D; end of line segment
242 * @return Point2D.Double; either <cite>lineP1</cite>, or <cite>lineP2</cite> or a new OTSPoint3D that lies somewhere in
243 * between those two. The Z-component of the result matches the Z-component of the line segment at that point
244 */
245 public final OTSPoint3D closestPointOnSegment(final OTSPoint3D segmentPoint1, final OTSPoint3D segmentPoint2)
246 {
247 double dX = segmentPoint2.x - segmentPoint1.x;
248 double dY = segmentPoint2.y - segmentPoint1.y;
249 if ((0 == dX) && (0 == dY))
250 {
251 return segmentPoint1;
252 }
253 final double u = ((this.x - segmentPoint1.x) * dX + (this.y - segmentPoint1.y) * dY) / (dX * dX + dY * dY);
254 if (u < 0)
255 {
256 return segmentPoint1;
257 }
258 else if (u > 1)
259 {
260 return segmentPoint2;
261 }
262 else
263 {
264 return interpolate(u, segmentPoint1, segmentPoint2);
265 // WAS new OTSPoint3D(segmentPoint1.x + u * dX, segmentPoint1.y + u * dY); // could use interpolate in stead
266 }
267 }
268
269 /**
270 * Return the closest point on an OTSLine3D.
271 * @param line OTSLine3D; the line
272 * @param useHorizontalDistance boolean; if true; the horizontal distance is used to determine the closest point; if false;
273 * the 3D distance is used to determine the closest point
274 * @return OTSPoint3D; the Z component of the returned point matches the Z-component of hte line at that point
275 */
276 private OTSPoint3D internalClosestPointOnLine(final OTSLine3D line, final boolean useHorizontalDistance)
277 {
278 OTSPoint3D prevPoint = null;
279 double distance = Double.MAX_VALUE;
280 OTSPoint3D result = null;
281 for (OTSPoint3D nextPoint : line.getPoints())
282 {
283 if (null != prevPoint)
284 {
285 OTSPoint3D closest = closestPointOnSegment(prevPoint, nextPoint);
286 double thisDistance = useHorizontalDistance ? horizontalDistanceSI(closest) : distanceSI(closest);
287 if (thisDistance < distance)
288 {
289 result = closest;
290 distance = thisDistance;
291 }
292 }
293 prevPoint = nextPoint;
294 }
295 return result;
296 }
297
298 /**
299 * Return the closest point on an OTSLine3D. This method takes the Z-component of this point and the line into account.
300 * @param line OTSLine3D; the line
301 * @return OTSPoint3D; the Z-component of the returned point matches the Z-component of the line at that point
302 */
303 public final OTSPoint3D closestPointOnLine(final OTSLine3D line)
304 {
305 return internalClosestPointOnLine(line, false);
306 }
307
308 /**
309 * Return the closest point on an OTSLine3D. This method ignores the Z-component of this point and the line when computing
310 * the distance.
311 * @param line OTSLine3D; the line
312 * @return OTSPoint3D; the Z-component of the returned point matches the Z-component of the line at that point
313 */
314 public final OTSPoint3D closestPointOnLine2D(final OTSLine3D line)
315 {
316 return internalClosestPointOnLine(line, true);
317 }
318
319 /**
320 * @param point the point to which the distance has to be calculated.
321 * @return the distance in 3D according to Pythagoras, expressed in the SI unit for length (meter)
322 */
323 public final double distanceSI(final OTSPoint3D point)
324 {
325 double dx = point.x - this.x;
326 double dy = point.y - this.y;
327 double dz = point.z - this.z;
328
329 return Math.sqrt(dx * dx + dy * dy + dz * dz);
330 }
331
332 /**
333 * @param point the point to which the distance has to be calculated.
334 * @return the distance in 3D according to Pythagoras, expressed in the SI unit for length (meter)
335 */
336 public final double horizontalDistanceSI(final OTSPoint3D point)
337 {
338 double dx = point.x - this.x;
339 double dy = point.y - this.y;
340
341 return Math.sqrt(dx * dx + dy * dy);
342 }
343
344 /**
345 * @param point the point to which the distance has to be calculated.
346 * @return the distance in 3D according to Pythagoras
347 */
348 public final Length horizontalDistance(final OTSPoint3D point)
349 {
350 return new Length(horizontalDistanceSI(point), LengthUnit.SI);
351 }
352
353 /**
354 * @param point the point to which the distance has to be calculated.
355 * @return the distance in 3D according to Pythagoras
356 */
357 public final Length distance(final OTSPoint3D point)
358 {
359 return new Length(distanceSI(point), LengthUnit.SI);
360 }
361
362 /**
363 * @return the equivalent geotools Coordinate of this point.
364 */
365 public final Coordinate getCoordinate()
366 {
367 return new Coordinate(this.x, this.y, this.z);
368 }
369
370 /**
371 * @return the equivalent DSOL DirectedPoint of this point. Should the result be cached?
372 */
373 public final DirectedPoint getDirectedPoint()
374 {
375 return new DirectedPoint(this.x, this.y, this.z);
376 }
377
378 /**
379 * @return a Point2D with the x and y structure.
380 */
381 public final Point2D getPoint2D()
382 {
383 return new Point2D.Double(this.x, this.y);
384 }
385
386 /** {@inheritDoc} */
387 @Override
388 public final DirectedPoint getLocation()
389 {
390 return getDirectedPoint();
391 }
392
393 /**
394 * This method returns a sphere with a diameter of half a meter as the default bounds for a point. {@inheritDoc}
395 */
396 @Override
397 public final Bounds getBounds()
398 {
399 return new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 0.5);
400 }
401
402 /** {@inheritDoc} */
403 @Override
404 @SuppressWarnings("checkstyle:designforextension")
405 public String toString()
406 {
407 return String.format("(%.3f,%.3f,%.3f)", this.x, this.y, this.z);
408 }
409
410 /** {@inheritDoc} */
411 @Override
412 @SuppressWarnings("checkstyle:designforextension")
413 public int hashCode()
414 {
415 final int prime = 31;
416 int result = 1;
417 long temp;
418 temp = Double.doubleToLongBits(this.x);
419 result = prime * result + (int) (temp ^ (temp >>> 32));
420 temp = Double.doubleToLongBits(this.y);
421 result = prime * result + (int) (temp ^ (temp >>> 32));
422 temp = Double.doubleToLongBits(this.z);
423 result = prime * result + (int) (temp ^ (temp >>> 32));
424 return result;
425 }
426
427 /** {@inheritDoc} */
428 @Override
429 @SuppressWarnings({ "checkstyle:designforextension", "checkstyle:needbraces" })
430 public boolean equals(final Object obj)
431 {
432 if (this == obj)
433 return true;
434 if (obj == null)
435 return false;
436 if (getClass() != obj.getClass())
437 return false;
438 OTSPoint3D other = (OTSPoint3D) obj;
439 if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x))
440 return false;
441 if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.y))
442 return false;
443 if (Double.doubleToLongBits(this.z) != Double.doubleToLongBits(other.z))
444 return false;
445 return true;
446 }
447
448 }