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