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