View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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          // act/assert
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          // act/assert
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          // arrange
80          final Comparator<Vector3D> cmp = Vector3D.COORDINATE_ASCENDING_ORDER;
81  
82          // act/assert
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         // arrange
101         final Vector3D c = Vector3D.of(1, 2, 3);
102 
103         // act/assert
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         // arrange
112         final Vector3D c = Vector3D.of(1, 2, 3);
113 
114         // act
115         final double[] arr = c.toArray();
116 
117         // assert
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         // arrange
127         final Vector3D c = Vector3D.of(1, 2, 3);
128 
129         // act/assert
130         Assertions.assertEquals(3, c.getDimension());
131     }
132 
133     @Test
134     void testNaN() {
135         // act/assert
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         // act/assert
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         // act/assert
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         // act
187         final Vector3D zero = Vector3D.of(1, 2, 3).getZero();
188 
189         // assert
190         checkVector(zero, 0, 0, 0);
191         Assertions.assertEquals(0, zero.norm(), EPS);
192     }
193 
194     @Test
195     void testNorm() {
196         // act/assert
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         // arrange
205         final Vector3D v = Vector3D.of(1.0, 2.0, 3.0).normalize();
206 
207         // act/assert
208         Assertions.assertEquals(1.0, v.norm(), 0.0);
209     }
210 
211     @Test
212     void testNormSq() {
213         // act/assert
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         // arrange
222         final Vector3D v = Vector3D.of(1.0, 2.0, 3.0).normalize();
223 
224         // act/assert
225         Assertions.assertEquals(1.0, v.normSq(), 0.0);
226     }
227 
228     @Test
229     void testWithNorm() {
230         // arrange
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         // act/assert
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         // act/assert
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         // arrange
277         final Vector3D v = Vector3D.of(2.0, -3.0, 4.0).normalize();
278 
279         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
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         // act/assert
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         // arrange
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         // act/assert
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         // arrange
382         final double invSqrt3 = 1 / Math.sqrt(3);
383 
384         // act/assert
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         // arrange
420         final Pattern illegalNorm = Pattern.compile("^Illegal norm: (0\\.0|-?Infinity|NaN)");
421 
422         // act/assert
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         // arrange
436         final double invSqrt3 = 1 / Math.sqrt(3);
437         final Vector3D v = Vector3D.of(2, 2, 2).normalize();
438 
439         // act/assert
440         Assertions.assertSame(v, v.normalize());
441         checkVector(v.normalize(), invSqrt3, invSqrt3, invSqrt3);
442     }
443 
444     @Test
445     void testNormalizeOrNull() {
446         // arrange
447         final double invSqrt3 = 1 / Math.sqrt(3);
448 
449         // act/assert
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         // arrange
478         final double invSqrt3 = 1 / Math.sqrt(3);
479         final Vector3D v = Vector3D.of(2, 2, 2).normalizeOrNull();
480 
481         // act/assert
482         Assertions.assertSame(v, v.normalizeOrNull());
483         checkVector(v.normalizeOrNull(), invSqrt3, invSqrt3, invSqrt3);
484     }
485 
486     @Test
487     void testOrthogonal() {
488         // arrange
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         // act/assert
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         // act/assert
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         // arrange
513         final double invSqrt2 = 1.0 / Math.sqrt(2.0);
514 
515         // act/assert
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         // act/assert
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         // act/assert
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         // arrange
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         // act/assert
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         // arrange
571         final Vector3D v = Vector3D.of(1.0, 1.0, 1.0);
572 
573         // act/assert
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         // arrange
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         // act/assert
595         Assertions.assertTrue(Math.abs(v1.angle(v2) - 1.2) < 1.0e-12);
596     }
597 
598     @Test
599     void testCrossProduct() {
600         // act/assert
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         // the vectors u1 and u2 are nearly but not exactly anti-parallel
616         // (7.31e-16 degrees from 180 degrees) naive cross product (i.e.
617         // computing u1.x * u2.x + u1.y * u2.y + u1.z * u2.z
618         // leads to a result of   [0.0009765, -0.0001220, -0.0039062],
619         // instead of the correct [0.0006913, -0.0001254, -0.0007909]
620 
621         // arrange
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         // act
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         // assert
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         // we compare accurate versus naive cross product implementations
646         // on regular vectors (i.e. not extreme cases like in the previous test)
647         final UniformRandomProvider random = RandomSource.create(RandomSource.WELL_1024_A, 885362227452043215L);
648         for (int i = 0; i < 10000; ++i) {
649             // arrange
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             // act
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             // assert
662             Assertions.assertEquals(0.0, cAccurate.distance(cNaive), 6.0e-15 * cAccurate.norm());
663         }
664     }
665 
666     @Test
667     void testCrossProduct_cancellation() {
668         // act/assert
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         // arrange
682         final Vector3D v1 = Vector3D.of(2, 3, 4);
683         final Vector3D v2 = Vector3D.of(-2, -3, -4);
684 
685         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
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         // the following two vectors are nearly but not exactly orthogonal
759         // naive dot product (i.e. computing u1.x * u2.x + u1.y * u2.y + u1.z * u2.z
760         // leads to a result of 0.0, instead of the correct -1.855129...
761 
762         // arrange
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         // act
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         // assert
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         // we compare accurate versus naive dot product implementations
782         // on regular vectors (i.e. not extreme cases like in the previous test)
783         final UniformRandomProvider random = RandomSource.create(RandomSource.WELL_1024_A, 553267312521321237L);
784         for (int i = 0; i < 10000; ++i) {
785             // arrange
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             // act
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             // assert
798             Assertions.assertEquals(sNaive, sAccurate, 2.5e-16 * sAccurate);
799         }
800     }
801 
802     @Test
803     void testProject() {
804         // arrange
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         // act/assert
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         // arrange
835         final Vector3D v = Vector3D.of(1.0, 1.0, 1.0);
836 
837         // act/assert
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         // arrange
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         // act/assert
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         // arrange
877         final Vector3D v = Vector3D.of(1.0, 1.0, 1.0);
878 
879         // act/assert
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         // arrange
891         final double eps = 1e-12;
892 
893         // act/assert
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                 // ensure that the projection and rejection sum to the original vector
914                 EuclideanTestUtils.assertCoordinatesEqual(vec, proj.add(rej), eps);
915 
916                 final double angle = base.angle(vec);
917 
918                 // check the angle between the projection and the base; this will
919                 // be undefined when the angle between the original vector and the
920                 // base is pi/2 (which means that the projection is the zero vector)
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                 // check the angle between the rejection and the base; this should
928                 // always be pi/2 except for when the angle between the original vector
929                 // and the base is 0 or pi, in which case the rejection is the zero vector.
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         // act/assert
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         // act/assert
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         // act/assert
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         // act/assert
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         // arrange
976         final Vector3D p = Vector3D.of(1, 2, 3);
977 
978         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
1022         checkVector(v1.transform(transform), 3, 6, 9);
1023         checkVector(v2.transform(transform), -7, -8, -9);
1024     }
1025 
1026     @Test
1027     void testPrecisionEquals() {
1028         // arrange
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         // act/assert
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         // arrange
1060         final Precision.DoubleEquivalence smallEps = Precision.doubleEquivalenceOfEpsilon(1e-6);
1061         final Precision.DoubleEquivalence largeEps = Precision.doubleEquivalenceOfEpsilon(1e-1);
1062 
1063         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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         // act
1156         final String str = v.toString();
1157 
1158         // assert
1159         Assertions.assertTrue(pattern.matcher(str).matches(),
1160                 "Expected string " + str + " to match regex " + pattern);
1161     }
1162 
1163     @Test
1164     void testParse() {
1165         // act/assert
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         // act/assert
1180         Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.parse("abc"));
1181     }
1182 
1183     @Test
1184     void testOf() {
1185         // act/assert
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         // act/assert
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         // act/assert
1208         Assertions.assertThrows(IllegalArgumentException.class, () -> Vector3D.of(new double[] {0.0, 0.0}));
1209     }
1210 
1211     @Test
1212     void testUnitFrom_coordinates() {
1213         // arrange
1214         final double invSqrt3 = 1.0 / Math.sqrt(3.0);
1215 
1216         // act/assert
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         // arrange
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         // act/assert
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         // act/assert
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         // arrange
1257         final String msg = "Cannot compute vector max: no vectors given";
1258 
1259         // act/assert
1260         GeometryTestUtils.assertThrowsWithMessage(() -> {
1261             Vector3D.max(new ArrayList<>());
1262         }, IllegalArgumentException.class, msg);
1263     }
1264 
1265     @Test
1266     void testMin() {
1267         // act/assert
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         // arrange
1281         final String msg = "Cannot compute vector min: no vectors given";
1282 
1283         // act/assert
1284         GeometryTestUtils.assertThrowsWithMessage(() -> {
1285             Vector3D.min(new ArrayList<>());
1286         }, IllegalArgumentException.class, msg);
1287     }
1288 
1289     @Test
1290     void testCentroid() {
1291         // act/assert
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         // arrange
1310         final String msg = "Cannot compute centroid: no points given";
1311 
1312         // act/assert
1313         GeometryTestUtils.assertThrowsWithMessage(() -> {
1314             Vector3D.centroid(new ArrayList<>());
1315         }, IllegalArgumentException.class, msg);
1316     }
1317 
1318     @Test
1319     void testSum_factoryMethods() {
1320         // act/assert
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         // arrange
1333         final Vector3D p1 = Vector3D.of(1, 2, 3);
1334         final Vector3D p2 = Vector3D.of(4, 6, 8);
1335 
1336         // act/assert
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         // arrange
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         // act/assert
1357         Arrays.asList(p1, Vector3D.ZERO, p2).forEach(s);
1358         units.forEach(s);
1359 
1360         // assert
1361         checkVector(s.get(), 5, -3, 6);
1362     }
1363 
1364     @Test
1365     void testUnitFactoryOptimization() {
1366         // An already normalized vector will avoid unnecessary creation.
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 }