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