1 package org.djutils.draw.point;
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.Oriented3d;
9 import org.djutils.exceptions.Throw;
10 import org.djutils.math.AngleUtil;
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 public class OrientedPoint3d extends DirectedPoint3d implements Oriented3d<OrientedPoint3d>
27 {
28
29 private static final long serialVersionUID = 20200828L;
30
31
32 @SuppressWarnings("checkstyle:visibilitymodifier")
33 public final double dirX;
34
35
36
37
38
39
40
41
42 public OrientedPoint3d(final double x, final double y, final double z)
43 {
44 this(x, y, z, 0.0, 0.0, 0.0);
45 }
46
47
48
49
50
51
52
53
54
55
56
57
58 public OrientedPoint3d(final double x, final double y, final double z, final double dirX, final double dirY,
59 final double dirZ)
60 {
61 super(x, y, z, dirY, dirZ);
62 Throw.whenNaN(dirX, "dirX");
63 this.dirX = dirX;
64 }
65
66
67
68
69
70
71
72
73 public OrientedPoint3d(final double[] xyz)
74 {
75 super(xyz, 0, 0);
76 this.dirX = 0.0;
77 }
78
79
80
81
82
83
84
85
86
87
88
89
90 public OrientedPoint3d(final double[] xyz, final double dirX, final double dirY, final double dirZ)
91 throws NullPointerException, IllegalArgumentException
92 {
93 super(xyz, dirY, dirZ);
94 Throw.whenNaN(dirX, "dirX");
95 this.dirX = dirX;
96 }
97
98
99
100
101
102
103
104
105
106
107 public OrientedPoint3d(final Point3d point, final double dirX, final double dirY, final double dirZ)
108 {
109 this(point.x, point.y, point.z, dirX, dirY, dirZ);
110 }
111
112
113
114
115
116
117
118
119 private static double checkOrientationVector(final double[] orientation)
120 {
121 Throw.when(orientation.length != 3, IllegalArgumentException.class, "length of orientation array must be 3");
122 return orientation[0];
123 }
124
125
126
127
128
129
130
131
132
133
134
135
136 public OrientedPoint3d(final double x, final double y, final double z, final double[] orientation)
137 {
138 this(x, y, z, checkOrientationVector(orientation), orientation[1], orientation[2]);
139 }
140
141
142
143
144
145
146
147
148
149
150
151 public OrientedPoint3d(final double[] xyz, final double[] orientation)
152 {
153 this(xyz, checkOrientationVector(orientation), orientation[1], orientation[2]);
154 }
155
156 @Override
157 public OrientedPoint3d translate(final double dX, final double dY)
158 {
159 Throw.whenNaN(dX, "dX");
160 Throw.whenNaN(dY, "dY");
161 return new OrientedPoint3d(this.x + dX, this.y + dY, this.z, this.dirX, this.dirY, this.dirZ);
162 }
163
164 @Override
165 public OrientedPoint3d translate(final double dX, final double dY, final double dZ)
166 {
167 Throw.whenNaN(dX, "dX");
168 Throw.whenNaN(dY, "dY");
169 Throw.whenNaN(dZ, "dZ");
170 return new OrientedPoint3d(this.x + dX, this.y + dY, this.z + dZ, this.dirX, this.dirY, this.dirZ);
171 }
172
173 @Override
174 public OrientedPoint3d scale(final double factor)
175 {
176 return new OrientedPoint3d(this.x * factor, this.y * factor, this.z * factor, this.dirX, this.dirY, this.dirZ);
177 }
178
179 @Override
180 public OrientedPoint3d neg()
181 {
182 return new OrientedPoint3d(-this.x, -this.y, -this.z, AngleUtil.normalizeAroundZero(this.dirX + Math.PI),
183 AngleUtil.normalizeAroundZero(this.dirY + Math.PI), AngleUtil.normalizeAroundZero(this.dirZ + Math.PI));
184 }
185
186 @Override
187 public OrientedPoint3d abs()
188 {
189 return new OrientedPoint3d(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z), this.dirX, this.dirY, this.dirZ);
190 }
191
192 @Override
193 public OrientedPoint3d normalize() throws DrawRuntimeException
194 {
195 double length = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
196 Throw.when(length == 0.0, DrawRuntimeException.class, "cannot normalize (0.0, 0.0, 0.0)");
197 return new OrientedPoint3d(this.x / length, this.y / length, this.z / length, this.dirX, this.dirY, this.dirZ);
198 }
199
200
201
202
203
204
205
206
207
208
209
210
211
212 public OrientedPoint3d interpolate(final OrientedPoint3d otherPoint, final double fraction)
213 {
214 Throw.whenNull(otherPoint, "otherPoint");
215 Throw.whenNaN(fraction, "fraction");
216 if (0.0 == fraction)
217 {
218 return this;
219 }
220 if (1.0 == fraction)
221 {
222 return otherPoint;
223 }
224 return new OrientedPoint3d((1.0 - fraction) * this.x + fraction * otherPoint.x,
225 (1.0 - fraction) * this.y + fraction * otherPoint.y, (1.0 - fraction) * this.z + fraction * otherPoint.z,
226 AngleUtil.interpolateShortest(this.dirX, otherPoint.dirX, fraction),
227 AngleUtil.interpolateShortest(this.dirY, otherPoint.dirY, fraction),
228 AngleUtil.interpolateShortest(this.dirZ, otherPoint.dirZ, fraction));
229 }
230
231
232
233
234
235
236
237
238
239 @Override
240 public OrientedPoint3d rotate(final double rotateZ)
241 {
242 Throw.whenNaN(rotateZ, "rotZ");
243 return new OrientedPoint3d(this.x, this.y, this.z, this.dirX, this.dirY,
244 AngleUtil.normalizeAroundZero(this.dirZ + rotateZ));
245 }
246
247
248
249
250
251
252
253
254
255
256 public OrientedPoint3d rotate(final double rotateX, final double rotateY, final double rotateZ)
257 {
258 Throw.whenNaN(rotateX, "rotateX");
259 Throw.whenNaN(rotateY, "rotateY");
260 Throw.whenNaN(rotateZ, "rotateZ");
261 return new OrientedPoint3d(this.x, this.y, this.z, AngleUtil.normalizeAroundZero(this.dirX + rotateX),
262 AngleUtil.normalizeAroundZero(this.dirY + rotateY), AngleUtil.normalizeAroundZero(this.dirZ + rotateZ));
263 }
264
265 @Override
266 public double getDirX()
267 {
268 return this.dirX;
269 }
270
271 @Override
272 public double getDirY()
273 {
274 return this.dirY;
275 }
276
277 @Override
278 public double getDirZ()
279 {
280 return this.dirZ;
281 }
282
283 @Override
284 public Iterator<Point3d> iterator()
285 {
286 return Arrays.stream(new Point3d[] {this}).iterator();
287 }
288
289 @Override
290 public String toString()
291 {
292 return toString("%f", false);
293 }
294
295 @Override
296 public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
297 {
298 String format = String.format("%1$s[x=%2$s, y=%2$s, z=%2$s, rotX=%2$s, rotY=%2$s, rotZ=%2$s]",
299 doNotIncludeClassName ? "" : "OrientedPoint3d ", doubleFormat);
300 return String.format(Locale.US, format, this.x, this.y, this.z, this.dirX, this.dirY, this.dirZ);
301 }
302
303 @Override
304 public boolean epsilonEquals(final OrientedPoint3d other, final double epsilonCoordinate, final double epsilonRotation)
305 throws NullPointerException, IllegalArgumentException
306 {
307 Throw.whenNull(other, "other");
308 if (Math.abs(this.x - other.x) > epsilonCoordinate)
309 {
310 return false;
311 }
312 if (Math.abs(this.y - other.y) > epsilonCoordinate)
313 {
314 return false;
315 }
316 if (Math.abs(this.z - other.z) > epsilonCoordinate)
317 {
318 return false;
319 }
320 if (Math.abs(AngleUtil.normalizeAroundZero(this.dirX - other.dirX)) > epsilonRotation)
321 {
322 return false;
323 }
324 if (Math.abs(AngleUtil.normalizeAroundZero(this.dirY - other.dirY)) > epsilonRotation)
325 {
326 return false;
327 }
328 if (Math.abs(AngleUtil.normalizeAroundZero(this.dirZ - other.dirZ)) > epsilonRotation)
329 {
330 return false;
331 }
332 return true;
333 }
334
335 @Override
336 public int hashCode()
337 {
338 final int prime = 31;
339 int result = super.hashCode();
340 long temp;
341 temp = Double.doubleToLongBits(this.dirX);
342 result = prime * result + (int) (temp ^ (temp >>> 32));
343 temp = Double.doubleToLongBits(this.dirY);
344 result = prime * result + (int) (temp ^ (temp >>> 32));
345 temp = Double.doubleToLongBits(this.dirZ);
346 result = prime * result + (int) (temp ^ (temp >>> 32));
347 return result;
348 }
349
350 @Override
351 @SuppressWarnings("checkstyle:needbraces")
352 public boolean equals(final Object obj)
353 {
354 if (this == obj)
355 return true;
356 if (!super.equals(obj))
357 return false;
358 OrientedPoint3d other = (OrientedPoint3d) obj;
359 if (Double.doubleToLongBits(this.dirX) != Double.doubleToLongBits(other.dirX))
360 return false;
361 if (Double.doubleToLongBits(this.dirY) != Double.doubleToLongBits(other.dirY))
362 return false;
363 if (Double.doubleToLongBits(this.dirZ) != Double.doubleToLongBits(other.dirZ))
364 return false;
365 return true;
366 }
367
368 }