1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.geometry.euclidean.threed;
19
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.List;
25 import java.util.regex.Pattern;
26
27 import org.apache.commons.geometry.core.GeometryTestUtils;
28 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
29 import org.apache.commons.numbers.angle.Angle;
30 import org.apache.commons.numbers.core.Precision;
31 import org.apache.commons.rng.UniformRandomProvider;
32 import org.apache.commons.rng.simple.RandomSource;
33 import org.junit.jupiter.api.Assertions;
34 import org.junit.jupiter.api.Test;
35
36 class Vector3DTest {
37
38 private static final double EPS = 1e-15;
39
40 @Test
41 void testConstants() {
42
43 checkVector(Vector3D.ZERO, 0, 0, 0);
44
45 checkVector(Vector3D.Unit.PLUS_X, 1, 0, 0);
46 checkVector(Vector3D.Unit.MINUS_X, -1, 0, 0);
47
48 checkVector(Vector3D.Unit.PLUS_Y, 0, 1, 0);
49 checkVector(Vector3D.Unit.MINUS_Y, 0, -1, 0);
50
51 checkVector(Vector3D.Unit.PLUS_Z, 0, 0, 1);
52 checkVector(Vector3D.Unit.MINUS_Z, 0, 0, -1);
53
54 checkVector(Vector3D.NaN, Double.NaN, Double.NaN, Double.NaN);
55 checkVector(Vector3D.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
56 checkVector(Vector3D.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
57 }
58
59 @Test
60 void testConstants_normalize() {
61
62 Assertions.assertThrows(IllegalArgumentException.class, Vector3D.ZERO::normalize);
63 Assertions.assertThrows(IllegalArgumentException.class, Vector3D.NaN::normalize);
64 Assertions.assertThrows(IllegalArgumentException.class, Vector3D.POSITIVE_INFINITY::normalize);
65 Assertions.assertThrows(IllegalArgumentException.class, Vector3D.NEGATIVE_INFINITY::normalize);
66
67 Assertions.assertSame(Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_X.normalize());
68 Assertions.assertSame(Vector3D.Unit.MINUS_X, Vector3D.Unit.MINUS_X.normalize());
69
70 Assertions.assertSame(Vector3D.Unit.PLUS_Y, Vector3D.Unit.PLUS_Y.normalize());
71 Assertions.assertSame(Vector3D.Unit.MINUS_Y, Vector3D.Unit.MINUS_Y.normalize());
72
73 Assertions.assertSame(Vector3D.Unit.PLUS_Z, Vector3D.Unit.PLUS_Z.normalize());
74 Assertions.assertSame(Vector3D.Unit.MINUS_Z, Vector3D.Unit.MINUS_Z.normalize());
75 }
76
77 @Test
78 void testCoordinateAscendingOrder() {
79
80 final Comparator<Vector3D> cmp = Vector3D.COORDINATE_ASCENDING_ORDER;
81
82
83 Assertions.assertEquals(0, cmp.compare(Vector3D.of(1, 2, 3), Vector3D.of(1, 2, 3)));
84
85 Assertions.assertEquals(-1, cmp.compare(Vector3D.of(0, 2, 3), Vector3D.of(1, 2, 3)));
86 Assertions.assertEquals(-1, cmp.compare(Vector3D.of(1, 1, 3), Vector3D.of(1, 2, 3)));
87 Assertions.assertEquals(-1, cmp.compare(Vector3D.of(1, 2, 2), Vector3D.of(1, 2, 3)));
88
89 Assertions.assertEquals(1, cmp.compare(Vector3D.of(2, 2, 3), Vector3D.of(1, 2, 3)));
90 Assertions.assertEquals(1, cmp.compare(Vector3D.of(1, 3, 3), Vector3D.of(1, 2, 3)));
91 Assertions.assertEquals(1, cmp.compare(Vector3D.of(1, 2, 4), Vector3D.of(1, 2, 3)));
92
93 Assertions.assertEquals(-1, cmp.compare(Vector3D.of(1, 2, 3), null));
94 Assertions.assertEquals(1, cmp.compare(null, Vector3D.of(1, 2, 3)));
95 Assertions.assertEquals(0, cmp.compare(null, null));
96 }
97
98 @Test
99 void testCoordinates() {
100
101 final Vector3D c = Vector3D.of(1, 2, 3);
102
103
104 Assertions.assertEquals(1.0, c.getX(), EPS);
105 Assertions.assertEquals(2.0, c.getY(), EPS);
106 Assertions.assertEquals(3.0, c.getZ(), EPS);
107 }
108
109 @Test
110 void testToArray() {
111
112 final Vector3D c = Vector3D.of(1, 2, 3);
113
114
115 final double[] arr = c.toArray();
116
117
118 Assertions.assertEquals(3, arr.length);
119 Assertions.assertEquals(1.0, arr[0], EPS);
120 Assertions.assertEquals(2.0, arr[1], EPS);
121 Assertions.assertEquals(3.0, arr[2], EPS);
122 }
123
124 @Test
125 void testDimension() {
126
127 final Vector3D c = Vector3D.of(1, 2, 3);
128
129
130 Assertions.assertEquals(3, c.getDimension());
131 }
132
133 @Test
134 void testNaN() {
135
136 Assertions.assertTrue(Vector3D.of(0, 0, Double.NaN).isNaN());
137 Assertions.assertTrue(Vector3D.of(0, Double.NaN, 0).isNaN());
138 Assertions.assertTrue(Vector3D.of(Double.NaN, 0, 0).isNaN());
139
140 Assertions.assertFalse(Vector3D.of(1, 1, 1).isNaN());
141 Assertions.assertFalse(Vector3D.of(1, 1, Double.NEGATIVE_INFINITY).isNaN());
142 Assertions.assertFalse(Vector3D.of(1, Double.POSITIVE_INFINITY, 1).isNaN());
143 Assertions.assertFalse(Vector3D.of(Double.NEGATIVE_INFINITY, 1, 1).isNaN());
144 }
145
146 @Test
147 void testInfinite() {
148
149 Assertions.assertTrue(Vector3D.of(0, 0, Double.NEGATIVE_INFINITY).isInfinite());
150 Assertions.assertTrue(Vector3D.of(0, Double.NEGATIVE_INFINITY, 0).isInfinite());
151 Assertions.assertTrue(Vector3D.of(Double.NEGATIVE_INFINITY, 0, 0).isInfinite());
152 Assertions.assertTrue(Vector3D.of(0, 0, Double.POSITIVE_INFINITY).isInfinite());
153 Assertions.assertTrue(Vector3D.of(0, Double.POSITIVE_INFINITY, 0).isInfinite());
154 Assertions.assertTrue(Vector3D.of(Double.POSITIVE_INFINITY, 0, 0).isInfinite());
155
156 Assertions.assertFalse(Vector3D.of(1, 1, 1).isInfinite());
157 Assertions.assertFalse(Vector3D.of(0, 0, Double.NaN).isInfinite());
158 Assertions.assertFalse(Vector3D.of(0, Double.NEGATIVE_INFINITY, Double.NaN).isInfinite());
159 Assertions.assertFalse(Vector3D.of(Double.NaN, 0, Double.NEGATIVE_INFINITY).isInfinite());
160 Assertions.assertFalse(Vector3D.of(Double.POSITIVE_INFINITY, Double.NaN, 0).isInfinite());
161 Assertions.assertFalse(Vector3D.of(0, Double.NaN, Double.POSITIVE_INFINITY).isInfinite());
162 }
163
164 @Test
165 void testFinite() {
166
167 Assertions.assertTrue(Vector3D.ZERO.isFinite());
168 Assertions.assertTrue(Vector3D.of(1, 1, 1).isFinite());
169
170 Assertions.assertFalse(Vector3D.of(0, 0, Double.NEGATIVE_INFINITY).isFinite());
171 Assertions.assertFalse(Vector3D.of(0, Double.NEGATIVE_INFINITY, 0).isFinite());
172 Assertions.assertFalse(Vector3D.of(Double.NEGATIVE_INFINITY, 0, 0).isFinite());
173 Assertions.assertFalse(Vector3D.of(0, 0, Double.POSITIVE_INFINITY).isFinite());
174 Assertions.assertFalse(Vector3D.of(0, Double.POSITIVE_INFINITY, 0).isFinite());
175 Assertions.assertFalse(Vector3D.of(Double.POSITIVE_INFINITY, 0, 0).isFinite());
176
177 Assertions.assertFalse(Vector3D.of(0, 0, Double.NaN).isFinite());
178 Assertions.assertFalse(Vector3D.of(0, Double.NEGATIVE_INFINITY, Double.NaN).isFinite());
179 Assertions.assertFalse(Vector3D.of(Double.NaN, 0, Double.NEGATIVE_INFINITY).isFinite());
180 Assertions.assertFalse(Vector3D.of(Double.POSITIVE_INFINITY, Double.NaN, 0).isFinite());
181 Assertions.assertFalse(Vector3D.of(0, Double.NaN, Double.POSITIVE_INFINITY).isFinite());
182 }
183
184 @Test
185 void testZero() {
186
187 final Vector3D zero = Vector3D.of(1, 2, 3).getZero();
188
189
190 checkVector(zero, 0, 0, 0);
191 Assertions.assertEquals(0, zero.norm(), EPS);
192 }
193
194 @Test
195 void testNorm() {
196
197 Assertions.assertEquals(0.0, Vector3D.ZERO.norm(), 0);
198 Assertions.assertEquals(Math.sqrt(29), Vector3D.of(2, 3, 4).norm(), EPS);
199 Assertions.assertEquals(Math.sqrt(29), Vector3D.of(-2, -3, -4).norm(), EPS);
200 }
201
202 @Test
203 void testNorm_unitVectors() {
204
205 final Vector3D v = Vector3D.of(1.0, 2.0, 3.0).normalize();
206
207
208 Assertions.assertEquals(1.0, v.norm(), 0.0);
209 }
210
211 @Test
212 void testNormSq() {
213
214 Assertions.assertEquals(0.0, Vector3D.ZERO.normSq(), 0);
215 Assertions.assertEquals(29, Vector3D.of(2, 3, 4).normSq(), EPS);
216 Assertions.assertEquals(29, Vector3D.of(-2, -3, -4).normSq(), EPS);
217 }
218
219 @Test
220 void testNormSq_unitVectors() {
221
222 final Vector3D v = Vector3D.of(1.0, 2.0, 3.0).normalize();
223
224
225 Assertions.assertEquals(1.0, v.normSq(), 0.0);
226 }
227
228 @Test
229 void testWithNorm() {
230
231 final double x = 2;
232 final double y = 3;
233 final double z = 4;
234
235 final double len = Math.sqrt((x * x) + (y * y) + (z * z));
236
237 final double normX = x / len;
238 final double normY = y / len;
239 final double normZ = z / len;
240
241
242 checkVector(Vector3D.of(x, y, z).withNorm(0.0), 0.0, 0.0, 0.0);
243
244 checkVector(Vector3D.of(x, y, z).withNorm(1.0), normX, normY, normZ);
245 checkVector(Vector3D.of(x, y, -z).withNorm(1.0), normX, normY, -normZ);
246 checkVector(Vector3D.of(x, -y, z).withNorm(1.0), normX, -normY, normZ);
247 checkVector(Vector3D.of(x, -y, -z).withNorm(1.0), normX, -normY, -normZ);
248 checkVector(Vector3D.of(-x, y, z).withNorm(1.0), -normX, normY, normZ);
249 checkVector(Vector3D.of(-x, y, -z).withNorm(1.0), -normX, normY, -normZ);
250 checkVector(Vector3D.of(-x, -y, z).withNorm(1.0), -normX, -normY, normZ);
251 checkVector(Vector3D.of(-x, -y, -z).withNorm(1.0), -normX, -normY, -normZ);
252
253 checkVector(Vector3D.of(x, y, z).withNorm(0.5), 0.5 * normX, 0.5 * normY, 0.5 * normZ);
254 checkVector(Vector3D.of(x, y, z).withNorm(3), 3 * normX, 3 * normY, 3 * normZ);
255
256 checkVector(Vector3D.of(x, y, z).withNorm(-0.5), -0.5 * normX, -0.5 * normY, -0.5 * normZ);
257 checkVector(Vector3D.of(x, y, z).withNorm(-3), -3 * normX, -3 * normY, -3 * normZ);
258
259 for (int i = 0; i <= 10; i++) {
260 final double mag = i * 0.12345 - 5;
261 Assertions.assertEquals(Math.abs(mag), Vector3D.of(x, y, z).withNorm(mag).norm(), EPS);
262 }
263 }
264
265 @Test
266 void testWithNorm_illegalNorm() {
267
268 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.ZERO.withNorm(2.0));
269 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.NaN.withNorm(2.0));
270 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.POSITIVE_INFINITY.withNorm(2.0));
271 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.NEGATIVE_INFINITY.withNorm(2.0));
272 }
273
274 @Test
275 void testWithNorm_unitVectors() {
276
277 final Vector3D v = Vector3D.of(2.0, -3.0, 4.0).normalize();
278
279
280 checkVector(Vector3D.Unit.PLUS_X.withNorm(2.5), 2.5, 0.0, 0.0);
281 checkVector(Vector3D.Unit.MINUS_Y.withNorm(3.14), 0.0, -3.14, 0.0);
282 checkVector(Vector3D.Unit.PLUS_Z.withNorm(-1.1), 0.0, 0.0, -1.1);
283
284 for (double mag = -10.0; mag <= 10.0; ++mag) {
285 Assertions.assertEquals(Math.abs(mag), v.withNorm(mag).norm(), EPS);
286 }
287 }
288
289 @Test
290 void testAdd() {
291
292 final Vector3D v1 = Vector3D.of(1, 2, 3);
293 final Vector3D v2 = Vector3D.of(-4, -5, -6);
294 final Vector3D v3 = Vector3D.of(7, 8, 9);
295
296
297 checkVector(v1.add(v1), 2, 4, 6);
298
299 checkVector(v1.add(v2), -3, -3, -3);
300 checkVector(v2.add(v1), -3, -3, -3);
301
302 checkVector(v1.add(v3), 8, 10, 12);
303 checkVector(v3.add(v1), 8, 10, 12);
304 }
305
306 @Test
307 void testAdd_scaled() {
308
309 final Vector3D v1 = Vector3D.of(1, 2, 3);
310 final Vector3D v2 = Vector3D.of(-4, -5, -6);
311 final Vector3D v3 = Vector3D.of(7, 8, 9);
312
313
314 checkVector(v1.add(0, v1), 1, 2, 3);
315 checkVector(v1.add(0.5, v1), 1.5, 3, 4.5);
316 checkVector(v1.add(1, v1), 2, 4, 6);
317
318 checkVector(v1.add(2, v2), -7, -8, -9);
319 checkVector(v2.add(2, v1), -2, -1, -0);
320
321 checkVector(v1.add(-2, v3), -13, -14, -15);
322 checkVector(v3.add(-2, v1), 5, 4, 3);
323 }
324
325 @Test
326 void testSubtract() {
327
328 final Vector3D v1 = Vector3D.of(1, 2, 3);
329 final Vector3D v2 = Vector3D.of(-4, -5, -6);
330 final Vector3D v3 = Vector3D.of(7, 8, 9);
331
332
333 checkVector(v1.subtract(v1), 0, 0, 0);
334
335 checkVector(v1.subtract(v2), 5, 7, 9);
336 checkVector(v2.subtract(v1), -5, -7, -9);
337
338 checkVector(v1.subtract(v3), -6, -6, -6);
339 checkVector(v3.subtract(v1), 6, 6, 6);
340 }
341
342 @Test
343 void testSubtract_scaled() {
344
345 final Vector3D v1 = Vector3D.of(1, 2, 3);
346 final Vector3D v2 = Vector3D.of(-4, -5, -6);
347 final Vector3D v3 = Vector3D.of(7, 8, 9);
348
349
350 checkVector(v1.subtract(0, v1), 1, 2, 3);
351 checkVector(v1.subtract(0.5, v1), 0.5, 1, 1.5);
352 checkVector(v1.subtract(1, v1), 0, 0, 0);
353
354 checkVector(v1.subtract(2, v2), 9, 12, 15);
355 checkVector(v2.subtract(2, v1), -6, -9, -12);
356
357 checkVector(v1.subtract(-2, v3), 15, 18, 21);
358 checkVector(v3.subtract(-2, v1), 9, 12, 15);
359 }
360
361 @Test
362 void testNegate() {
363
364 checkVector(Vector3D.of(0.1, 2.5, 1.3).negate(), -0.1, -2.5, -1.3);
365 checkVector(Vector3D.of(-0.1, -2.5, -1.3).negate(), 0.1, 2.5, 1.3);
366 }
367
368 @Test
369 void testNegate_unitVectors() {
370
371 final Vector3D v1 = Vector3D.of(1.0, 2.0, 3.0).normalize();
372 final Vector3D v2 = Vector3D.of(-2.0, -4.0, -3.0).normalize();
373
374
375 checkVector(v1.negate(), -1.0 / Math.sqrt(14.0), -Math.sqrt(2.0 / 7.0), -3.0 / Math.sqrt(14.0));
376 checkVector(v2.negate(), 2.0 / Math.sqrt(29.0), 4.0 / Math.sqrt(29.0), 3.0 / Math.sqrt(29.0));
377 }
378
379 @Test
380 void testNormalize() {
381
382 final double invSqrt3 = 1 / Math.sqrt(3);
383
384
385 checkVector(Vector3D.of(100, 0, 0).normalize(), 1, 0, 0);
386 checkVector(Vector3D.of(-100, 0, 0).normalize(), -1, 0, 0);
387
388 checkVector(Vector3D.of(0, 100, 0).normalize(), 0, 1, 0);
389 checkVector(Vector3D.of(0, -100, 0).normalize(), 0, -1, 0);
390
391 checkVector(Vector3D.of(0, 0, 100).normalize(), 0, 0, 1);
392 checkVector(Vector3D.of(0, 0, -100).normalize(), 0, 0, -1);
393
394 checkVector(Vector3D.of(2, 2, 2).normalize(), invSqrt3, invSqrt3, invSqrt3);
395 checkVector(Vector3D.of(-2, -2, -2).normalize(), -invSqrt3, -invSqrt3, -invSqrt3);
396
397 checkVector(Vector3D.of(Double.MIN_VALUE, 0, 0).normalize(), 1, 0, 0);
398 checkVector(Vector3D.of(0, Double.MIN_VALUE, 0).normalize(), 0, 1, 0);
399 checkVector(Vector3D.of(0, 0, Double.MIN_VALUE).normalize(), 0, 0, 1);
400
401 checkVector(Vector3D.of(-Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE).normalize(),
402 -invSqrt3, invSqrt3, invSqrt3);
403
404 checkVector(Vector3D.of(Double.MIN_NORMAL, 0, 0).normalize(), 1, 0, 0);
405 checkVector(Vector3D.of(0, Double.MIN_NORMAL, 0).normalize(), 0, 1, 0);
406 checkVector(Vector3D.of(0, 0, Double.MIN_NORMAL).normalize(), 0, 0, 1);
407
408 checkVector(Vector3D.of(Double.MIN_NORMAL, Double.MIN_NORMAL, -Double.MIN_NORMAL).normalize(),
409 invSqrt3, invSqrt3, -invSqrt3);
410
411 checkVector(Vector3D.of(Double.MAX_VALUE, -Double.MAX_VALUE, Double.MAX_VALUE).normalize(),
412 invSqrt3, -invSqrt3, invSqrt3);
413
414 Assertions.assertEquals(1.0, Vector3D.of(5, -4, 2).normalize().norm(), EPS);
415 }
416
417 @Test
418 void testNormalize_illegalNorm() {
419
420 final Pattern illegalNorm = Pattern.compile("^Illegal norm: (0\\.0|-?Infinity|NaN)");
421
422
423 GeometryTestUtils.assertThrowsWithMessage(Vector3D.ZERO::normalize,
424 IllegalArgumentException.class, illegalNorm);
425 GeometryTestUtils.assertThrowsWithMessage(Vector3D.NaN::normalize,
426 IllegalArgumentException.class, illegalNorm);
427 GeometryTestUtils.assertThrowsWithMessage(Vector3D.POSITIVE_INFINITY::normalize,
428 IllegalArgumentException.class, illegalNorm);
429 GeometryTestUtils.assertThrowsWithMessage(Vector3D.NEGATIVE_INFINITY::normalize,
430 IllegalArgumentException.class, illegalNorm);
431 }
432
433 @Test
434 void testNormalize_isIdempotent() {
435
436 final double invSqrt3 = 1 / Math.sqrt(3);
437 final Vector3D v = Vector3D.of(2, 2, 2).normalize();
438
439
440 Assertions.assertSame(v, v.normalize());
441 checkVector(v.normalize(), invSqrt3, invSqrt3, invSqrt3);
442 }
443
444 @Test
445 void testNormalizeOrNull() {
446
447 final double invSqrt3 = 1 / Math.sqrt(3);
448
449
450 checkVector(Vector3D.of(100, 0, 0).normalizeOrNull(), 1, 0, 0);
451 checkVector(Vector3D.of(-100, 0, 0).normalizeOrNull(), -1, 0, 0);
452
453 checkVector(Vector3D.of(2, 2, 2).normalizeOrNull(), invSqrt3, invSqrt3, invSqrt3);
454 checkVector(Vector3D.of(-2, -2, -2).normalizeOrNull(), -invSqrt3, -invSqrt3, -invSqrt3);
455
456 checkVector(Vector3D.of(Double.MIN_VALUE, 0, 0).normalizeOrNull(), 1, 0, 0);
457 checkVector(Vector3D.of(0, Double.MIN_VALUE, 0).normalizeOrNull(), 0, 1, 0);
458 checkVector(Vector3D.of(0, 0, Double.MIN_VALUE).normalizeOrNull(), 0, 0, 1);
459
460 checkVector(Vector3D.of(-Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE).normalizeOrNull(),
461 -invSqrt3, invSqrt3, invSqrt3);
462
463 checkVector(Vector3D.of(Double.MIN_NORMAL, Double.MIN_NORMAL, -Double.MIN_NORMAL).normalizeOrNull(),
464 invSqrt3, invSqrt3, -invSqrt3);
465
466 checkVector(Vector3D.of(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE).normalizeOrNull(),
467 -invSqrt3, -invSqrt3, -invSqrt3);
468
469 Assertions.assertNull(Vector3D.ZERO.normalizeOrNull());
470 Assertions.assertNull(Vector3D.NaN.normalizeOrNull());
471 Assertions.assertNull(Vector3D.POSITIVE_INFINITY.normalizeOrNull());
472 Assertions.assertNull(Vector3D.NEGATIVE_INFINITY.normalizeOrNull());
473 }
474
475 @Test
476 void testNormalizeOrNull_isIdempotent() {
477
478 final double invSqrt3 = 1 / Math.sqrt(3);
479 final Vector3D v = Vector3D.of(2, 2, 2).normalizeOrNull();
480
481
482 Assertions.assertSame(v, v.normalizeOrNull());
483 checkVector(v.normalizeOrNull(), invSqrt3, invSqrt3, invSqrt3);
484 }
485
486 @Test
487 void testOrthogonal() {
488
489 final Vector3D v1 = Vector3D.of(0.1, 2.5, 1.3);
490 final Vector3D v2 = Vector3D.of(2.3, -0.003, 7.6);
491 final Vector3D v3 = Vector3D.of(-1.7, 1.4, 0.2);
492 final Vector3D v4 = Vector3D.of(4.2, 0.1, -1.8);
493
494
495 Assertions.assertEquals(0.0, v1.dot(v1.orthogonal()), EPS);
496 Assertions.assertEquals(0.0, v2.dot(v2.orthogonal()), EPS);
497 Assertions.assertEquals(0.0, v3.dot(v3.orthogonal()), EPS);
498 Assertions.assertEquals(0.0, v4.dot(v4.orthogonal()), EPS);
499 }
500
501 @Test
502 void testOrthogonal_illegalNorm() {
503
504 Assertions.assertThrows(IllegalArgumentException.class, Vector3D.ZERO::orthogonal);
505 Assertions.assertThrows(IllegalArgumentException.class, Vector3D.NaN::orthogonal);
506 Assertions.assertThrows(IllegalArgumentException.class, Vector3D.POSITIVE_INFINITY::orthogonal);
507 Assertions.assertThrows(IllegalArgumentException.class, Vector3D.NEGATIVE_INFINITY::orthogonal);
508 }
509
510 @Test
511 void testOrthogonal_givenDirection() {
512
513 final double invSqrt2 = 1.0 / Math.sqrt(2.0);
514
515
516 checkVector(Vector3D.Unit.PLUS_X.orthogonal(Vector3D.of(-1.0, 0.1, 0.0)), 0.0, 1.0, 0.0);
517 checkVector(Vector3D.Unit.PLUS_Y.orthogonal(Vector3D.of(2.0, 2.0, 2.0)), invSqrt2, 0.0, invSqrt2);
518 checkVector(Vector3D.Unit.PLUS_Z.orthogonal(Vector3D.of(3.0, 3.0, -3.0)), invSqrt2, invSqrt2, 0.0);
519
520 checkVector(Vector3D.of(invSqrt2, invSqrt2, 0.0).orthogonal(Vector3D.of(1.0, 1.0, 0.2)), 0.0, 0.0, 1.0);
521 }
522
523 @Test
524 void testOrthogonal_givenDirection_illegalNorm() {
525
526
527 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.ZERO.orthogonal(Vector3D.Unit.PLUS_X));
528 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.NaN.orthogonal(Vector3D.Unit.PLUS_X));
529 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.POSITIVE_INFINITY.orthogonal(Vector3D.Unit.PLUS_X));
530 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.NEGATIVE_INFINITY.orthogonal(Vector3D.Unit.PLUS_X));
531 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.Unit.PLUS_X.orthogonal(Vector3D.ZERO));
532 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.Unit.PLUS_X.orthogonal(Vector3D.NaN));
533 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.Unit.PLUS_X.orthogonal(Vector3D.POSITIVE_INFINITY));
534 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.Unit.PLUS_X.orthogonal(Vector3D.NEGATIVE_INFINITY));
535 }
536
537 @Test
538 void testOrthogonal_givenDirection_directionIsCollinear() {
539
540 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.Unit.PLUS_X.orthogonal(Vector3D.Unit.PLUS_X));
541 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.Unit.PLUS_X.orthogonal(Vector3D.Unit.MINUS_X));
542 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.of(1.0, 1.0, 1.0).orthogonal(Vector3D.of(2.0, 2.0, 2.0)));
543 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.of(-1.01, -1.01, -1.01).orthogonal(Vector3D.of(20.1, 20.1, 20.1)));
544 }
545
546 @Test
547 void testAngle() {
548
549 final double tolerance = 1e-10;
550
551 final Vector3D v1 = Vector3D.of(1, 2, 3);
552 final Vector3D v2 = Vector3D.of(4, 5, 6);
553
554
555 Assertions.assertEquals(0.22572612855273393616, v1.angle(v2), tolerance);
556 Assertions.assertEquals(7.98595620686106654517199e-8, v1.angle(Vector3D.of(2, 4, 6.000001)), tolerance);
557 Assertions.assertEquals(3.14159257373023116985197793156, v1.angle(Vector3D.of(-2, -4, -6.000001)), tolerance);
558
559 Assertions.assertEquals(0.0, Vector3D.Unit.PLUS_X.angle(Vector3D.Unit.PLUS_X), tolerance);
560 Assertions.assertEquals(Math.PI, Vector3D.Unit.PLUS_X.angle(Vector3D.Unit.MINUS_X), tolerance);
561
562 Assertions.assertEquals(Angle.PI_OVER_TWO, Vector3D.Unit.PLUS_X.angle(Vector3D.Unit.PLUS_Y), tolerance);
563 Assertions.assertEquals(Angle.PI_OVER_TWO, Vector3D.Unit.PLUS_X.angle(Vector3D.Unit.MINUS_Y), tolerance);
564 Assertions.assertEquals(Angle.PI_OVER_TWO, Vector3D.Unit.PLUS_X.angle(Vector3D.Unit.PLUS_Z), tolerance);
565 Assertions.assertEquals(Angle.PI_OVER_TWO, Vector3D.Unit.PLUS_X.angle(Vector3D.Unit.MINUS_Z), tolerance);
566 }
567
568 @Test
569 void testAngle_illegalNorm() {
570
571 final Vector3D v = Vector3D.of(1.0, 1.0, 1.0);
572
573
574
575 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.ZERO.angle(v));
576 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.NaN.angle(v));
577 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.POSITIVE_INFINITY.angle(v));
578 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.NEGATIVE_INFINITY.angle(v));
579 Assertions.assertThrows(IllegalArgumentException.class, () -> v.angle(Vector3D.ZERO));
580 Assertions.assertThrows(IllegalArgumentException.class, () -> v.angle(Vector3D.NaN));
581 Assertions.assertThrows(IllegalArgumentException.class, () -> v.angle(Vector3D.POSITIVE_INFINITY));
582 Assertions.assertThrows(IllegalArgumentException.class, () -> v.angle(Vector3D.NEGATIVE_INFINITY));
583 }
584
585 @Test
586 void testAngle_angularSeparation() {
587
588 final Vector3D v1 = Vector3D.of(2, -1, 4);
589
590 final Vector3D k = v1.normalize();
591 final Vector3D i = k.orthogonal();
592 final Vector3D v2 = k.multiply(Math.cos(1.2)).add(i.multiply(Math.sin(1.2)));
593
594
595 Assertions.assertTrue(Math.abs(v1.angle(v2) - 1.2) < 1.0e-12);
596 }
597
598 @Test
599 void testCrossProduct() {
600
601 checkVector(Vector3D.Unit.PLUS_X.cross(Vector3D.Unit.PLUS_Y), 0, 0, 1);
602 checkVector(Vector3D.Unit.PLUS_X.cross(Vector3D.Unit.MINUS_Y), 0, 0, -1);
603
604 checkVector(Vector3D.Unit.MINUS_X.cross(Vector3D.Unit.MINUS_Y), 0, 0, 1);
605 checkVector(Vector3D.Unit.MINUS_X.cross(Vector3D.Unit.PLUS_Y), 0, 0, -1);
606
607 checkVector(Vector3D.of(2, 1, -4).cross(Vector3D.of(3, 1, -1)), 3, -10, -1);
608
609 final double invSqrt6 = 1 / Math.sqrt(6);
610 checkVector(Vector3D.of(1, 1, 1).cross(Vector3D.of(-1, 0, 1)).normalize(), invSqrt6, -2 * invSqrt6, invSqrt6);
611 }
612
613 @Test
614 void testCrossProduct_nearlyAntiParallel() {
615
616
617
618
619
620
621
622 final Vector3D u1 = Vector3D.of(-1321008684645961.0 / 268435456.0,
623 -5774608829631843.0 / 268435456.0,
624 -7645843051051357.0 / 8589934592.0);
625 final Vector3D u2 = Vector3D.of(1796571811118507.0 / 2147483648.0,
626 7853468008299307.0 / 2147483648.0,
627 2599586637357461.0 / 17179869184.0);
628 final Vector3D u3 = Vector3D.of(12753243807587107.0 / 18446744073709551616.0,
629 -2313766922703915.0 / 18446744073709551616.0,
630 -227970081415313.0 / 288230376151711744.0);
631
632
633 final Vector3D cNaive = Vector3D.of(u1.getY() * u2.getZ() - u1.getZ() * u2.getY(),
634 u1.getZ() * u2.getX() - u1.getX() * u2.getZ(),
635 u1.getX() * u2.getY() - u1.getY() * u2.getX());
636 final Vector3D cAccurate = u1.cross(u2);
637
638
639 Assertions.assertTrue(u3.distance(cNaive) > 2.9 * u3.norm());
640 Assertions.assertEquals(0.0, u3.distance(cAccurate), 1.0e-30 * cAccurate.norm());
641 }
642
643 @Test
644 void testCrossProduct_accuracy() {
645
646
647 final UniformRandomProvider random = RandomSource.create(RandomSource.WELL_1024_A, 885362227452043215L);
648 for (int i = 0; i < 10000; ++i) {
649
650 final double ux = 10000 * random.nextDouble();
651 final double uy = 10000 * random.nextDouble();
652 final double uz = 10000 * random.nextDouble();
653 final double vx = 10000 * random.nextDouble();
654 final double vy = 10000 * random.nextDouble();
655 final double vz = 10000 * random.nextDouble();
656
657
658 final Vector3D cNaive = Vector3D.of(uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx);
659 final Vector3D cAccurate = Vector3D.of(ux, uy, uz).cross(Vector3D.of(vx, vy, vz));
660
661
662 Assertions.assertEquals(0.0, cAccurate.distance(cNaive), 6.0e-15 * cAccurate.norm());
663 }
664 }
665
666 @Test
667 void testCrossProduct_cancellation() {
668
669 final Vector3D v1 = Vector3D.of(9070467121.0, 4535233560.0, 1);
670 final Vector3D v2 = Vector3D.of(9070467123.0, 4535233561.0, 1);
671 checkVector(v1.cross(v2), -1, 2, 1);
672
673 final double scale = Math.scalb(1.0, 100);
674 final Vector3D big1 = v1.multiply(scale);
675 final Vector3D small2 = v2.multiply(1 / scale);
676 checkVector(big1.cross(small2), -1, 2, 1);
677 }
678
679 @Test
680 void testScalarMultiply() {
681
682 final Vector3D v1 = Vector3D.of(2, 3, 4);
683 final Vector3D v2 = Vector3D.of(-2, -3, -4);
684
685
686 checkVector(v1.multiply(0), 0, 0, 0);
687 checkVector(v1.multiply(0.5), 1, 1.5, 2);
688 checkVector(v1.multiply(1), 2, 3, 4);
689 checkVector(v1.multiply(2), 4, 6, 8);
690 checkVector(v1.multiply(-2), -4, -6, -8);
691
692 checkVector(v2.multiply(0), 0, 0, 0);
693 checkVector(v2.multiply(0.5), -1, -1.5, -2);
694 checkVector(v2.multiply(1), -2, -3, -4);
695 checkVector(v2.multiply(2), -4, -6, -8);
696 checkVector(v2.multiply(-2), 4, 6, 8);
697 }
698
699 @Test
700 void testDistance() {
701
702 final Vector3D v1 = Vector3D.of(1, -2, 3);
703 final Vector3D v2 = Vector3D.of(-4, 2, 0);
704 final Vector3D v3 = Vector3D.of(5, -6, -7);
705
706
707 Assertions.assertEquals(0.0, v1.distance(v1), EPS);
708 Assertions.assertEquals(0.0, v2.distance(v2), EPS);
709
710 Assertions.assertEquals(Math.sqrt(50), v1.distance(v2), EPS);
711 Assertions.assertEquals(Math.sqrt(50), v2.distance(v1), EPS);
712
713 Assertions.assertEquals(v1.subtract(v2).norm(), v1.distance(v2), EPS);
714
715 Assertions.assertEquals(Math.sqrt(132), v1.distance(v3), EPS);
716 Assertions.assertEquals(Math.sqrt(132), v3.distance(v1), EPS);
717 }
718
719 @Test
720 void testDistanceSq() {
721
722 final Vector3D v1 = Vector3D.of(1, -2, 3);
723 final Vector3D v2 = Vector3D.of(-4, 2, 0);
724 final Vector3D v3 = Vector3D.of(5, -6, -7);
725
726
727 Assertions.assertEquals(0.0, v1.distanceSq(v1), EPS);
728 Assertions.assertEquals(0.0, v2.distanceSq(v2), EPS);
729
730 Assertions.assertEquals(50, v1.distanceSq(v2), EPS);
731 Assertions.assertEquals(50, v2.distanceSq(v1), EPS);
732
733 Assertions.assertEquals(v1.subtract(v2).normSq(), v1.distanceSq(v2), EPS);
734
735 Assertions.assertEquals(132, v1.distanceSq(v3), EPS);
736 Assertions.assertEquals(132, v3.distanceSq(v1), EPS);
737 }
738
739 @Test
740 void testDotProduct() {
741
742 final Vector3D v1 = Vector3D.of(1, -2, 3);
743 final Vector3D v2 = Vector3D.of(-4, 5, -6);
744 final Vector3D v3 = Vector3D.of(7, 8, 9);
745
746
747 Assertions.assertEquals(14, v1.dot(v1), EPS);
748
749 Assertions.assertEquals(-32, v1.dot(v2), EPS);
750 Assertions.assertEquals(-32, v2.dot(v1), EPS);
751
752 Assertions.assertEquals(18, v1.dot(v3), EPS);
753 Assertions.assertEquals(18, v3.dot(v1), EPS);
754 }
755
756 @Test
757 void testDotProduct_nearlyOrthogonal() {
758
759
760
761
762
763 final Vector3D u1 = Vector3D.of(-1321008684645961.0 / 268435456.0,
764 -5774608829631843.0 / 268435456.0,
765 -7645843051051357.0 / 8589934592.0);
766 final Vector3D u2 = Vector3D.of(-5712344449280879.0 / 2097152.0,
767 -4550117129121957.0 / 2097152.0,
768 8846951984510141.0 / 131072.0);
769
770
771 final double sNaive = u1.getX() * u2.getX() + u1.getY() * u2.getY() + u1.getZ() * u2.getZ();
772 final double sAccurate = u1.dot(u2);
773
774
775 Assertions.assertEquals(0.0, sNaive, 1.0e-30);
776 Assertions.assertEquals(-2088690039198397.0 / 1125899906842624.0, sAccurate, 1.0e-15);
777 }
778
779 @Test
780 void testDotProduct_accuracy() {
781
782
783 final UniformRandomProvider random = RandomSource.create(RandomSource.WELL_1024_A, 553267312521321237L);
784 for (int i = 0; i < 10000; ++i) {
785
786 final double ux = 10000 * random.nextDouble();
787 final double uy = 10000 * random.nextDouble();
788 final double uz = 10000 * random.nextDouble();
789 final double vx = 10000 * random.nextDouble();
790 final double vy = 10000 * random.nextDouble();
791 final double vz = 10000 * random.nextDouble();
792
793
794 final double sNaive = ux * vx + uy * vy + uz * vz;
795 final double sAccurate = Vector3D.of(ux, uy, uz).dot(Vector3D.of(vx, vy, vz));
796
797
798 Assertions.assertEquals(sNaive, sAccurate, 2.5e-16 * sAccurate);
799 }
800 }
801
802 @Test
803 void testProject() {
804
805 final Vector3D v1 = Vector3D.of(2.0, 3.0, 4.0);
806 final Vector3D v2 = Vector3D.of(-5.0, -6.0, -7.0);
807
808
809 checkVector(Vector3D.ZERO.project(Vector3D.Unit.PLUS_X), 0.0, 0.0, 0.0);
810
811 checkVector(v1.project(Vector3D.Unit.PLUS_X), 2.0, 0.0, 0.0);
812 checkVector(v1.project(Vector3D.Unit.MINUS_X), 2.0, 0.0, 0.0);
813 checkVector(v1.project(Vector3D.Unit.PLUS_Y), 0.0, 3.0, 0.0);
814 checkVector(v1.project(Vector3D.Unit.MINUS_Y), 0.0, 3.0, 0.0);
815 checkVector(v1.project(Vector3D.Unit.PLUS_Z), 0.0, 0.0, 4.0);
816 checkVector(v1.project(Vector3D.Unit.MINUS_Z), 0.0, 0.0, 4.0);
817
818 checkVector(v2.project(Vector3D.Unit.PLUS_X), -5.0, 0.0, 0.0);
819 checkVector(v2.project(Vector3D.Unit.MINUS_X), -5.0, 0.0, 0.0);
820 checkVector(v2.project(Vector3D.Unit.PLUS_Y), 0.0, -6.0, 0.0);
821 checkVector(v2.project(Vector3D.Unit.MINUS_Y), 0.0, -6.0, 0.0);
822 checkVector(v2.project(Vector3D.Unit.PLUS_Z), 0.0, 0.0, -7.0);
823 checkVector(v2.project(Vector3D.Unit.MINUS_Z), 0.0, 0.0, -7.0);
824
825 checkVector(v1.project(Vector3D.of(1.0, 1.0, 1.0)), 3.0, 3.0, 3.0);
826 checkVector(v1.project(Vector3D.of(-1.0, -1.0, -1.0)), 3.0, 3.0, 3.0);
827
828 checkVector(v2.project(Vector3D.of(1.0, 1.0, 1.0)), -6.0, -6.0, -6.0);
829 checkVector(v2.project(Vector3D.of(-1.0, -1.0, -1.0)), -6.0, -6.0, -6.0);
830 }
831
832 @Test
833 void testProject_baseHasIllegalNorm() {
834
835 final Vector3D v = Vector3D.of(1.0, 1.0, 1.0);
836
837
838 Assertions.assertThrows(IllegalArgumentException.class, () -> v.project(Vector3D.ZERO));
839 Assertions.assertThrows(IllegalArgumentException.class, () -> v.project(Vector3D.NaN));
840 Assertions.assertThrows(IllegalArgumentException.class, () -> v.project(Vector3D.POSITIVE_INFINITY));
841 Assertions.assertThrows(IllegalArgumentException.class, () -> v.project(Vector3D.NEGATIVE_INFINITY));
842 }
843
844 @Test
845 void testReject() {
846
847 final Vector3D v1 = Vector3D.of(2.0, 3.0, 4.0);
848 final Vector3D v2 = Vector3D.of(-5.0, -6.0, -7.0);
849
850
851 checkVector(Vector3D.ZERO.reject(Vector3D.Unit.PLUS_X), 0.0, 0.0, 0.0);
852
853 checkVector(v1.reject(Vector3D.Unit.PLUS_X), 0.0, 3.0, 4.0);
854 checkVector(v1.reject(Vector3D.Unit.MINUS_X), 0.0, 3.0, 4.0);
855 checkVector(v1.reject(Vector3D.Unit.PLUS_Y), 2.0, 0.0, 4.0);
856 checkVector(v1.reject(Vector3D.Unit.MINUS_Y), 2.0, 0.0, 4.0);
857 checkVector(v1.reject(Vector3D.Unit.PLUS_Z), 2.0, 3.0, 0.0);
858 checkVector(v1.reject(Vector3D.Unit.MINUS_Z), 2.0, 3.0, 0.0);
859
860 checkVector(v2.reject(Vector3D.Unit.PLUS_X), 0.0, -6.0, -7.0);
861 checkVector(v2.reject(Vector3D.Unit.MINUS_X), 0.0, -6.0, -7.0);
862 checkVector(v2.reject(Vector3D.Unit.PLUS_Y), -5.0, 0.0, -7.0);
863 checkVector(v2.reject(Vector3D.Unit.MINUS_Y), -5.0, 0.0, -7.0);
864 checkVector(v2.reject(Vector3D.Unit.PLUS_Z), -5.0, -6.0, 0.0);
865 checkVector(v2.reject(Vector3D.Unit.MINUS_Z), -5.0, -6.0, 0.0);
866
867 checkVector(v1.reject(Vector3D.of(1.0, 1.0, 1.0)), -1.0, 0.0, 1.0);
868 checkVector(v1.reject(Vector3D.of(-1.0, -1.0, -1.0)), -1.0, 0.0, 1.0);
869
870 checkVector(v2.reject(Vector3D.of(1.0, 1.0, 1.0)), 1.0, 0.0, -1.0);
871 checkVector(v2.reject(Vector3D.of(-1.0, -1.0, -1.0)), 1.0, 0.0, -1.0);
872 }
873
874 @Test
875 void testReject_baseHasIllegalNorm() {
876
877 final Vector3D v = Vector3D.of(1.0, 1.0, 1.0);
878
879
880
881 Assertions.assertThrows(IllegalArgumentException.class, () -> v.reject(Vector3D.ZERO));
882 Assertions.assertThrows(IllegalArgumentException.class, () -> v.reject(Vector3D.NaN));
883 Assertions.assertThrows(IllegalArgumentException.class, () -> v.reject(Vector3D.POSITIVE_INFINITY));
884 Assertions.assertThrows(IllegalArgumentException.class, () -> v.reject(Vector3D.NEGATIVE_INFINITY));
885
886 }
887
888 @Test
889 void testProjectAndReject_areComplementary() {
890
891 final double eps = 1e-12;
892
893
894 checkProjectAndRejectFullSphere(Vector3D.of(1.0, 0.0, 0.0), 1.0, eps);
895 checkProjectAndRejectFullSphere(Vector3D.of(0.0, 1.0, 0.0), 2.0, eps);
896 checkProjectAndRejectFullSphere(Vector3D.of(0.0, 0.0, 1.0), 2.0, eps);
897 checkProjectAndRejectFullSphere(Vector3D.of(1.0, 1.0, 1.0), 3.0, eps);
898
899 checkProjectAndRejectFullSphere(Vector3D.of(-2.0, 0.0, 0.0), 1.0, eps);
900 checkProjectAndRejectFullSphere(Vector3D.of(0.0, -2.0, 0.0), 2.0, eps);
901 checkProjectAndRejectFullSphere(Vector3D.of(0.0, 0.0, -2.0), 2.0, eps);
902 checkProjectAndRejectFullSphere(Vector3D.of(-2.0, -2.0, -2.0), 3.0, eps);
903 }
904
905 private void checkProjectAndRejectFullSphere(final Vector3D vec, final double baseMag, final double eps) {
906 for (double polar = 0.0; polar <= Math.PI; polar += 0.5) {
907 for (double azimuth = 0.0; azimuth <= Angle.TWO_PI; azimuth += 0.5) {
908 final Vector3D base = SphericalCoordinates.toCartesian(baseMag, azimuth, polar);
909
910 final Vector3D proj = vec.project(base);
911 final Vector3D rej = vec.reject(base);
912
913
914 EuclideanTestUtils.assertCoordinatesEqual(vec, proj.add(rej), eps);
915
916 final double angle = base.angle(vec);
917
918
919
920
921 if (angle < Angle.PI_OVER_TWO) {
922 Assertions.assertEquals(0.0, proj.angle(base), eps);
923 } else if (angle > Angle.PI_OVER_TWO) {
924 Assertions.assertEquals(Math.PI, proj.angle(base), eps);
925 }
926
927
928
929
930 if (angle > 0.0 && angle < Math.PI) {
931 Assertions.assertEquals(Angle.PI_OVER_TWO, rej.angle(base), eps);
932 }
933 }
934 }
935 }
936
937 @Test
938 void testVectorTo() {
939
940 final Vector3D p1 = Vector3D.of(1, 2, 3);
941 final Vector3D p2 = Vector3D.of(4, 5, 6);
942 final Vector3D p3 = Vector3D.of(-7, -8, -9);
943
944
945 checkVector(p1.vectorTo(p1), 0, 0, 0);
946 checkVector(p2.vectorTo(p2), 0, 0, 0);
947 checkVector(p3.vectorTo(p3), 0, 0, 0);
948
949 checkVector(p1.vectorTo(p2), 3, 3, 3);
950 checkVector(p2.vectorTo(p1), -3, -3, -3);
951
952 checkVector(p1.vectorTo(p3), -8, -10, -12);
953 checkVector(p3.vectorTo(p1), 8, 10, 12);
954 }
955
956 @Test
957 void testDirectionTo() {
958
959 final double invSqrt3 = 1.0 / Math.sqrt(3);
960
961 final Vector3D p1 = Vector3D.of(1, 1, 1);
962 final Vector3D p2 = Vector3D.of(1, 5, 1);
963 final Vector3D p3 = Vector3D.of(-2, -2, -2);
964
965
966 checkVector(p1.directionTo(p2), 0, 1, 0);
967 checkVector(p2.directionTo(p1), 0, -1, 0);
968
969 checkVector(p1.directionTo(p3), -invSqrt3, -invSqrt3, -invSqrt3);
970 checkVector(p3.directionTo(p1), invSqrt3, invSqrt3, invSqrt3);
971 }
972
973 @Test
974 void testDirectionTo_illegalNorm() {
975
976 final Vector3D p = Vector3D.of(1, 2, 3);
977
978
979 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.ZERO.directionTo(Vector3D.ZERO));
980 Assertions.assertThrows(IllegalArgumentException.class, () -> p.directionTo(p));
981 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.NEGATIVE_INFINITY.directionTo(p));
982 Assertions.assertThrows(IllegalArgumentException.class, () -> p.directionTo(Vector3D.POSITIVE_INFINITY));
983 }
984
985 @Test
986 void testLerp() {
987
988 final Vector3D v1 = Vector3D.of(1, -5, 2);
989 final Vector3D v2 = Vector3D.of(-4, 0, 2);
990 final Vector3D v3 = Vector3D.of(10, -4, 0);
991
992
993 checkVector(v1.lerp(v1, 0), 1, -5, 2);
994 checkVector(v1.lerp(v1, 1), 1, -5, 2);
995
996 checkVector(v1.lerp(v2, -0.25), 2.25, -6.25, 2);
997 checkVector(v1.lerp(v2, 0), 1, -5, 2);
998 checkVector(v1.lerp(v2, 0.25), -0.25, -3.75, 2);
999 checkVector(v1.lerp(v2, 0.5), -1.5, -2.5, 2);
1000 checkVector(v1.lerp(v2, 0.75), -2.75, -1.25, 2);
1001 checkVector(v1.lerp(v2, 1), -4, 0, 2);
1002 checkVector(v1.lerp(v2, 1.25), -5.25, 1.25, 2);
1003
1004 checkVector(v1.lerp(v3, 0), 1, -5, 2);
1005 checkVector(v1.lerp(v3, 0.25), 3.25, -4.75, 1.5);
1006 checkVector(v1.lerp(v3, 0.5), 5.5, -4.5, 1);
1007 checkVector(v1.lerp(v3, 0.75), 7.75, -4.25, 0.5);
1008 checkVector(v1.lerp(v3, 1), 10, -4, 0);
1009 }
1010
1011 @Test
1012 void testTransform() {
1013
1014 final AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity()
1015 .scale(2)
1016 .translate(1, 2, 3);
1017
1018 final Vector3D v1 = Vector3D.of(1, 2, 3);
1019 final Vector3D v2 = Vector3D.of(-4, -5, -6);
1020
1021
1022 checkVector(v1.transform(transform), 3, 6, 9);
1023 checkVector(v2.transform(transform), -7, -8, -9);
1024 }
1025
1026 @Test
1027 void testPrecisionEquals() {
1028
1029 final Precision.DoubleEquivalence smallEps = Precision.doubleEquivalenceOfEpsilon(1e-6);
1030 final Precision.DoubleEquivalence largeEps = Precision.doubleEquivalenceOfEpsilon(1e-1);
1031
1032 final Vector3D vec = Vector3D.of(1, -2, 3);
1033
1034
1035 Assertions.assertTrue(vec.eq(vec, smallEps));
1036 Assertions.assertTrue(vec.eq(vec, largeEps));
1037
1038 Assertions.assertTrue(vec.eq(Vector3D.of(1.0000007, -2.0000009, 3.0000009), smallEps));
1039 Assertions.assertTrue(vec.eq(Vector3D.of(1.0000007, -2.0000009, 3.0000009), largeEps));
1040
1041 Assertions.assertFalse(vec.eq(Vector3D.of(1.004, -2, 3), smallEps));
1042 Assertions.assertFalse(vec.eq(Vector3D.of(1, -2.004, 3), smallEps));
1043 Assertions.assertFalse(vec.eq(Vector3D.of(1, -2, 2.999), smallEps));
1044 Assertions.assertTrue(vec.eq(Vector3D.of(1.004, -2.004, 2.999), largeEps));
1045
1046 Assertions.assertFalse(vec.eq(Vector3D.of(2, -2, 3), smallEps));
1047 Assertions.assertFalse(vec.eq(Vector3D.of(1, -3, 3), smallEps));
1048 Assertions.assertFalse(vec.eq(Vector3D.of(1, -2, 4), smallEps));
1049 Assertions.assertFalse(vec.eq(Vector3D.of(2, -3, 4), smallEps));
1050
1051 Assertions.assertFalse(vec.eq(Vector3D.of(2, -2, 3), largeEps));
1052 Assertions.assertFalse(vec.eq(Vector3D.of(1, -3, 3), largeEps));
1053 Assertions.assertFalse(vec.eq(Vector3D.of(1, -2, 4), largeEps));
1054 Assertions.assertFalse(vec.eq(Vector3D.of(2, -3, 4), largeEps));
1055 }
1056
1057 @Test
1058 void testIsZero() {
1059
1060 final Precision.DoubleEquivalence smallEps = Precision.doubleEquivalenceOfEpsilon(1e-6);
1061 final Precision.DoubleEquivalence largeEps = Precision.doubleEquivalenceOfEpsilon(1e-1);
1062
1063
1064 Assertions.assertTrue(Vector3D.of(0.0, -0.0, 0.0).isZero(smallEps));
1065 Assertions.assertTrue(Vector3D.of(-0.0, 0.0, -0.0).isZero(largeEps));
1066
1067 Assertions.assertTrue(Vector3D.of(-1e-7, 1e-7, -1e-8).isZero(smallEps));
1068 Assertions.assertTrue(Vector3D.of(1e-7, -1e-7, 1e-8).isZero(largeEps));
1069
1070 Assertions.assertFalse(Vector3D.of(1e-2, 0.0, 0.0).isZero(smallEps));
1071 Assertions.assertFalse(Vector3D.of(0.0, 1e-2, 0.0).isZero(smallEps));
1072 Assertions.assertFalse(Vector3D.of(0.0, 0.0, 1e-2).isZero(smallEps));
1073 Assertions.assertTrue(Vector3D.of(1e-2, -1e-2, 1e-2).isZero(largeEps));
1074
1075 Assertions.assertFalse(Vector3D.of(0.2, 0.0, 0.0).isZero(smallEps));
1076 Assertions.assertFalse(Vector3D.of(0.0, 0.2, 0.0).isZero(smallEps));
1077 Assertions.assertFalse(Vector3D.of(0.0, 0.0, 0.2).isZero(smallEps));
1078 Assertions.assertFalse(Vector3D.of(0.2, 0.2, 0.2).isZero(smallEps));
1079
1080 Assertions.assertFalse(Vector3D.of(0.2, 0.0, 0.0).isZero(largeEps));
1081 Assertions.assertFalse(Vector3D.of(0.0, 0.2, 0.0).isZero(largeEps));
1082 Assertions.assertFalse(Vector3D.of(0.0, 0.0, 0.2).isZero(largeEps));
1083 Assertions.assertFalse(Vector3D.of(0.2, 0.2, 0.2).isZero(largeEps));
1084 }
1085
1086 @Test
1087 void testHashCode() {
1088
1089 final double delta = 10 * Precision.EPSILON;
1090 final Vector3D u = Vector3D.of(1, 1, 1);
1091 final Vector3D v = Vector3D.of(1 + delta, 1 + delta, 1 + delta);
1092 final Vector3D w = Vector3D.of(1, 1, 1);
1093
1094
1095 Assertions.assertTrue(u.hashCode() != v.hashCode());
1096 Assertions.assertEquals(u.hashCode(), w.hashCode());
1097
1098 Assertions.assertEquals(Vector3D.of(0, 0, Double.NaN).hashCode(), Vector3D.NaN.hashCode());
1099 Assertions.assertEquals(Vector3D.of(0, Double.NaN, 0).hashCode(), Vector3D.NaN.hashCode());
1100 Assertions.assertEquals(Vector3D.of(Double.NaN, 0, 0).hashCode(), Vector3D.NaN.hashCode());
1101 Assertions.assertEquals(Vector3D.of(0, 0, Double.NaN).hashCode(), Vector3D.of(Double.NaN, 0, 0).hashCode());
1102 }
1103
1104 @Test
1105 void testEquals() {
1106
1107 final double delta = 10 * Precision.EPSILON;
1108 final Vector3D u1 = Vector3D.of(1, 2, 3);
1109 final Vector3D u2 = Vector3D.of(1, 2, 3);
1110
1111
1112 GeometryTestUtils.assertSimpleEqualsCases(u1);
1113 Assertions.assertEquals(u1, u2);
1114
1115 Assertions.assertNotEquals(u1, Vector3D.of(-1, -2, -3));
1116 Assertions.assertNotEquals(u1, Vector3D.of(1 + delta, 2, 3));
1117 Assertions.assertNotEquals(u1, Vector3D.of(1, 2 + delta, 3));
1118 Assertions.assertNotEquals(u1, Vector3D.of(1, 2, 3 + delta));
1119
1120 Assertions.assertEquals(Vector3D.of(0, Double.NaN, 0), Vector3D.of(Double.NaN, 0, 0));
1121
1122 Assertions.assertEquals(Vector3D.of(0, 0, Double.POSITIVE_INFINITY), Vector3D.of(0, 0, Double.POSITIVE_INFINITY));
1123 Assertions.assertNotEquals(Vector3D.of(0, Double.POSITIVE_INFINITY, 0), Vector3D.of(0, 0, Double.POSITIVE_INFINITY));
1124 Assertions.assertNotEquals(Vector3D.of(Double.POSITIVE_INFINITY, 0, 0), Vector3D.of(0, 0, Double.POSITIVE_INFINITY));
1125
1126 Assertions.assertEquals(Vector3D.of(Double.NEGATIVE_INFINITY, 0, 0), Vector3D.of(Double.NEGATIVE_INFINITY, 0, 0));
1127 Assertions.assertNotEquals(Vector3D.of(0, Double.NEGATIVE_INFINITY, 0), Vector3D.of(Double.NEGATIVE_INFINITY, 0, 0));
1128 Assertions.assertNotEquals(Vector3D.of(0, 0, Double.NEGATIVE_INFINITY), Vector3D.of(Double.NEGATIVE_INFINITY, 0, 0));
1129 }
1130
1131 @Test
1132 void testEqualsAndHashCode_signedZeroConsistency() {
1133
1134 final Vector3D a = Vector3D.of(0.0, -0.0, 0.0);
1135 final Vector3D b = Vector3D.of(-0.0, 0.0, -0.0);
1136 final Vector3D c = Vector3D.of(0.0, -0.0, 0.0);
1137 final Vector3D d = Vector3D.of(-0.0, 0.0, -0.0);
1138
1139
1140 Assertions.assertFalse(a.equals(b));
1141
1142 Assertions.assertTrue(a.equals(c));
1143 Assertions.assertEquals(a.hashCode(), c.hashCode());
1144
1145 Assertions.assertTrue(b.equals(d));
1146 Assertions.assertEquals(b.hashCode(), d.hashCode());
1147 }
1148
1149 @Test
1150 void testToString() {
1151
1152 final Vector3D v = Vector3D.of(1, 2, 3);
1153 final Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}, 3.{0,2}\\)");
1154
1155
1156 final String str = v.toString();
1157
1158
1159 Assertions.assertTrue(pattern.matcher(str).matches(),
1160 "Expected string " + str + " to match regex " + pattern);
1161 }
1162
1163 @Test
1164 void testParse() {
1165
1166 checkVector(Vector3D.parse("(1, 2, 3)"), 1, 2, 3);
1167 checkVector(Vector3D.parse("(-1, -2, -3)"), -1, -2, -3);
1168
1169 checkVector(Vector3D.parse("(0.01, -1e-3, 0)"), 1e-2, -1e-3, 0);
1170
1171 checkVector(Vector3D.parse("(NaN, -Infinity, Infinity)"), Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
1172
1173 checkVector(Vector3D.parse(Vector3D.ZERO.toString()), 0, 0, 0);
1174 checkVector(Vector3D.parse(Vector3D.Unit.MINUS_X.toString()), -1, 0, 0);
1175 }
1176
1177 @Test
1178 void testParse_failure() {
1179
1180 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.parse("abc"));
1181 }
1182
1183 @Test
1184 void testOf() {
1185
1186 checkVector(Vector3D.of(1, 2, 3), 1, 2, 3);
1187 checkVector(Vector3D.of(-1, -2, -3), -1, -2, -3);
1188 checkVector(Vector3D.of(Math.PI, Double.NaN, Double.POSITIVE_INFINITY),
1189 Math.PI, Double.NaN, Double.POSITIVE_INFINITY);
1190 checkVector(Vector3D.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E),
1191 Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E);
1192 }
1193
1194 @Test
1195 void testOf_arrayArg() {
1196
1197 checkVector(Vector3D.of(new double[] {1, 2, 3}), 1, 2, 3);
1198 checkVector(Vector3D.of(new double[] {-1, -2, -3}), -1, -2, -3);
1199 checkVector(Vector3D.of(new double[] {Math.PI, Double.NaN, Double.POSITIVE_INFINITY}),
1200 Math.PI, Double.NaN, Double.POSITIVE_INFINITY);
1201 checkVector(Vector3D.of(new double[] {Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E}),
1202 Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E);
1203 }
1204
1205 @Test
1206 void testOf_arrayArg_invalidDimensions() {
1207
1208 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.of(new double[] {0.0, 0.0}));
1209 }
1210
1211 @Test
1212 void testUnitFrom_coordinates() {
1213
1214 final double invSqrt3 = 1.0 / Math.sqrt(3.0);
1215
1216
1217 checkVector(Vector3D.Unit.from(2.0, -2.0, 2.0), invSqrt3, -invSqrt3, invSqrt3);
1218 checkVector(Vector3D.Unit.from(-4.0, 4.0, -4.0), -invSqrt3, invSqrt3, -invSqrt3);
1219 }
1220
1221 @Test
1222 void testUnitFrom_vector() {
1223
1224 final double invSqrt3 = 1.0 / Math.sqrt(3.0);
1225 final Vector3D vec = Vector3D.of(2.0, -2.0, 2.0);
1226 final Vector3D.Unit unitVec = Vector3D.Unit.from(2.0, -2.0, 2.0);
1227
1228
1229 checkVector(Vector3D.Unit.from(vec), invSqrt3, -invSqrt3, invSqrt3);
1230 Assertions.assertSame(unitVec, Vector3D.Unit.from(unitVec));
1231 }
1232
1233 @Test
1234 void testUnitFrom_static_illegalNorm() {
1235 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.Unit.from(0.0, 0.0, 0.0));
1236 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.Unit.from(Double.NaN, 1.0, 1.0));
1237 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.Unit.from(1.0, Double.NEGATIVE_INFINITY, 1.0));
1238 Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.Unit.from(1.0, 1.0, Double.POSITIVE_INFINITY));
1239 }
1240
1241 @Test
1242 void testMax() {
1243
1244 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-100, 1, 100),
1245 Vector3D.max(Collections.singletonList(Vector3D.of(-100, 1, 100))), EPS);
1246
1247 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 100),
1248 Vector3D.max(Arrays.asList(Vector3D.of(-100, 1, 100), Vector3D.of(0, 1, 0))), EPS);
1249
1250 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 0, 2),
1251 Vector3D.max(Vector3D.of(-2, 0, 0), Vector3D.of(-1, -5, 1), Vector3D.of(-10, -10, 2)), EPS);
1252 }
1253
1254 @Test
1255 void testMax_noPointsGiven() {
1256
1257 final String msg = "Cannot compute vector max: no vectors given";
1258
1259
1260 GeometryTestUtils.assertThrowsWithMessage(() -> {
1261 Vector3D.max(new ArrayList<>());
1262 }, IllegalArgumentException.class, msg);
1263 }
1264
1265 @Test
1266 void testMin() {
1267
1268 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-100, 1, 100),
1269 Vector3D.min(Collections.singletonList(Vector3D.of(-100, 1, 100))), EPS);
1270
1271 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-100, 1, 0),
1272 Vector3D.min(Arrays.asList(Vector3D.of(-100, 1, 100), Vector3D.of(0, 1, 0))), EPS);
1273
1274 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-10, -10, 0),
1275 Vector3D.min(Vector3D.of(-2, 0, 0), Vector3D.of(-1, -5, 1), Vector3D.of(-10, -10, 2)), EPS);
1276 }
1277
1278 @Test
1279 void testMin_noPointsGiven() {
1280
1281 final String msg = "Cannot compute vector min: no vectors given";
1282
1283
1284 GeometryTestUtils.assertThrowsWithMessage(() -> {
1285 Vector3D.min(new ArrayList<>());
1286 }, IllegalArgumentException.class, msg);
1287 }
1288
1289 @Test
1290 void testCentroid() {
1291
1292 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 3),
1293 Vector3D.centroid(Vector3D.of(1, 2, 3)), EPS);
1294
1295 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2.5, 3.5, 4.5),
1296 Vector3D.centroid(Vector3D.of(1, 2, 3), Vector3D.of(2, 3, 4),
1297 Vector3D.of(3, 4, 5), Vector3D.of(4, 5, 6)), EPS);
1298
1299 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 3),
1300 Vector3D.centroid(Collections.singletonList(Vector3D.of(1, 2, 3))), EPS);
1301
1302 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 1, 1.5),
1303 Vector3D.centroid(Arrays.asList(Vector3D.of(1, 2, 3), Vector3D.of(1, 2, 3),
1304 Vector3D.ZERO, Vector3D.ZERO)), EPS);
1305 }
1306
1307 @Test
1308 void testCentroid_noPointsGiven() {
1309
1310 final String msg = "Cannot compute centroid: no points given";
1311
1312
1313 GeometryTestUtils.assertThrowsWithMessage(() -> {
1314 Vector3D.centroid(new ArrayList<>());
1315 }, IllegalArgumentException.class, msg);
1316 }
1317
1318 @Test
1319 void testSum_factoryMethods() {
1320
1321 checkVector(Vector3D.Sum.create().get(), 0, 0, 0);
1322 checkVector(Vector3D.Sum.of(Vector3D.of(1, 2, 3)).get(), 1, 2, 3);
1323 checkVector(Vector3D.Sum.of(
1324 Vector3D.of(1, 2, 3),
1325 Vector3D.Unit.PLUS_X,
1326 Vector3D.Unit.PLUS_Y,
1327 Vector3D.Unit.PLUS_Z).get(), 2, 3, 4);
1328 }
1329
1330 @Test
1331 void testSum_instanceMethods() {
1332
1333 final Vector3D p1 = Vector3D.of(1, 2, 3);
1334 final Vector3D p2 = Vector3D.of(4, 6, 8);
1335
1336
1337 checkVector(Vector3D.Sum.create()
1338 .add(p1)
1339 .addScaled(0.5, p2)
1340 .get(), 3, 5, 7);
1341 }
1342
1343 @Test
1344 void testSum_accept() {
1345
1346 final Vector3D p1 = Vector3D.of(1, 2, -3);
1347 final Vector3D p2 = Vector3D.of(3, -6, 8);
1348
1349 final List<Vector3D.Unit> units = Arrays.asList(
1350 Vector3D.Unit.PLUS_X,
1351 Vector3D.Unit.PLUS_Y,
1352 Vector3D.Unit.PLUS_Z);
1353
1354 final Vector3D.Sum s = Vector3D.Sum.create();
1355
1356
1357 Arrays.asList(p1, Vector3D.ZERO, p2).forEach(s);
1358 units.forEach(s);
1359
1360
1361 checkVector(s.get(), 5, -3, 6);
1362 }
1363
1364 @Test
1365 void testUnitFactoryOptimization() {
1366
1367 final Vector3D v = Vector3D.of(3, 4, 5).normalize();
1368 Assertions.assertSame(v, v.normalize());
1369 }
1370
1371 private void checkVector(final Vector3D v, final double x, final double y, final double z) {
1372 Assertions.assertEquals(x, v.getX(), EPS);
1373 Assertions.assertEquals(y, v.getY(), EPS);
1374 Assertions.assertEquals(z, v.getZ(), EPS);
1375 }
1376 }