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 }