View Javadoc
1   package org.djutils.draw.line;
2   
3   import java.util.Arrays;
4   import java.util.Iterator;
5   import java.util.Locale;
6   
7   import org.djutils.draw.DrawRuntimeException;
8   import org.djutils.draw.Drawable3d;
9   import org.djutils.draw.bounds.Bounds3d;
10  import org.djutils.draw.point.Point3d;
11  import org.djutils.exceptions.Throw;
12  
13  /**
14   * LineSegment3d is a line segment bound by 2 end points in 3D-space. A line segment stores the order in which it has been
15   * created, so the end points are known as 'start' and 'end'.
16   * <p>
17   * Copyright (c) 2020-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
18   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
19   * </p>
20   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
21   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
22   */
23  public class LineSegment3d implements Drawable3d, LineSegment<Point3d, Ray3d>
24  {
25      /** */
26      private static final long serialVersionUID = 20210121L;
27  
28      /** The start x-coordinate. */
29      @SuppressWarnings("checkstyle:visibilitymodifier")
30      public final double startX;
31  
32      /** The start y-coordinate. */
33      @SuppressWarnings("checkstyle:visibilitymodifier")
34      public final double startY;
35  
36      /** The start z-coordinate. */
37      @SuppressWarnings("checkstyle:visibilitymodifier")
38      public final double startZ;
39  
40      /** The end x-coordinate. */
41      @SuppressWarnings("checkstyle:visibilitymodifier")
42      public final double endX;
43  
44      /** The end y-coordinate. */
45      @SuppressWarnings("checkstyle:visibilitymodifier")
46      public final double endY;
47  
48      /** The end z-coordinate. */
49      @SuppressWarnings("checkstyle:visibilitymodifier")
50      public final double endZ;
51  
52      /**
53       * Construct a new LineSegment3d start six coordinates.
54       * @param startX double; the x-coordinate of the start point
55       * @param startY double; the y-coordinate of the start point
56       * @param startZ double; the z-coordinate of the start point
57       * @param endX double; the x-coordinate of the end point
58       * @param endY double; the y-coordinate of the end point
59       * @param endZ double; the z-coordinate of the end point
60       * @throws DrawRuntimeException when (startX,startY) is equals end (endX,endY)
61       */
62      public LineSegment3d(final double startX, final double startY, final double startZ, final double endX, final double endY,
63              final double endZ) throws DrawRuntimeException
64      {
65          Throw.when(startX == endX && startY == endY && startZ == endZ, DrawRuntimeException.class,
66                  "Start and end may not be equal");
67          this.startX = startX;
68          this.startY = startY;
69          this.startZ = startZ;
70          this.endX = endX;
71          this.endY = endY;
72          this.endZ = endZ;
73      }
74  
75      /**
76       * Construct a new LineSegment3d start a Point3d and three coordinates.
77       * @param start Point3d; the start point
78       * @param endX double; the x-coordinate of the end point
79       * @param endY double; the y-coordinate of the end point
80       * @param endZ double; the z-coordinate of the end point
81       * @throws NullPointerException when start is null
82       * @throws DrawRuntimeException when start has the exact coordinates endX, endY
83       */
84      public LineSegment3d(final Point3d start, final double endX, final double endY, final double endZ)
85              throws NullPointerException, DrawRuntimeException
86      {
87          this(Throw.whenNull(start, "start point may not be null").x, start.y, start.z, endX, endY, endZ);
88      }
89  
90      /**
91       * Construct a new LineSegment3d start three coordinates and a Point3d.
92       * @param startX double; the x-coordinate of the start point
93       * @param startY double; the y-coordinate of the start point
94       * @param startZ double; the z-coordinate of the start point
95       * @param end Point3d; the end point
96       * @throws NullPointerException when end is null
97       * @throws DrawRuntimeException when end has the exact coordinates startX, startY
98       */
99      public LineSegment3d(final double startX, final double startY, final double startZ, final Point3d end)
100             throws NullPointerException, DrawRuntimeException
101     {
102         this(startX, startY, startZ, Throw.whenNull(end, "end point may not be null").x, end.y, end.z);
103     }
104 
105     /**
106      * Construct a new LineSegment3d start two Point3d objects.
107      * @param start Point3d; the start point
108      * @param end Point3d; the end point
109      * @throws NullPointerException when start is null
110      * @throws DrawRuntimeException when start has the exact coordinates endX, endY
111      */
112     public LineSegment3d(final Point3d start, final Point3d end) throws NullPointerException, DrawRuntimeException
113     {
114         this(Throw.whenNull(start, "start point may not be null").x, start.y, start.z,
115                 Throw.whenNull(end, "end point may not be null").x, end.y, end.z);
116     }
117 
118     /** {@inheritDoc} */
119     @Override
120     public Point3d getStartPoint()
121     {
122         return new Point3d(this.startX, this.startY, this.startZ);
123     }
124 
125     /** {@inheritDoc} */
126     @Override
127     public Point3d getEndPoint()
128     {
129         return new Point3d(this.endX, this.endY, this.endZ);
130     }
131 
132     /** {@inheritDoc} */
133     @Override
134     public double getLength()
135     {
136         // There is no varargs hypot function in Math
137         double dX = this.endX - this.startX;
138         double dY = this.endY - this.startY;
139         double dZ = this.endZ - this.startZ;
140         return Math.sqrt(dX * dX + dY * dY + dZ * dZ);
141     }
142 
143     /** {@inheritDoc} */
144     @Override
145     public Iterator<? extends Point3d> getPoints()
146     {
147         return Arrays.stream(new Point3d[] { getStartPoint(), getEndPoint() }).iterator();
148     }
149 
150     /** {@inheritDoc} */
151     @Override
152     public int size()
153     {
154         return 2;
155     }
156 
157     /** {@inheritDoc} */
158     @Override
159     public Bounds3d getBounds()
160     {
161         return new Bounds3d(Math.min(this.startX, this.endX), Math.max(this.startX, this.endX),
162                 Math.min(this.startY, this.endY), Math.max(this.startY, this.endY), Math.min(this.startZ, this.endZ),
163                 Math.max(this.startZ, this.endZ));
164     }
165 
166     /** {@inheritDoc} */
167     @Override
168     public LineSegment2d project() throws DrawRuntimeException
169     {
170         return new LineSegment2d(this.startX, this.startY, this.endX, this.endY);
171     }
172 
173     /** {@inheritDoc} */
174     @Override
175     public Ray3d getLocationExtended(final double position) throws DrawRuntimeException
176     {
177         Throw.when(Double.isNaN(position) || Double.isInfinite(position), DrawRuntimeException.class,
178                 "position must be finite");
179         double dX = this.endX - this.startX;
180         double dY = this.endY - this.startY;
181         double dZ = this.endZ - this.startZ;
182         double length = Math.sqrt(dX * dX + dY * dY + dZ * dZ);
183         return new Ray3d(this.startX + position * dX / length, this.startY + position * dY / length,
184                 this.startZ + position * dZ / length, Math.atan2(dY, dX), Math.atan2(dZ, Math.hypot(dX, dY)));
185     }
186 
187     /** {@inheritDoc} */
188     @Override
189     public Point3d closestPointOnSegment(final Point3d point)
190     {
191         Throw.whenNull(point, "point may not be null");
192         return point.closestPointOnLine(this.startX, this.startY, this.startZ, this.endX, this.endY, this.endZ, true, true);
193     }
194 
195     /** {@inheritDoc} */
196     @Override
197     public LineSegment3d reverse()
198     {
199         return new LineSegment3d(this.endX, this.endY, this.endZ, this.startX, this.startY, this.startZ);
200     }
201 
202     /** {@inheritDoc} */
203     @Override
204     public Point3d projectOrthogonal(final Point3d point) throws NullPointerException
205     {
206         Throw.whenNull(point, "point may not be null");
207         return point.closestPointOnLine(this.startX, this.startY, this.startZ, this.endX, this.endY, this.endZ, null, null);
208     }
209 
210     /** {@inheritDoc} */
211     @Override
212     public Point3d projectOrthogonalExtended(final Point3d point) throws NullPointerException
213     {
214         Throw.whenNull(point, "point may not be null");
215         return point.closestPointOnLine(this.startX, this.startY, this.startZ, this.endX, this.endY, this.endZ);
216     }
217 
218     /** {@inheritDoc} */
219     @Override
220     public double projectOrthogonalFractional(final Point3d point) throws NullPointerException
221     {
222         Throw.whenNull(point, "point may not be null");
223         return point.fractionalPositionOnLine(this.startX, this.startY, this.startZ, this.endX, this.endY, this.endZ, null,
224                 null);
225     }
226 
227     /** {@inheritDoc} */
228     @Override
229     public double projectOrthogonalFractionalExtended(final Point3d point) throws NullPointerException
230     {
231         Throw.whenNull(point, "point may not be null");
232         return point.fractionalPositionOnLine(this.startX, this.startY, this.startZ, this.endX, this.endY, this.endZ, false,
233                 false);
234     }
235 
236     /** {@inheritDoc} */
237     @Override
238     public String toString()
239     {
240         return toString("%f", false);
241     }
242 
243     /** {@inheritDoc} */
244     @Override
245     public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
246     {
247         String format = String.format("%1$s[startX=%2$s, startY=%2$s, startZ=%2$s - endX=%2%s, endY=%2$s, endZ=%2$s]",
248                 doNotIncludeClassName ? "" : "LineSegment3d ", doubleFormat);
249         return String.format(Locale.US, format, this.startX, this.startY, this.startZ, this.endX, this.endY, this.endZ);
250     }
251 
252     /** {@inheritDoc} */
253     @Override
254     public String toExcel()
255     {
256         return this.startX + "\t" + this.startY + "\t" + this.startZ + "\n" + this.endX + "\t" + this.endY + "\t" + this.endZ
257                 + "\n";
258     }
259 
260     /** {@inheritDoc} */
261     @Override
262     public int hashCode()
263     {
264         final int prime = 31;
265         int result = 1;
266         long temp;
267         temp = Double.doubleToLongBits(this.endX);
268         result = prime * result + (int) (temp ^ (temp >>> 32));
269         temp = Double.doubleToLongBits(this.endY);
270         result = prime * result + (int) (temp ^ (temp >>> 32));
271         temp = Double.doubleToLongBits(this.endZ);
272         result = prime * result + (int) (temp ^ (temp >>> 32));
273         temp = Double.doubleToLongBits(this.startX);
274         result = prime * result + (int) (temp ^ (temp >>> 32));
275         temp = Double.doubleToLongBits(this.startY);
276         result = prime * result + (int) (temp ^ (temp >>> 32));
277         temp = Double.doubleToLongBits(this.startZ);
278         result = prime * result + (int) (temp ^ (temp >>> 32));
279         return result;
280     }
281 
282     /** {@inheritDoc} */
283     @Override
284     @SuppressWarnings("checkstyle:needbraces")
285     public boolean equals(final Object obj)
286     {
287         if (this == obj)
288             return true;
289         if (obj == null)
290             return false;
291         if (getClass() != obj.getClass())
292             return false;
293         LineSegment3d other = (LineSegment3d) obj;
294         if (Double.doubleToLongBits(this.endX) != Double.doubleToLongBits(other.endX))
295             return false;
296         if (Double.doubleToLongBits(this.endY) != Double.doubleToLongBits(other.endY))
297             return false;
298         if (Double.doubleToLongBits(this.endZ) != Double.doubleToLongBits(other.endZ))
299             return false;
300         if (Double.doubleToLongBits(this.startX) != Double.doubleToLongBits(other.startX))
301             return false;
302         if (Double.doubleToLongBits(this.startY) != Double.doubleToLongBits(other.startY))
303             return false;
304         if (Double.doubleToLongBits(this.startZ) != Double.doubleToLongBits(other.startZ))
305             return false;
306         return true;
307     }
308 
309 }