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
15
16
17
18
19
20
21
22
23 public class LineSegment3d implements Drawable3d, LineSegment<Point3d, Ray3d>
24 {
25
26 private static final long serialVersionUID = 20210121L;
27
28
29 @SuppressWarnings("checkstyle:visibilitymodifier")
30 public final double startX;
31
32
33 @SuppressWarnings("checkstyle:visibilitymodifier")
34 public final double startY;
35
36
37 @SuppressWarnings("checkstyle:visibilitymodifier")
38 public final double startZ;
39
40
41 @SuppressWarnings("checkstyle:visibilitymodifier")
42 public final double endX;
43
44
45 @SuppressWarnings("checkstyle:visibilitymodifier")
46 public final double endY;
47
48
49 @SuppressWarnings("checkstyle:visibilitymodifier")
50 public final double endZ;
51
52
53
54
55
56
57
58
59
60
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
77
78
79
80
81
82
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
92
93
94
95
96
97
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
107
108
109
110
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
119 @Override
120 public Point3d getStartPoint()
121 {
122 return new Point3d(this.startX, this.startY, this.startZ);
123 }
124
125
126 @Override
127 public Point3d getEndPoint()
128 {
129 return new Point3d(this.endX, this.endY, this.endZ);
130 }
131
132
133 @Override
134 public double getLength()
135 {
136
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
144 @Override
145 public Iterator<? extends Point3d> getPoints()
146 {
147 return Arrays.stream(new Point3d[] { getStartPoint(), getEndPoint() }).iterator();
148 }
149
150
151 @Override
152 public int size()
153 {
154 return 2;
155 }
156
157
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
167 @Override
168 public LineSegment2d project() throws DrawRuntimeException
169 {
170 return new LineSegment2d(this.startX, this.startY, this.endX, this.endY);
171 }
172
173
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
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
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
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
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
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
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
237 @Override
238 public String toString()
239 {
240 return toString("%f", false);
241 }
242
243
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
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
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
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 }