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