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  package org.apache.commons.geometry.euclidean.twod;
    18
    19  import java.util.ArrayList;
    20  import java.util.Arrays;
    21  import java.util.Collections;
    22  import java.util.Comparator;
    23  import java.util.regex.Pattern;
    24
    25  import org.apache.commons.geometry.core.GeometryTestUtils;
    26  import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
    27  import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
    28  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
    29  import org.apache.commons.numbers.angle.PlaneAngleRadians;
    30  import org.apache.commons.numbers.core.Precision;
    31  import org.junit.Assert;
    32  import org.junit.Test;
    33
    34  public class Vector2DTest {
    35
    36      private static final double EPS = Math.ulp(1d);
    37
    38      @Test
    39      public void testConstants() {
    40          // act/assert
    41          checkVector(Vector2D.ZERO, 0, 0);
    42          checkVector(Vector2D.Unit.PLUS_X, 1, 0);
    43          checkVector(Vector2D.Unit.MINUS_X, -1, 0);
    44          checkVector(Vector2D.Unit.PLUS_Y, 0, 1);
    45          checkVector(Vector2D.Unit.MINUS_Y, 0, -1);
    46          checkVector(Vector2D.NaN, Double.NaN, Double.NaN);
    47          checkVector(Vector2D.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
    48          checkVector(Vector2D.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
    49      }
    50
    51      @Test
    52      public void testConstants_normalize() {
    53          // act/assert
    54          GeometryTestUtils.assertThrows(Vector2D.ZERO::normalize, IllegalArgumentException.class);
    55          GeometryTestUtils.assertThrows(Vector2D.NaN::normalize, IllegalArgumentException.class);
    56          GeometryTestUtils.assertThrows(Vector2D.POSITIVE_INFINITY::normalize, IllegalArgumentException.class);
    57          GeometryTestUtils.assertThrows(Vector2D.NEGATIVE_INFINITY::normalize, IllegalArgumentException.class);
    58
    59          Assert.assertSame(Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X.normalize());
    60          Assert.assertSame(Vector2D.Unit.MINUS_X, Vector2D.Unit.MINUS_X.normalize());
    61
    62          Assert.assertSame(Vector2D.Unit.PLUS_Y, Vector2D.Unit.PLUS_Y.normalize());
    63          Assert.assertSame(Vector2D.Unit.MINUS_Y, Vector2D.Unit.MINUS_Y.normalize());
    64      }
    65
    66      @Test
    67      public void testCoordinateAscendingOrder() {
    68          // arrange
    69          final Comparator<Vector2D> cmp = Vector2D.COORDINATE_ASCENDING_ORDER;
    70
    71          // act/assert
    72          Assert.assertEquals(0, cmp.compare(Vector2D.of(1, 2), Vector2D.of(1, 2)));
    73
    74          Assert.assertEquals(-1, cmp.compare(Vector2D.of(0, 2), Vector2D.of(1, 2)));
    75          Assert.assertEquals(-1, cmp.compare(Vector2D.of(1, 1), Vector2D.of(1, 2)));
    76
    77          Assert.assertEquals(1, cmp.compare(Vector2D.of(2, 2), Vector2D.of(1, 2)));
    78          Assert.assertEquals(1, cmp.compare(Vector2D.of(1, 3), Vector2D.of(1, 2)));
    79
    80          Assert.assertEquals(-1, cmp.compare(Vector2D.of(1, 3), null));
    81          Assert.assertEquals(1, cmp.compare(null, Vector2D.of(1, 2)));
    82          Assert.assertEquals(0, cmp.compare(null, null));
    83      }
    84
    85      @Test
    86      public void testCoordinates() {
    87          // arrange
    88          final Vector2D v = Vector2D.of(1, 2);
    89
    90          // act/assert
    91          Assert.assertEquals(1.0, v.getX(), EPS);
    92          Assert.assertEquals(2.0, v.getY(), EPS);
    93      }
    94
    95      @Test
    96      public void testToArray() {
    97          // arrange
    98          final Vector2D oneTwo = Vector2D.of(1, 2);
    99
    100         // act
    101         final double[] array = oneTwo.toArray();
    102
    103         // assert
    104         Assert.assertEquals(2, array.length);
    105         Assert.assertEquals(1.0, array[0], EPS);
    106         Assert.assertEquals(2.0, array[1], EPS);
    107     }
    108
    109     @Test
    110     public void testDimension() {
    111         // arrange
    112         final Vector2D v = Vector2D.of(1, 2);
    113
    114         // act/assert
    115         Assert.assertEquals(2, v.getDimension());
    116     }
    117
    118     @Test
    119     public void testNaN() {
    120         // act/assert
    121         Assert.assertTrue(Vector2D.of(0, Double.NaN).isNaN());
    122         Assert.assertTrue(Vector2D.of(Double.NaN, 0).isNaN());
    123
    124         Assert.assertFalse(Vector2D.of(1, 1).isNaN());
    125         Assert.assertFalse(Vector2D.of(1, Double.NEGATIVE_INFINITY).isNaN());
    126         Assert.assertFalse(Vector2D.of(Double.POSITIVE_INFINITY, 1).isNaN());
    127     }
    128
    129     @Test
    130     public void testInfinite() {
    131         // act/assert
    132         Assert.assertTrue(Vector2D.of(0, Double.NEGATIVE_INFINITY).isInfinite());
    133         Assert.assertTrue(Vector2D.of(Double.NEGATIVE_INFINITY, 0).isInfinite());
    134         Assert.assertTrue(Vector2D.of(0, Double.POSITIVE_INFINITY).isInfinite());
    135         Assert.assertTrue(Vector2D.of(Double.POSITIVE_INFINITY, 0).isInfinite());
    136
    137         Assert.assertFalse(Vector2D.of(1, 1).isInfinite());
    138         Assert.assertFalse(Vector2D.of(0, Double.NaN).isInfinite());
    139         Assert.assertFalse(Vector2D.of(Double.NEGATIVE_INFINITY, Double.NaN).isInfinite());
    140         Assert.assertFalse(Vector2D.of(Double.NaN, Double.NEGATIVE_INFINITY).isInfinite());
    141         Assert.assertFalse(Vector2D.of(Double.POSITIVE_INFINITY, Double.NaN).isInfinite());
    142         Assert.assertFalse(Vector2D.of(Double.NaN, Double.POSITIVE_INFINITY).isInfinite());
    143     }
    144
    145     @Test
    146     public void testFinite() {
    147         // act/assert
    148         Assert.assertTrue(Vector2D.ZERO.isFinite());
    149         Assert.assertTrue(Vector2D.of(1, 1).isFinite());
    150
    151         Assert.assertFalse(Vector2D.of(0, Double.NEGATIVE_INFINITY).isFinite());
    152         Assert.assertFalse(Vector2D.of(Double.NEGATIVE_INFINITY, 0).isFinite());
    153         Assert.assertFalse(Vector2D.of(0, Double.POSITIVE_INFINITY).isFinite());
    154         Assert.assertFalse(Vector2D.of(Double.POSITIVE_INFINITY, 0).isFinite());
    155
    156         Assert.assertFalse(Vector2D.of(0, Double.NaN).isFinite());
    157         Assert.assertFalse(Vector2D.of(Double.NEGATIVE_INFINITY, Double.NaN).isFinite());
    158         Assert.assertFalse(Vector2D.of(Double.NaN, Double.NEGATIVE_INFINITY).isFinite());
    159         Assert.assertFalse(Vector2D.of(Double.POSITIVE_INFINITY, Double.NaN).isFinite());
    160         Assert.assertFalse(Vector2D.of(Double.NaN, Double.POSITIVE_INFINITY).isFinite());
    161     }
    162
    163     @Test
    164     public void testGetZero() {
    165         // act/assert
    166         checkVector(Vector2D.of(1.0, 1.0).getZero(), 0, 0);
    167     }
    168
    169     @Test
    170     public void testNorm() {
    171         // act/assert
    172         Assert.assertEquals(0.0, Vector2D.of(0, 0).norm(), EPS);
    173
    174         Assert.assertEquals(5.0, Vector2D.of(3, 4).norm(), EPS);
    175         Assert.assertEquals(5.0, Vector2D.of(3, -4).norm(), EPS);
    176         Assert.assertEquals(5.0, Vector2D.of(-3, 4).norm(), EPS);
    177         Assert.assertEquals(5.0, Vector2D.of(-3, -4).norm(), EPS);
    178
    179         Assert.assertEquals(Math.sqrt(5.0), Vector2D.of(-1, -2).norm(), EPS);
    180     }
    181
    182     @Test
    183     public void testNorm_unitVectors() {
    184         // arrange
    185         final Vector2D v = Vector2D.of(2.0, 3.0).normalize();
    186
    187         // act/assert
    188         Assert.assertEquals(1.0, v.norm(), 0.0);
    189     }
    190
    191     @Test
    192     public void testNormSq() {
    193         // act/assert
    194         Assert.assertEquals(0.0, Vector2D.of(0, 0).normSq(), EPS);
    195
    196         Assert.assertEquals(25.0, Vector2D.of(3, 4).normSq(), EPS);
    197         Assert.assertEquals(25.0, Vector2D.of(3, -4).normSq(), EPS);
    198         Assert.assertEquals(25.0, Vector2D.of(-3, 4).normSq(), EPS);
    199         Assert.assertEquals(25.0, Vector2D.of(-3, -4).normSq(), EPS);
    200
    201         Assert.assertEquals(5.0, Vector2D.of(-1, -2).normSq(), EPS);
    202     }
    203
    204     @Test
    205     public void testNormSq_unitVectors() {
    206         // arrange
    207         final Vector2D v = Vector2D.of(2.0, 3.0).normalize();
    208
    209         // act/assert
    210         Assert.assertEquals(1.0, v.normSq(), 0.0);
    211     }
    212
    213     @Test
    214     public void testWithNorm() {
    215         // act/assert
    216         checkVector(Vector2D.of(3, 4).withNorm(1.0), 0.6, 0.8);
    217         checkVector(Vector2D.of(4, 3).withNorm(1.0), 0.8, 0.6);
    218
    219         checkVector(Vector2D.of(-3, 4).withNorm(0.5), -0.3, 0.4);
    220         checkVector(Vector2D.of(3, -4).withNorm(2.0), 1.2, -1.6);
    221         checkVector(Vector2D.of(-3, -4).withNorm(3.0), -1.8, 3.0 * Math.sin(Math.atan2(-4, -3)));
    222
    223         checkVector(Vector2D.of(0.5, 0.5).withNorm(2), Math.sqrt(2), Math.sqrt(2));
    224     }
    225
    226     @Test
    227     public void testWithNorm_illegalNorm() {
    228         // act/assert
    229         GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.withNorm(2.0),
    230                 IllegalArgumentException.class);
    231         GeometryTestUtils.assertThrows(() -> Vector2D.NaN.withNorm(2.0),
    232                 IllegalArgumentException.class);
    233         GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.withNorm(2.0),
    234                 IllegalArgumentException.class);
    235         GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.withNorm(2.0),
    236                 IllegalArgumentException.class);
    237     }
    238
    239     @Test
    240     public void testWithNorm_unitVectors() {
    241         // arrange
    242         final double eps = 1e-14;
    243         final Vector2D v = Vector2D.of(2.0, -3.0).normalize();
    244
    245         // act/assert
    246         checkVector(Vector2D.Unit.PLUS_X.withNorm(2.5), 2.5, 0.0);
    247         checkVector(Vector2D.Unit.MINUS_Y.withNorm(3.14), 0.0, -3.14);
    248
    249         for (int i = -10; i <= 10; i++) {
    250             Assert.assertEquals(Math.abs((double) i), v.withNorm(i).norm(), eps);
    251         }
    252     }
    253
    254     @Test
    255     public void testAdd() {
    256         // arrange
    257         final Vector2D v1 = Vector2D.of(-1, 2);
    258         final Vector2D v2 = Vector2D.of(3, -4);
    259         final Vector2D v3 = Vector2D.of(5, 6);
    260
    261         // act/assert
    262         checkVector(v1.add(v1), -2, 4);
    263
    264         checkVector(v1.add(v2), 2, -2);
    265         checkVector(v2.add(v1), 2, -2);
    266
    267         checkVector(v1.add(v3), 4, 8);
    268         checkVector(v3.add(v1), 4, 8);
    269     }
    270
    271     @Test
    272     public void testAdd_scaled() {
    273         // arrange
    274         final Vector2D v1 = Vector2D.of(-1, 2);
    275         final Vector2D v2 = Vector2D.of(3, -4);
    276         final Vector2D v3 = Vector2D.of(5, 6);
    277
    278         // act/assert
    279         checkVector(v1.add(2, v1), -3, 6);
    280
    281         checkVector(v1.add(0, v2), -1, 2);
    282         checkVector(v2.add(1, v1), 2, -2);
    283
    284         checkVector(v1.add(-1, v3), -6, -4);
    285         checkVector(v3.add(-2, v1), 7, 2);
    286     }
    287
    288     @Test
    289     public void testSubtract() {
    290         // arrange
    291         final Vector2D v1 = Vector2D.of(-1, 2);
    292         final Vector2D v2 = Vector2D.of(3, -4);
    293         final Vector2D v3 = Vector2D.of(5, 6);
    294
    295         // act/assert
    296         checkVector(v1.subtract(v1), 0, 0);
    297
    298         checkVector(v1.subtract(v2), -4, 6);
    299         checkVector(v2.subtract(v1), 4, -6);
    300
    301         checkVector(v1.subtract(v3), -6, -4);
    302         checkVector(v3.subtract(v1), 6, 4);
    303     }
    304
    305     @Test
    306     public void testSubtract_scaled() {
    307         // arrange
    308         final Vector2D v1 = Vector2D.of(-1, 2);
    309         final Vector2D v2 = Vector2D.of(3, -4);
    310         final Vector2D v3 = Vector2D.of(5, 6);
    311
    312         // act/assert
    313         checkVector(v1.subtract(2, v1), 1, -2);
    314
    315         checkVector(v1.subtract(0, v2), -1, 2);
    316         checkVector(v2.subtract(1, v1), 4, -6);
    317
    318         checkVector(v1.subtract(-1, v3), 4, 8);
    319         checkVector(v3.subtract(-2, v1), 3, 10);
    320     }
    321
    322     @Test
    323     public void testNormalize() {
    324         // act/assert
    325         checkVector(Vector2D.of(100, 0).normalize(), 1, 0);
    326         checkVector(Vector2D.of(-100, 0).normalize(), -1, 0);
    327         checkVector(Vector2D.of(0, 100).normalize(), 0, 1);
    328         checkVector(Vector2D.of(0, -100).normalize(), 0, -1);
    329         checkVector(Vector2D.of(-1, 2).normalize(), -1.0 / Math.sqrt(5), 2.0 / Math.sqrt(5));
    330     }
    331
    332     @Test
    333     public void testNormalize_illegalNorm() {
    334         // act/assert
    335         GeometryTestUtils.assertThrows(Vector2D.ZERO::normalize, IllegalArgumentException.class);
    336         GeometryTestUtils.assertThrows(Vector2D.NaN::normalize, IllegalArgumentException.class);
    337         GeometryTestUtils.assertThrows(Vector2D.POSITIVE_INFINITY::normalize, IllegalArgumentException.class);
    338         GeometryTestUtils.assertThrows(Vector2D.NEGATIVE_INFINITY::normalize, IllegalArgumentException.class);
    339     }
    340
    341     @Test
    342     public void testNormalize_isIdempotent() {
    343         // arrange
    344         final double invSqrt2 = 1.0 / Math.sqrt(2);
    345         final Vector2D v = Vector2D.of(2, 2).normalize();
    346
    347         // act/assert
    348         Assert.assertSame(v, v.normalize());
    349         checkVector(v.normalize(), invSqrt2, invSqrt2);
    350     }
    351
    352     @Test
    353     public void testNegate() {
    354         // act/assert
    355         checkVector(Vector2D.of(1, 2).negate(), -1, -2);
    356         checkVector(Vector2D.of(-3, -4).negate(), 3, 4);
    357         checkVector(Vector2D.of(5, -6).negate().negate(), 5, -6);
    358     }
    359
    360     @Test
    361     public void testNegate_unitVectors() {
    362         // arrange
    363         final Vector2D v1 = Vector2D.of(1.0, 1.0).normalize();
    364         final Vector2D v2 = Vector2D.of(-1.0, -2.0).normalize();
    365         final Vector2D v3 = Vector2D.of(2.0, -3.0).normalize();
    366
    367         // act/assert
    368         checkVector(v1.negate(), -1.0 / Math.sqrt(2.0), -1.0 / Math.sqrt(2.0));
    369         checkVector(v2.negate(), 1.0 / Math.sqrt(5.0), 2.0 / Math.sqrt(5.0));
    370         checkVector(v3.negate(), -2.0 / Math.sqrt(13.0), 3.0 / Math.sqrt(13.0));
    371     }
    372
    373     @Test
    374     public void testScalarMultiply() {
    375         // act/assert
    376         checkVector(Vector2D.of(1, 2).multiply(0), 0, 0);
    377
    378         checkVector(Vector2D.of(1, 2).multiply(3), 3, 6);
    379         checkVector(Vector2D.of(1, 2).multiply(-3), -3, -6);
    380
    381         checkVector(Vector2D.of(2, 3).multiply(1.5), 3, 4.5);
    382         checkVector(Vector2D.of(2, 3).multiply(-1.5), -3, -4.5);
    383     }
    384
    385     @Test
    386     public void testDistance() {
    387         // arrange
    388         final Vector2D v1 = Vector2D.of(1, 1);
    389         final Vector2D v2 = Vector2D.of(4, 5);
    390         final Vector2D v3 = Vector2D.of(-1, 0);
    391
    392         // act/assert
    393         Assert.assertEquals(0, v1.distance(v1), EPS);
    394
    395         Assert.assertEquals(5, v1.distance(v2), EPS);
    396         Assert.assertEquals(5, v2.distance(v1), EPS);
    397
    398         Assert.assertEquals(Math.sqrt(5), v1.distance(v3), EPS);
    399         Assert.assertEquals(Math.sqrt(5), v3.distance(v1), EPS);
    400     }
    401
    402     @Test
    403     public void testDistanceSq() {
    404         // arrange
    405         final Vector2D v1 = Vector2D.of(1, 1);
    406         final Vector2D v2 = Vector2D.of(4, 5);
    407         final Vector2D v3 = Vector2D.of(-1, 0);
    408
    409         // act/assert
    410         Assert.assertEquals(0, v1.distanceSq(v1), EPS);
    411
    412         Assert.assertEquals(25, v1.distanceSq(v2), EPS);
    413         Assert.assertEquals(25, v2.distanceSq(v1), EPS);
    414
    415         Assert.assertEquals(5, v1.distanceSq(v3), EPS);
    416         Assert.assertEquals(5, v3.distanceSq(v1), EPS);
    417     }
    418
    419     @Test
    420     public void testDotProduct() {
    421         // arrange
    422         final Vector2D v1 = Vector2D.of(1, 1);
    423         final Vector2D v2 = Vector2D.of(4, 5);
    424         final Vector2D v3 = Vector2D.of(-1, 0);
    425
    426         // act/assert
    427         Assert.assertEquals(2, v1.dot(v1), EPS);
    428         Assert.assertEquals(41, v2.dot(v2), EPS);
    429         Assert.assertEquals(1, v3.dot(v3), EPS);
    430
    431         Assert.assertEquals(9, v1.dot(v2), EPS);
    432         Assert.assertEquals(9, v2.dot(v1), EPS);
    433
    434         Assert.assertEquals(-1, v1.dot(v3), EPS);
    435         Assert.assertEquals(-1, v3.dot(v1), EPS);
    436
    437         Assert.assertEquals(1, Vector2D.Unit.PLUS_X.dot(Vector2D.Unit.PLUS_X), EPS);
    438         Assert.assertEquals(0, Vector2D.Unit.PLUS_X.dot(Vector2D.Unit.PLUS_Y), EPS);
    439         Assert.assertEquals(-1, Vector2D.Unit.PLUS_X.dot(Vector2D.Unit.MINUS_X), EPS);
    440         Assert.assertEquals(0, Vector2D.Unit.PLUS_X.dot(Vector2D.Unit.MINUS_Y), EPS);
    441     }
    442
    443     @Test
    444     public void testOrthogonal() {
    445         // arrange
    446         final double invSqrt2 = 1.0 / Math.sqrt(2.0);
    447
    448         // act/assert
    449         checkVector(Vector2D.of(3, 0).orthogonal(), 0.0, 1.0);
    450         checkVector(Vector2D.of(1.0, 1.0).orthogonal(), -invSqrt2, invSqrt2);
    451
    452         checkVector(Vector2D.of(0, 2).orthogonal(), -1.0, 0.0);
    453         checkVector(Vector2D.of(-1.0, 1.0).orthogonal(), -invSqrt2, -invSqrt2);
    454
    455         checkVector(Vector2D.Unit.MINUS_X.orthogonal(), 0.0, -1.0);
    456         checkVector(Vector2D.of(-1.0, -1.0).orthogonal(), invSqrt2, -invSqrt2);
    457
    458         checkVector(Vector2D.Unit.MINUS_Y.orthogonal(), 1.0, 0.0);
    459         checkVector(Vector2D.of(1.0, -1.0).orthogonal(), invSqrt2, invSqrt2);
    460     }
    461
    462     @Test
    463     public void testOrthogonal_fullCircle() {
    464         for (double az = 0.0; az <= PlaneAngleRadians.TWO_PI; az += 0.25) {
    465             // arrange
    466             final Vector2D v = PolarCoordinates.toCartesian(Math.PI, az);
    467
    468             //act
    469             final Vector2D ortho = v.orthogonal();
    470
    471             // assert
    472             Assert.assertEquals(1.0, ortho.norm(), EPS);
    473             Assert.assertEquals(0.0, v.dot(ortho), EPS);
    474         }
    475     }
    476
    477     @Test
    478     public void testOrthogonal_illegalNorm() {
    479         // act/assert
    480         GeometryTestUtils.assertThrows(Vector2D.ZERO::orthogonal, IllegalArgumentException.class);
    481         GeometryTestUtils.assertThrows(Vector2D.NaN::orthogonal, IllegalArgumentException.class);
    482         GeometryTestUtils.assertThrows(Vector2D.POSITIVE_INFINITY::orthogonal, IllegalArgumentException.class);
    483         GeometryTestUtils.assertThrows(Vector2D.NEGATIVE_INFINITY::orthogonal, IllegalArgumentException.class);
    484     }
    485
    486     @Test
    487     public void testOrthogonal_givenDirection() {
    488         // arrange
    489         final double invSqrt2 = 1.0 / Math.sqrt(2.0);
    490
    491         // act/assert
    492         checkVector(Vector2D.Unit.PLUS_X.orthogonal(Vector2D.of(-1.0, 0.1)), 0.0, 1.0);
    493         checkVector(Vector2D.Unit.PLUS_Y.orthogonal(Vector2D.of(2.0, 2.0)), 1.0, 0.0);
    494
    495         checkVector(Vector2D.of(2.9, 2.9).orthogonal(Vector2D.of(1.0, 0.22)), invSqrt2, -invSqrt2);
    496         checkVector(Vector2D.of(2.9, 2.9).orthogonal(Vector2D.of(0.22, 1.0)), -invSqrt2, invSqrt2);
    497     }
    498
    499     @Test
    500     public void testOrthogonal_givenDirection_illegalNorm() {
    501         // act/assert
    502         GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.orthogonal(Vector2D.Unit.PLUS_X),
    503                 IllegalArgumentException.class);
    504         GeometryTestUtils.assertThrows(() -> Vector2D.NaN.orthogonal(Vector2D.Unit.PLUS_X),
    505                 IllegalArgumentException.class);
    506         GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.orthogonal(Vector2D.Unit.PLUS_X),
    507                 IllegalArgumentException.class);
    508         GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.orthogonal(Vector2D.Unit.PLUS_X),
    509                 IllegalArgumentException.class);
    510
    511         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.ZERO),
    512                 IllegalArgumentException.class);
    513         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.NaN),
    514                 IllegalArgumentException.class);
    515         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.POSITIVE_INFINITY),
    516                 IllegalArgumentException.class);
    517         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.NEGATIVE_INFINITY),
    518                 IllegalArgumentException.class);
    519     }
    520
    521     @Test
    522     public void testOrthogonal_givenDirection_directionIsCollinear() {
    523         // act/assert
    524         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.Unit.PLUS_X),
    525                 IllegalArgumentException.class);
    526         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.PLUS_X.orthogonal(Vector2D.Unit.MINUS_X),
    527                 IllegalArgumentException.class);
    528         GeometryTestUtils.assertThrows(() -> Vector2D.of(1.0, 1.0).orthogonal(Vector2D.of(2.0, 2.0)),
    529                 IllegalArgumentException.class);
    530         GeometryTestUtils.assertThrows(() -> Vector2D.of(-1.01, -1.01).orthogonal(Vector2D.of(20.1, 20.1)),
    531                 IllegalArgumentException.class);
    532     }
    533
    534     @Test
    535     public void testAngle() {
    536         // act/assert
    537         Assert.assertEquals(0, Vector2D.Unit.PLUS_X.angle(Vector2D.Unit.PLUS_X), EPS);
    538
    539         Assert.assertEquals(PlaneAngleRadians.PI, Vector2D.Unit.PLUS_X.angle(Vector2D.Unit.MINUS_X), EPS);
    540         Assert.assertEquals(PlaneAngleRadians.PI_OVER_TWO, Vector2D.Unit.PLUS_X.angle(Vector2D.Unit.PLUS_Y), EPS);
    541         Assert.assertEquals(PlaneAngleRadians.PI_OVER_TWO, Vector2D.Unit.PLUS_X.angle(Vector2D.Unit.MINUS_Y), EPS);
    542
    543         Assert.assertEquals(PlaneAngleRadians.PI / 4, Vector2D.of(1, 1).angle(Vector2D.of(1, 0)), EPS);
    544         Assert.assertEquals(PlaneAngleRadians.PI / 4, Vector2D.of(1, 0).angle(Vector2D.of(1, 1)), EPS);
    545
    546         Assert.assertEquals(0.004999958333958323, Vector2D.of(20.0, 0.0).angle(Vector2D.of(20.0, 0.1)), EPS);
    547     }
    548
    549
    550     @Test
    551     public void testAngle_illegalNorm() {
    552         // arrange
    553         final Vector2D v = Vector2D.of(1.0, 1.0);
    554
    555         // act/assert
    556         GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.angle(v),
    557                 IllegalArgumentException.class);
    558         GeometryTestUtils.assertThrows(() -> Vector2D.NaN.angle(v),
    559                 IllegalArgumentException.class);
    560         GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.angle(v),
    561                 IllegalArgumentException.class);
    562         GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.angle(v),
    563                 IllegalArgumentException.class);
    564
    565         GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.ZERO),
    566                 IllegalArgumentException.class);
    567         GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.NaN),
    568                 IllegalArgumentException.class);
    569         GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.POSITIVE_INFINITY),
    570                 IllegalArgumentException.class);
    571         GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.NEGATIVE_INFINITY),
    572                 IllegalArgumentException.class);
    573     }
    574
    575     @Test
    576     public void testSignedArea() {
    577         // arrange
    578         final double eps = 1e-10;
    579
    580         final Vector2D a = Vector2D.Unit.PLUS_X;
    581         final Vector2D b = Vector2D.Unit.PLUS_Y;
    582         final Vector2D c = Vector2D.of(1, 1).withNorm(2.0);
    583         final Vector2D d = Vector2D.of(-1, 1).withNorm(3.0);
    584
    585         // act/assert
    586         Assert.assertEquals(1.0, a.signedArea(b), eps);
    587         Assert.assertEquals(-1.0, b.signedArea(a), eps);
    588
    589         final double xAxisAndCArea = 2 * Math.cos(0.25 * PlaneAngleRadians.PI);
    590         Assert.assertEquals(xAxisAndCArea, a.signedArea(c), eps);
    591         Assert.assertEquals(-xAxisAndCArea, c.signedArea(a), eps);
    592
    593         final double xAxisAndDArea = 3 * Math.cos(0.25 * PlaneAngleRadians.PI);
    594         Assert.assertEquals(xAxisAndDArea, a.signedArea(d), eps);
    595         Assert.assertEquals(-xAxisAndDArea, d.signedArea(a), eps);
    596
    597         Assert.assertEquals(6.0, c.signedArea(d), eps);
    598         Assert.assertEquals(-6.0, d.signedArea(c), eps);
    599     }
    600
    601     @Test
    602     public void testSignedArea_collinear() {
    603         // arrange
    604         final Vector2D a = Vector2D.Unit.PLUS_X;
    605         final Vector2D b = Vector2D.Unit.PLUS_Y;
    606         final Vector2D c = Vector2D.of(-3, 8);
    607
    608         // act/assert
    609         Assert.assertEquals(0.0, a.signedArea(a), EPS);
    610         Assert.assertEquals(0.0, b.signedArea(b), EPS);
    611         Assert.assertEquals(0.0, c.signedArea(c), EPS);
    612
    613         Assert.assertEquals(0.0, a.signedArea(a.multiply(100.0)), EPS);
    614         Assert.assertEquals(0.0, b.signedArea(b.negate()), EPS);
    615         Assert.assertEquals(0.0, c.signedArea(c.multiply(-0.03)), EPS);
    616     }
    617
    618     @Test
    619     public void testProject() {
    620         // arrange
    621         final Vector2D v1 = Vector2D.of(3.0, 4.0);
    622         final Vector2D v2 = Vector2D.of(1.0, 4.0);
    623
    624         // act/assert
    625         checkVector(Vector2D.ZERO.project(v1), 0.0, 0.0);
    626
    627         checkVector(v1.project(v1), 3.0, 4.0);
    628         checkVector(v1.project(v1.negate()), 3.0, 4.0);
    629
    630         checkVector(v1.project(Vector2D.Unit.PLUS_X), 3.0, 0.0);
    631         checkVector(v1.project(Vector2D.Unit.MINUS_X), 3.0, 0.0);
    632
    633         checkVector(v1.project(Vector2D.Unit.PLUS_Y), 0.0, 4.0);
    634         checkVector(v1.project(Vector2D.Unit.MINUS_Y), 0.0, 4.0);
    635
    636         checkVector(v2.project(v1), (19.0 / 25.0) * 3.0, (19.0 / 25.0) * 4.0);
    637     }
    638
    639     @Test
    640     public void testProject_baseHasIllegalNorm() {
    641         // arrange
    642         final Vector2D v = Vector2D.of(1.0, 1.0);
    643
    644         // act/assert
    645         GeometryTestUtils.assertThrows(() -> v.project(Vector2D.ZERO),
    646                 IllegalArgumentException.class);
    647         GeometryTestUtils.assertThrows(() -> v.project(Vector2D.NaN),
    648                 IllegalArgumentException.class);
    649         GeometryTestUtils.assertThrows(() -> v.project(Vector2D.POSITIVE_INFINITY),
    650                 IllegalArgumentException.class);
    651         GeometryTestUtils.assertThrows(() -> v.project(Vector2D.NEGATIVE_INFINITY),
    652                 IllegalArgumentException.class);
    653     }
    654
    655     @Test
    656     public void testReject() {
    657         // arrange
    658         final Vector2D v1 = Vector2D.of(3.0, 4.0);
    659         final Vector2D v2 = Vector2D.of(1.0, 4.0);
    660
    661         // act/assert
    662         checkVector(Vector2D.ZERO.reject(v1), 0.0, 0.0);
    663
    664         checkVector(v1.reject(v1), 0.0, 0.0);
    665         checkVector(v1.reject(v1.negate()), 0.0, 0.0);
    666
    667         checkVector(v1.reject(Vector2D.Unit.PLUS_X), 0.0, 4.0);
    668         checkVector(v1.reject(Vector2D.Unit.MINUS_X), 0.0, 4.0);
    669
    670         checkVector(v1.reject(Vector2D.Unit.PLUS_Y), 3.0, 0.0);
    671         checkVector(v1.reject(Vector2D.Unit.MINUS_Y), 3.0, 0.0);
    672
    673         checkVector(v2.reject(v1), -32.0 / 25.0, (6.0 / 25.0) * 4.0);
    674     }
    675
    676     @Test
    677     public void testReject_baseHasIllegalNorm() {
    678         // arrange
    679         final Vector2D v = Vector2D.of(1.0, 1.0);
    680
    681         // act/assert
    682         GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.ZERO),
    683                 IllegalArgumentException.class);
    684         GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.NaN),
    685                 IllegalArgumentException.class);
    686         GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.POSITIVE_INFINITY),
    687                 IllegalArgumentException.class);
    688         GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.NEGATIVE_INFINITY),
    689                 IllegalArgumentException.class);
    690     }
    691
    692     @Test
    693     public void testProjectAndReject_areComplementary() {
    694         // arrange
    695         final double eps = 1e-12;
    696
    697         // act/assert
    698         checkProjectAndRejectFullCircle(Vector2D.of(1.0, 0.0), 1.0, eps);
    699         checkProjectAndRejectFullCircle(Vector2D.of(0.0, 1.0), 2.0, eps);
    700         checkProjectAndRejectFullCircle(Vector2D.of(1.0, 1.0), 3.0, eps);
    701
    702         checkProjectAndRejectFullCircle(Vector2D.of(-2.0, 0.0), 4.0, eps);
    703         checkProjectAndRejectFullCircle(Vector2D.of(0.0, -2.0), 5.0, eps);
    704         checkProjectAndRejectFullCircle(Vector2D.of(-2.0, -2.0), 6.0, eps);
    705     }
    706
    707     private void checkProjectAndRejectFullCircle(final Vector2D vec, final double baseMag, final double eps) {
    708         for (double theta = 0.0; theta <= PlaneAngleRadians.TWO_PI; theta += 0.5) {
    709             final Vector2D base = PolarCoordinates.toCartesian(baseMag, theta);
    710
    711             final Vector2D proj = vec.project(base);
    712             final Vector2D rej = vec.reject(base);
    713
    714             // ensure that the projection and rejection sum to the original vector
    715             EuclideanTestUtils.assertCoordinatesEqual(vec, proj.add(rej), eps);
    716
    717             final double angle = base.angle(vec);
    718
    719             // check the angle between the projection and the base; this will
    720             // be undefined when the angle between the original vector and the
    721             // base is pi/2 (which means that the projection is the zero vector)
    722             if (angle < PlaneAngleRadians.PI_OVER_TWO) {
    723                 Assert.assertEquals(0.0, proj.angle(base), eps);
    724             } else if (angle > PlaneAngleRadians.PI_OVER_TWO) {
    725                 Assert.assertEquals(PlaneAngleRadians.PI, proj.angle(base), eps);
    726             }
    727
    728             // check the angle between the rejection and the base; this should
    729             // always be pi/2 except for when the angle between the original vector
    730             // and the base is 0 or pi, in which case the rejection is the zero vector.
    731             if (angle > 0.0 && angle < PlaneAngleRadians.PI) {
    732                 Assert.assertEquals(PlaneAngleRadians.PI_OVER_TWO, rej.angle(base), eps);
    733             }
    734         }
    735     }
    736
    737     @Test
    738     public void testVectorTo() {
    739         // arrange
    740         final Vector2D p1 = Vector2D.of(1, 1);
    741         final Vector2D p2 = Vector2D.of(4, 5);
    742         final Vector2D p3 = Vector2D.of(-1, 0);
    743
    744         // act/assert
    745         checkVector(p1.vectorTo(p1), 0, 0);
    746         checkVector(p1.vectorTo(p2), 3, 4);
    747         checkVector(p2.vectorTo(p1), -3, -4);
    748
    749         checkVector(p1.vectorTo(p3), -2, -1);
    750         checkVector(p3.vectorTo(p1), 2, 1);
    751     }
    752
    753     @Test
    754     public void testDirectionTo() {
    755         // act/assert
    756         final double invSqrt2 = 1.0 / Math.sqrt(2);
    757
    758         final Vector2D p1 = Vector2D.of(1, 1);
    759         final Vector2D p2 = Vector2D.of(1, 5);
    760         final Vector2D p3 = Vector2D.of(-2, -2);
    761
    762         // act/assert
    763         checkVector(p1.directionTo(p2), 0, 1);
    764         checkVector(p2.directionTo(p1), 0, -1);
    765
    766         checkVector(p1.directionTo(p3), -invSqrt2, -invSqrt2);
    767         checkVector(p3.directionTo(p1), invSqrt2, invSqrt2);
    768     }
    769
    770     @Test
    771     public void testDirectionTo_illegalNorm() {
    772         // arrange
    773         final Vector2D p = Vector2D.of(1, 2);
    774
    775         // act/assert
    776         GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.directionTo(Vector2D.ZERO),
    777                 IllegalArgumentException.class);
    778         GeometryTestUtils.assertThrows(() -> p.directionTo(p),
    779                 IllegalArgumentException.class);
    780         GeometryTestUtils.assertThrows(() -> p.directionTo(Vector2D.NaN),
    781                 IllegalArgumentException.class);
    782         GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.directionTo(p),
    783                 IllegalArgumentException.class);
    784         GeometryTestUtils.assertThrows(() -> p.directionTo(Vector2D.POSITIVE_INFINITY),
    785                 IllegalArgumentException.class);
    786     }
    787
    788     @Test
    789     public void testLerp() {
    790         // arrange
    791         final Vector2D v1 = Vector2D.of(1, -5);
    792         final Vector2D v2 = Vector2D.of(-4, 0);
    793         final Vector2D v3 = Vector2D.of(10, -4);
    794
    795         // act/assert
    796         checkVector(v1.lerp(v1, 0), 1, -5);
    797         checkVector(v1.lerp(v1, 1), 1, -5);
    798
    799         checkVector(v1.lerp(v2, -0.25), 2.25, -6.25);
    800         checkVector(v1.lerp(v2, 0), 1, -5);
    801         checkVector(v1.lerp(v2, 0.25), -0.25, -3.75);
    802         checkVector(v1.lerp(v2, 0.5), -1.5, -2.5);
    803         checkVector(v1.lerp(v2, 0.75), -2.75, -1.25);
    804         checkVector(v1.lerp(v2, 1), -4, 0);
    805         checkVector(v1.lerp(v2, 1.25), -5.25, 1.25);
    806
    807         checkVector(v1.lerp(v3, 0), 1, -5);
    808         checkVector(v1.lerp(v3, 0.25), 3.25, -4.75);
    809         checkVector(v1.lerp(v3, 0.5), 5.5, -4.5);
    810         checkVector(v1.lerp(v3, 0.75), 7.75, -4.25);
    811         checkVector(v1.lerp(v3, 1), 10, -4);
    812     }
    813
    814     @Test
    815     public void testTransform() {
    816         // arrange
    817         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    818                 .scale(2)
    819                 .translate(1, 2);
    820
    821         final Vector2D v1 = Vector2D.of(1, 2);
    822         final Vector2D v2 = Vector2D.of(-4, -5);
    823
    824         // act/assert
    825         checkVector(v1.transform(transform), 3, 6);
    826         checkVector(v2.transform(transform), -7, -8);
    827     }
    828
    829     @Test
    830     public void testPrecisionEquals() {
    831         // arrange
    832         final DoublePrecisionContext smallEps = new EpsilonDoublePrecisionContext(1e-6);
    833         final DoublePrecisionContext largeEps = new EpsilonDoublePrecisionContext(1e-1);
    834
    835         final Vector2D vec = Vector2D.of(1, -2);
    836
    837         // act/assert
    838         Assert.assertTrue(vec.eq(vec, smallEps));
    839         Assert.assertTrue(vec.eq(vec, largeEps));
    840
    841         Assert.assertTrue(vec.eq(Vector2D.of(1.0000007, -2.0000009), smallEps));
    842         Assert.assertTrue(vec.eq(Vector2D.of(1.0000007, -2.0000009), largeEps));
    843
    844         Assert.assertFalse(vec.eq(Vector2D.of(1.004, -2), smallEps));
    845         Assert.assertFalse(vec.eq(Vector2D.of(1, -2.004), smallEps));
    846         Assert.assertTrue(vec.eq(Vector2D.of(1.004, -2.004), largeEps));
    847
    848         Assert.assertFalse(vec.eq(Vector2D.of(1, -3), smallEps));
    849         Assert.assertFalse(vec.eq(Vector2D.of(2, -2), smallEps));
    850         Assert.assertFalse(vec.eq(Vector2D.of(1, -3), largeEps));
    851         Assert.assertFalse(vec.eq(Vector2D.of(2, -2), largeEps));
    852     }
    853
    854     @Test
    855     public void testIsZero() {
    856         // arrange
    857         final DoublePrecisionContext smallEps = new EpsilonDoublePrecisionContext(1e-6);
    858         final DoublePrecisionContext largeEps = new EpsilonDoublePrecisionContext(1e-1);
    859
    860         // act/assert
    861         Assert.assertTrue(Vector2D.of(0.0, -0.0).isZero(smallEps));
    862         Assert.assertTrue(Vector2D.of(-0.0, 0.0).isZero(largeEps));
    863
    864         Assert.assertTrue(Vector2D.of(-1e-7, 1e-7).isZero(smallEps));
    865         Assert.assertTrue(Vector2D.of(1e-7, 1e-7).isZero(largeEps));
    866
    867         Assert.assertFalse(Vector2D.of(1e-2, 0.0).isZero(smallEps));
    868         Assert.assertFalse(Vector2D.of(0.0, 1e-2).isZero(smallEps));
    869         Assert.assertTrue(Vector2D.of(1e-2, -1e-2).isZero(largeEps));
    870
    871         Assert.assertFalse(Vector2D.of(0.2, 0.0).isZero(smallEps));
    872         Assert.assertFalse(Vector2D.of(0.0, 0.2).isZero(smallEps));
    873         Assert.assertFalse(Vector2D.of(0.2, 0.2).isZero(smallEps));
    874         Assert.assertFalse(Vector2D.of(-0.2, 0.0).isZero(largeEps));
    875         Assert.assertFalse(Vector2D.of(0.0, -0.2).isZero(largeEps));
    876         Assert.assertFalse(Vector2D.of(-0.2, -0.2).isZero(largeEps));
    877     }
    878
    879     @Test
    880     public void testHashCode() {
    881         // arrange
    882         final Vector2D u = Vector2D.of(1, 1);
    883         final Vector2D v = Vector2D.of(1 + 10 * Precision.EPSILON, 1 + 10 * Precision.EPSILON);
    884         final Vector2D w = Vector2D.of(1, 1);
    885
    886         // act/assert
    887         Assert.assertTrue(u.hashCode() != v.hashCode());
    888         Assert.assertEquals(u.hashCode(), w.hashCode());
    889
    890         Assert.assertEquals(Vector2D.of(0, Double.NaN).hashCode(), Vector2D.NaN.hashCode());
    891         Assert.assertEquals(Vector2D.of(Double.NaN, 0).hashCode(), Vector2D.NaN.hashCode());
    892         Assert.assertEquals(Vector2D.of(0, Double.NaN).hashCode(), Vector2D.of(Double.NaN, 0).hashCode());
    893     }
    894
    895     @Test
    896     public void testEquals() {
    897         // arrange
    898         final Vector2D u1 = Vector2D.of(1, 2);
    899         final Vector2D u2 = Vector2D.of(1, 2);
    900
    901         // act/assert
    902         Assert.assertFalse(u1.equals(null));
    903         Assert.assertFalse(u1.equals(new Object()));
    904
    905         Assert.assertEquals(u1, u1);
    906         Assert.assertEquals(u1, u2);
    907
    908         Assert.assertNotEquals(u1, Vector2D.of(-1, -2));
    909         Assert.assertNotEquals(u1, Vector2D.of(1 + 10 * Precision.EPSILON, 2));
    910         Assert.assertNotEquals(u1, Vector2D.of(1, 2 + 10 * Precision.EPSILON));
    911
    912         Assert.assertEquals(Vector2D.of(0, Double.NaN), Vector2D.of(Double.NaN, 0));
    913
    914         Assert.assertEquals(Vector2D.of(0, Double.POSITIVE_INFINITY), Vector2D.of(0, Double.POSITIVE_INFINITY));
    915         Assert.assertNotEquals(Vector2D.of(Double.POSITIVE_INFINITY, 0), Vector2D.of(0, Double.POSITIVE_INFINITY));
    916
    917         Assert.assertEquals(Vector2D.of(Double.NEGATIVE_INFINITY, 0), Vector2D.of(Double.NEGATIVE_INFINITY, 0));
    918         Assert.assertNotEquals(Vector2D.of(0, Double.NEGATIVE_INFINITY), Vector2D.of(Double.NEGATIVE_INFINITY, 0));
    919     }
    920
    921     @Test
    922     public void testEqualsAndHashCode_signedZeroConsistency() {
    923         // arrange
    924         final Vector2D a = Vector2D.of(0.0, 0.0);
    925         final Vector2D b = Vector2D.of(-0.0, -0.0);
    926         final Vector2D c = Vector2D.of(0.0, 0.0);
    927         final Vector2D d = Vector2D.of(-0.0, -0.0);
    928
    929         // act/assert
    930         Assert.assertFalse(a.equals(b));
    931
    932         Assert.assertTrue(a.equals(c));
    933         Assert.assertEquals(a.hashCode(), c.hashCode());
    934
    935         Assert.assertTrue(b.equals(d));
    936         Assert.assertEquals(b.hashCode(), d.hashCode());
    937     }
    938
    939     @Test
    940     public void testToString() {
    941         // arrange
    942         final Vector2D v = Vector2D.of(1, 2);
    943         final Pattern pattern = Pattern.compile("\\(1.{0,2}, 2.{0,2}\\)");
    944
    945         // act
    946         final String str = v.toString();
    947
    948         // assert
    949         Assert.assertTrue("Expected string " + str + " to match regex " + pattern,
    950                     pattern.matcher(str).matches());
    951     }
    952
    953     @Test
    954     public void testParse() {
    955         // act/assert
    956         checkVector(Vector2D.parse("(1, 2)"), 1, 2);
    957         checkVector(Vector2D.parse("(-1, -2)"), -1, -2);
    958
    959         checkVector(Vector2D.parse("(0.01, -1e-3)"), 1e-2, -1e-3);
    960
    961         checkVector(Vector2D.parse("(NaN, -Infinity)"), Double.NaN, Double.NEGATIVE_INFINITY);
    962
    963         checkVector(Vector2D.parse(Vector2D.ZERO.toString()), 0, 0);
    964         checkVector(Vector2D.parse(Vector2D.Unit.MINUS_X.toString()), -1, 0);
    965     }
    966
    967     @Test(expected = IllegalArgumentException.class)
    968     public void testParse_failure() {
    969         // act/assert
    970         Vector2D.parse("abc");
    971     }
    972
    973     @Test
    974     public void testOf() {
    975         // act/assert
    976         checkVector(Vector2D.of(0, 1), 0, 1);
    977         checkVector(Vector2D.of(-1, -2), -1, -2);
    978         checkVector(Vector2D.of(Math.PI, Double.NaN), Math.PI, Double.NaN);
    979         checkVector(Vector2D.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
    980     }
    981
    982     @Test
    983     public void testOf_arrayArg() {
    984         // act/assert
    985         checkVector(Vector2D.of(new double[] {0, 1}), 0, 1);
    986         checkVector(Vector2D.of(new double[] {-1, -2}), -1, -2);
    987         checkVector(Vector2D.of(new double[] {Math.PI, Double.NaN}), Math.PI, Double.NaN);
    988         checkVector(Vector2D.of(new double[] {Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY}), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
    989     }
    990
    991     @Test(expected = IllegalArgumentException.class)
    992     public void testOf_arrayArg_invalidDimensions() {
    993         // act/assert
    994         Vector2D.of(new double[] {0.0});
    995     }
    996
    997     @Test
    998     public void testUnitFrom_coordinates() {
    999         // arrange
    1000         final double invSqrt2 = 1.0 / Math.sqrt(2.0);
    1001
    1002         // act/assert
    1003         checkVector(Vector2D.Unit.from(2.0, -2.0), invSqrt2, -invSqrt2);
    1004         checkVector(Vector2D.Unit.from(-4.0, 4.0), -invSqrt2, invSqrt2);
    1005     }
    1006
    1007     @Test
    1008     public void testUnitFrom_vector() {
    1009         // arrange
    1010         final double invSqrt2 = 1.0 / Math.sqrt(2.0);
    1011         final Vector2D vec = Vector2D.of(2.0, -2.0);
    1012         final Vector2D.Unit unitVec = Vector2D.Unit.from(2.0, -2.0);
    1013
    1014         // act/assert
    1015         checkVector(Vector2D.Unit.from(vec), invSqrt2, -invSqrt2);
    1016         Assert.assertSame(unitVec, Vector2D.Unit.from(unitVec));
    1017     }
    1018
    1019     @Test
    1020     public void testUnitFrom_illegalNorm() {
    1021         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.from(0.0, 0.0),
    1022                 IllegalArgumentException.class);
    1023         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.from(Double.NaN, 1.0),
    1024                 IllegalArgumentException.class);
    1025         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.from(1.0, Double.NEGATIVE_INFINITY),
    1026                 IllegalArgumentException.class);
    1027         GeometryTestUtils.assertThrows(() -> Vector2D.Unit.from(1.0, Double.POSITIVE_INFINITY),
    1028                 IllegalArgumentException.class);
    1029     }
    1030
    1031     @Test
    1032     public void testMax() {
    1033         // act/assert
    1034         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-100, 1),
    1035                 Vector2D.max(Collections.singletonList(Vector2D.of(-100, 1))), EPS);
    1036
    1037         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 1),
    1038                 Vector2D.max(Arrays.asList(Vector2D.of(-100, 1), Vector2D.of(0, 1))), EPS);
    1039
    1040         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 0),
    1041                 Vector2D.max(Vector2D.of(-2, 0), Vector2D.of(-1, -5), Vector2D.of(-10, -10)), EPS);
    1042     }
    1043
    1044     @Test
    1045     public void testMax_noPointsGiven() {
    1046         // arrange
    1047         final String msg = "Cannot compute vector max: no vectors given";
    1048
    1049         // act/assert
    1050         GeometryTestUtils.assertThrows(() -> {
    1051             Vector2D.max(new ArrayList<>());
    1052         }, IllegalArgumentException.class, msg);
    1053     }
    1054
    1055     @Test
    1056     public void testMin() {
    1057         // act/assert
    1058         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-100, 1),
    1059                 Vector2D.min(Collections.singletonList(Vector2D.of(-100, 1))), EPS);
    1060
    1061         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-100, 1),
    1062                 Vector2D.min(Arrays.asList(Vector2D.of(-100, 1), Vector2D.of(0, 1))), EPS);
    1063
    1064         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-10, -10),
    1065                 Vector2D.min(Vector2D.of(-2, 0), Vector2D.of(-1, -5), Vector2D.of(-10, -10)), EPS);
    1066     }
    1067
    1068     @Test
    1069     public void testMin_noPointsGiven() {
    1070         // arrange
    1071         final String msg = "Cannot compute vector min: no vectors given";
    1072
    1073         // act/assert
    1074         GeometryTestUtils.assertThrows(() -> {
    1075             Vector2D.min(new ArrayList<>());
    1076         }, IllegalArgumentException.class, msg);
    1077     }
    1078
    1079     @Test
    1080     public void testCentroid() {
    1081         // act/assert
    1082         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2),
    1083                 Vector2D.centroid(Vector2D.of(1, 2)), EPS);
    1084
    1085         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2.5, 3.5),
    1086                 Vector2D.centroid(Vector2D.of(1, 2), Vector2D.of(2, 3),
    1087                         Vector2D.of(3, 4), Vector2D.of(4, 5)), EPS);
    1088
    1089         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2),
    1090                 Vector2D.centroid(Collections.singletonList(Vector2D.of(1, 2))), EPS);
    1091
    1092         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 1),
    1093                 Vector2D.centroid(Arrays.asList(Vector2D.of(1, 2), Vector2D.of(1, 2),
    1094                         Vector2D.ZERO, Vector2D.ZERO)), EPS);
    1095     }
    1096
    1097     @Test
    1098     public void testCentroid_noPointsGiven() {
    1099         // arrange
    1100         final String msg = "Cannot compute centroid: no points given";
    1101
    1102         // act/assert
    1103         GeometryTestUtils.assertThrows(() -> {
    1104             Vector2D.centroid(new ArrayList<>());
    1105         }, IllegalArgumentException.class, msg);
    1106     }
    1107
    1108     @Test
    1109     public void testLinearCombination1() {
    1110         // arrange
    1111         final Vector2D p1 = Vector2D.of(1, 2);
    1112
    1113         // act/assert
    1114         checkVector(Vector2D.linearCombination(0, p1), 0, 0);
    1115
    1116         checkVector(Vector2D.linearCombination(1, p1), 1, 2);
    1117         checkVector(Vector2D.linearCombination(-1, p1), -1, -2);
    1118
    1119         checkVector(Vector2D.linearCombination(0.5, p1), 0.5, 1);
    1120         checkVector(Vector2D.linearCombination(-0.5, p1), -0.5, -1);
    1121     }
    1122
    1123     @Test
    1124     public void testLinearCombination2() {
    1125         // arrange
    1126         final Vector2D p1 = Vector2D.of(1, 2);
    1127         final Vector2D p2 = Vector2D.of(-3, -4);
    1128
    1129         // act/assert
    1130         checkVector(Vector2D.linearCombination(2, p1, -3, p2), 11, 16);
    1131         checkVector(Vector2D.linearCombination(-3, p1, 2, p2), -9, -14);
    1132     }
    1133
    1134     @Test
    1135     public void testLinearCombination3() {
    1136         // arrange
    1137         final Vector2D p1 = Vector2D.of(1, 2);
    1138         final Vector2D p2 = Vector2D.of(-3, -4);
    1139         final Vector2D p3 = Vector2D.of(5, 6);
    1140
    1141         // act/assert
    1142         checkVector(Vector2D.linearCombination(2, p1, -3, p2, 4, p3), 31, 40);
    1143         checkVector(Vector2D.linearCombination(-3, p1, 2, p2, -4, p3), -29, -38);
    1144     }
    1145
    1146     @Test
    1147     public void testLinearCombination4() {
    1148         // arrange
    1149         final Vector2D p1 = Vector2D.of(1, 2);
    1150         final Vector2D p2 = Vector2D.of(-3, -4);
    1151         final Vector2D p3 = Vector2D.of(5, 6);
    1152         final Vector2D p4 = Vector2D.of(-7, -8);
    1153
    1154         // act/assert
    1155         checkVector(Vector2D.linearCombination(2, p1, -3, p2, 4, p3, -5, p4), 66, 80);
    1156         checkVector(Vector2D.linearCombination(-3, p1, 2, p2, -4, p3, 5, p4), -64, -78);
    1157     }
    1158
    1159     @Test
    1160     public void testUnitFactoryOptimization() {
    1161         // An already normalized vector will avoid unnecessary creation.
    1162         final Vector2D v = Vector2D.of(4, 5).normalize();
    1163         Assert.assertSame(v, v.normalize());
    1164     }
    1165
    1166     private void checkVector(final Vector2D v, final double x, final double y) {
    1167         checkVector(v, x, y, EPS);
    1168     }
    1169
    1170     private void checkVector(final Vector2D v, final double x, final double y, final double eps) {
    1171         Assert.assertEquals(x, v.getX(), eps);
    1172         Assert.assertEquals(y, v.getY(), eps);
    1173     }
    1174 }