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