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