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.function.UnaryOperator;
    20
    21  import org.apache.commons.geometry.core.GeometryTestUtils;
    22  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
    23  import org.apache.commons.geometry.euclidean.twod.rotation.Rotation2D;
    24  import org.apache.commons.numbers.angle.Angle;
    25  import org.apache.commons.numbers.core.Precision;
    26  import org.junit.jupiter.api.Assertions;
    27  import org.junit.jupiter.api.Test;
    28
    29  class AffineTransformMatrix2DTest {
    30
    31      private static final double EPS = 1e-12;
    32
    33      private static final Precision.DoubleEquivalence TEST_PRECISION =
    34              Precision.doubleEquivalenceOfEpsilon(EPS);
    35
    36      private static final double THREE_PI_OVER_TWO = 3 * Math.PI / 2;
    37
    38      @Test
    39      void testOf() {
    40          // arrange
    41          final double[] arr = {
    42              1, 2, 3,
    43              4, 5, 6
    44          };
    45
    46          // act
    47          final AffineTransformMatrix2D transform = AffineTransformMatrix2D.of(arr);
    48
    49          // assert
    50          final double[] result = transform.toArray();
    51          Assertions.assertNotSame(arr, result);
    52          Assertions.assertArrayEquals(arr, result, 0.0);
    53      }
    54
    55      @Test
    56      void testOf_invalidDimensions() {
    57          // act/assert
    58          GeometryTestUtils.assertThrowsWithMessage(() -> AffineTransformMatrix2D.of(1, 2),
    59                  IllegalArgumentException.class, "Dimension mismatch: 2 != 6");
    60      }
    61
    62      @Test
    63      void testFromColumnVectors_twoVector() {
    64          // arrange
    65          final Vector2D u = Vector2D.of(1, 2);
    66          final Vector2D v = Vector2D.of(3, 4);
    67
    68          // act
    69          final AffineTransformMatrix2D transform = AffineTransformMatrix2D.fromColumnVectors(u, v);
    70
    71          // assert
    72          Assertions.assertArrayEquals(new double[] {
    73              1, 3, 0,
    74              2, 4, 0
    75          }, transform.toArray(), 0.0);
    76      }
    77
    78      @Test
    79      void testFromColumnVectors_threeVectors() {
    80          // arrange
    81          final Vector2D u = Vector2D.of(1, 2);
    82          final Vector2D v = Vector2D.of(3, 4);
    83          final Vector2D t = Vector2D.of(5, 6);
    84
    85          // act
    86          final AffineTransformMatrix2D transform = AffineTransformMatrix2D.fromColumnVectors(u, v, t);
    87
    88          // assert
    89          Assertions.assertArrayEquals(new double[] {
    90              1, 3, 5,
    91              2, 4, 6
    92          }, transform.toArray(), 0.0);
    93      }
    94
    95      @Test
    96      void testIdentity() {
    97          // act
    98          final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity();
    99
    100         // assert
    101         final double[] expected = {
    102             1, 0, 0,
    103             0, 1, 0
    104         };
    105         Assertions.assertArrayEquals(expected, transform.toArray(), 0.0);
    106     }
    107
    108     @Test
    109     void testFrom() {
    110         // act/assert
    111         Assertions.assertArrayEquals(new double[] {
    112             1, 0, 0,
    113             0, 1, 0
    114         }, AffineTransformMatrix2D.from(UnaryOperator.identity()).toArray(), EPS);
    115         Assertions.assertArrayEquals(new double[] {
    116             1, 0, 2,
    117             0, 1, 3
    118         }, AffineTransformMatrix2D.from(v -> v.add(Vector2D.of(2, 3))).toArray(), EPS);
    119         Assertions.assertArrayEquals(new double[] {
    120             3, 0, 0,
    121             0, 3, 0
    122         }, AffineTransformMatrix2D.from(v -> v.multiply(3)).toArray(), EPS);
    123         Assertions.assertArrayEquals(new double[] {
    124             3, 0, 6,
    125             0, 3, 9
    126         }, AffineTransformMatrix2D.from(v -> v.add(Vector2D.of(2, 3)).multiply(3)).toArray(), EPS);
    127     }
    128
    129     @Test
    130     void testFrom_invalidFunction() {
    131         // act/assert
    132         Assertions.assertThrows(IllegalArgumentException.class, () -> AffineTransformMatrix2D.from(v -> v.multiply(0)));
    133     }
    134
    135     @Test
    136     void testCreateTranslation_xy() {
    137         // act
    138         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createTranslation(2, 3);
    139
    140         // assert
    141         final double[] expected = {
    142             1, 0, 2,
    143             0, 1, 3
    144         };
    145         Assertions.assertArrayEquals(expected, transform.toArray(), 0.0);
    146     }
    147
    148     @Test
    149     void testCreateTranslation_vector() {
    150         // act
    151         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createTranslation(Vector2D.of(5, 6));
    152
    153         // assert
    154         final double[] expected = {
    155             1, 0, 5,
    156             0, 1, 6
    157         };
    158         Assertions.assertArrayEquals(expected, transform.toArray(), 0.0);
    159     }
    160
    161     @Test
    162     void testCreateScale_xy() {
    163         // act
    164         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(2, 3);
    165
    166         // assert
    167         final double[] expected = {
    168             2, 0, 0,
    169             0, 3, 0
    170         };
    171         Assertions.assertArrayEquals(expected, transform.toArray(), 0.0);
    172     }
    173
    174     @Test
    175     void testTranslate_xy() {
    176         // arrange
    177         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    178                     2, 0, 10,
    179                     0, 3, 11
    180                 );
    181
    182         // act
    183         final AffineTransformMatrix2D result = a.translate(4, 5);
    184
    185         // assert
    186         final double[] expected = {
    187             2, 0, 14,
    188             0, 3, 16
    189         };
    190         Assertions.assertArrayEquals(expected, result.toArray(), 0.0);
    191     }
    192
    193     @Test
    194     void testTranslate_vector() {
    195         // arrange
    196         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    197                     2, 0, 10,
    198                     0, 3, 11
    199                 );
    200
    201         // act
    202         final AffineTransformMatrix2D result = a.translate(Vector2D.of(7, 8));
    203
    204         // assert
    205         final double[] expected = {
    206             2, 0, 17,
    207             0, 3, 19
    208         };
    209         Assertions.assertArrayEquals(expected, result.toArray(), 0.0);
    210     }
    211
    212     @Test
    213     void testCreateScale_vector() {
    214         // act
    215         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(Vector2D.of(4, 5));
    216
    217         // assert
    218         final double[] expected = {
    219             4, 0, 0,
    220             0, 5, 0
    221         };
    222         Assertions.assertArrayEquals(expected, transform.toArray(), 0.0);
    223     }
    224
    225     @Test
    226     void testCreateScale_singleValue() {
    227         // act
    228         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(7);
    229
    230         // assert
    231         final double[] expected = {
    232             7, 0, 0,
    233             0, 7, 0
    234         };
    235         Assertions.assertArrayEquals(expected, transform.toArray(), 0.0);
    236     }
    237
    238     @Test
    239     void testScale_xy() {
    240         // arrange
    241         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    242                     2, 0, 10,
    243                     0, 3, 11
    244                 );
    245
    246         // act
    247         final AffineTransformMatrix2D result = a.scale(4, 5);
    248
    249         // assert
    250         final double[] expected = {
    251             8, 0, 40,
    252             0, 15, 55
    253         };
    254         Assertions.assertArrayEquals(expected, result.toArray(), 0.0);
    255     }
    256
    257     @Test
    258     void testScale_vector() {
    259         // arrange
    260         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    261                     2, 0, 10,
    262                     0, 3, 11
    263                 );
    264
    265         // act
    266         final AffineTransformMatrix2D result = a.scale(Vector2D.of(7, 8));
    267
    268         // assert
    269         final double[] expected = {
    270             14, 0, 70,
    271             0, 24, 88
    272         };
    273         Assertions.assertArrayEquals(expected, result.toArray(), 0.0);
    274     }
    275
    276     @Test
    277     void testScale_singleValue() {
    278         // arrange
    279         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    280                     2, 0, 10,
    281                     0, 3, 11
    282                 );
    283
    284         // act
    285         final AffineTransformMatrix2D result = a.scale(10);
    286
    287         // assert
    288         final double[] expected = {
    289             20, 0, 100,
    290             0, 30, 110
    291         };
    292         Assertions.assertArrayEquals(expected, result.toArray(), 0.0);
    293     }
    294
    295     @Test
    296     void testCreateRotation() {
    297         // act
    298         final double angle = Math.PI * 2.0 / 3.0;
    299         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(angle);
    300
    301         // assert
    302         final double sin = Math.sin(angle);
    303         final double cos = Math.cos(angle);
    304
    305         final double[] expected = {
    306             cos, -sin, 0,
    307             sin, cos, 0
    308         };
    309         Assertions.assertArrayEquals(expected, transform.toArray(), EPS);
    310     }
    311
    312     @Test
    313     void testCreateRotation_aroundCenter_rawAngle() {
    314         // act
    315         final Vector2D center = Vector2D.of(1, 2);
    316         final double angle = Math.PI * 2.0 / 3.0;
    317         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(center, angle);
    318
    319         // assert
    320         final double sin = Math.sin(angle);
    321         final double cos = Math.cos(angle);
    322
    323         final double[] expected = {
    324             cos, -sin, -cos + (2 * sin) + 1,
    325             sin, cos, -sin - (2 * cos) + 2
    326         };
    327         Assertions.assertArrayEquals(expected, transform.toArray(), EPS);
    328     }
    329
    330     @Test
    331     void testCreateRotation_aroundCenter_rotationInstance() {
    332         // act
    333         final Vector2D center = Vector2D.of(1, 2);
    334         final double angle = Math.PI * 4.0 / 3.0;
    335         final Rotation2D rotation = Rotation2D.of(angle);
    336         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(center, rotation);
    337
    338         // assert
    339         final double sin = Math.sin(angle);
    340         final double cos = Math.cos(angle);
    341
    342         final double[] expected = {
    343             cos, -sin, -cos + (2 * sin) + 1,
    344             sin, cos, -sin - (2 * cos) + 2
    345         };
    346         Assertions.assertArrayEquals(expected, transform.toArray(), EPS);
    347     }
    348
    349     @Test
    350     void testRotate_rawAngle() {
    351         // arrange
    352         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    353                     1, 2, 3,
    354                     4, 5, 6
    355                 );
    356
    357         // act
    358         final AffineTransformMatrix2D result = a.rotate(Angle.PI_OVER_TWO);
    359
    360         // assert
    361         final double[] expected = {
    362             -4, -5, -6,
    363             1, 2, 3
    364         };
    365         Assertions.assertArrayEquals(expected, result.toArray(), EPS);
    366     }
    367
    368     @Test
    369     void testRotate_rotationInstance() {
    370         // arrange
    371         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    372                     1, 2, 3,
    373                     4, 5, 6
    374                 );
    375
    376         // act
    377         final AffineTransformMatrix2D result = a.rotate(Rotation2D.of(Angle.PI_OVER_TWO));
    378
    379         // assert
    380         final double[] expected = {
    381             -4, -5, -6,
    382             1, 2, 3
    383         };
    384         Assertions.assertArrayEquals(expected, result.toArray(), EPS);
    385     }
    386
    387     @Test
    388     void testRotate_aroundCenter_rawAngle() {
    389         // arrange
    390         final Vector2D center = Vector2D.of(1, 2);
    391
    392         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    393                     1, 2, 3,
    394                     4, 5, 6
    395                 );
    396
    397         // act
    398         final AffineTransformMatrix2D result = a.rotate(center, Angle.PI_OVER_TWO);
    399
    400         // assert
    401         final double[] expected = {
    402             -4, -5, -3,
    403             1, 2, 4
    404         };
    405         Assertions.assertArrayEquals(expected, result.toArray(), EPS);
    406     }
    407
    408     @Test
    409     void testRotate_aroundCenter_rotationInstance() {
    410         // arrange
    411         final Vector2D center = Vector2D.of(1, 2);
    412
    413         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    414                     1, 2, 3,
    415                     4, 5, 6
    416                 );
    417
    418         // act
    419         final AffineTransformMatrix2D result = a.rotate(center, Rotation2D.of(Angle.PI_OVER_TWO));
    420
    421         // assert
    422         final double[] expected = {
    423             -4, -5, -3,
    424             1, 2, 4
    425         };
    426         Assertions.assertArrayEquals(expected, result.toArray(), EPS);
    427     }
    428
    429     @Test
    430     void testCreateShear() {
    431         // act
    432         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createShear(2, 3);
    433
    434         // assert
    435         final double[] expected = {
    436             1, 2, 0,
    437             3, 1, 0
    438         };
    439         Assertions.assertArrayEquals(expected, transform.toArray(), 0.0);
    440     }
    441
    442     @Test
    443     void testShear() {
    444         // arrange
    445         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    446                 1, 2, 3,
    447                 4, 5, 6
    448             );
    449
    450         // act
    451         final AffineTransformMatrix2D result = a.shear(-2, 3);
    452
    453         // assert
    454         final double[] expected = {
    455             -7, -8, -9,
    456             7, 11, 15
    457         };
    458         Assertions.assertArrayEquals(expected, result.toArray(), EPS);
    459     }
    460
    461     @Test
    462     void testShear_noShear() {
    463         // arrange
    464         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    465                 1, 2, 3,
    466                 4, 5, 6
    467             );
    468
    469         // act
    470         final AffineTransformMatrix2D result = a.shear(0, 0);
    471
    472         // assert
    473         final double[] expected = {
    474             1, 2, 3,
    475             4, 5, 6
    476         };
    477         Assertions.assertArrayEquals(expected, result.toArray(), EPS);
    478     }
    479
    480     @Test
    481     void testApply_identity() {
    482         // arrange
    483         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity();
    484
    485         // act/assert
    486         runWithCoordinates((x, y) -> {
    487             final Vector2D v = Vector2D.of(x, y);
    488
    489             EuclideanTestUtils.assertCoordinatesEqual(v, transform.apply(v), EPS);
    490         });
    491     }
    492
    493     @Test
    494     void testApply_translate() {
    495         // arrange
    496         final Vector2D translation = Vector2D.of(1.1, -Math.PI);
    497
    498         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    499                 .translate(translation);
    500
    501         // act/assert
    502         runWithCoordinates((x, y) -> {
    503             final Vector2D vec = Vector2D.of(x, y);
    504
    505             final Vector2D expectedVec = vec.add(translation);
    506
    507             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
    508         });
    509     }
    510
    511     @Test
    512     void testApply_scale() {
    513         // arrange
    514         final Vector2D factors = Vector2D.of(2.0, -3.0);
    515
    516         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    517                 .scale(factors);
    518
    519         // act/assert
    520         runWithCoordinates((x, y) -> {
    521             final Vector2D vec = Vector2D.of(x, y);
    522
    523             final Vector2D expectedVec = Vector2D.of(factors.getX() * x, factors.getY() * y);
    524
    525             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
    526         });
    527     }
    528
    529     @Test
    530     void testApply_rotate() {
    531         // arrange
    532         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    533                 .rotate(-Angle.PI_OVER_TWO);
    534
    535         // act/assert
    536         runWithCoordinates((x, y) -> {
    537             final Vector2D vec = Vector2D.of(x, y);
    538
    539             final Vector2D expectedVec = Vector2D.of(y, -x);
    540
    541             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
    542         });
    543     }
    544
    545     @Test
    546     void testApply_rotate_aroundCenter_minusHalfPi() {
    547         // arrange
    548         final Vector2D center = Vector2D.of(1, 2);
    549         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    550                 .rotate(center, -Angle.PI_OVER_TWO);
    551
    552         // act/assert
    553         runWithCoordinates((x, y) -> {
    554             final Vector2D vec = Vector2D.of(x, y);
    555
    556             final Vector2D centered = vec.subtract(center);
    557             final Vector2D expectedVec = Vector2D.of(centered.getY(), -centered.getX()).add(center);
    558
    559             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
    560         });
    561     }
    562
    563     @Test
    564     void testApply_rotate_aroundCenter_pi() {
    565         // arrange
    566         final Vector2D center = Vector2D.of(1, 2);
    567         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    568                 .rotate(center, Math.PI);
    569
    570         // act/assert
    571         runWithCoordinates((x, y) -> {
    572             final Vector2D vec = Vector2D.of(x, y);
    573
    574             final Vector2D centered = vec.subtract(center);
    575             final Vector2D expectedVec = Vector2D.of(-centered.getX(), -centered.getY()).add(center);
    576
    577             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
    578         });
    579     }
    580
    581     @Test
    582     void testApply_shearAlongX() {
    583         // arrange
    584         final double shearFactor = -2;
    585         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    586                 .shear(shearFactor, 0);
    587
    588         // act/assert
    589         runWithCoordinates((x, y) -> {
    590             final Vector2D vec = Vector2D.of(x, y);
    591
    592             final Vector2D expectedVec = Vector2D.of(x + (shearFactor * y), y);
    593
    594             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
    595         });
    596     }
    597
    598     @Test
    599     void testApply_shearAlongY() {
    600         // arrange
    601         final double shearFactor = 2;
    602         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    603                 .shear(0, shearFactor);
    604
    605         // act/assert
    606         runWithCoordinates((x, y) -> {
    607             final Vector2D vec = Vector2D.of(x, y);
    608
    609             final Vector2D expectedVec = Vector2D.of(x, y + (shearFactor * x));
    610
    611             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
    612         });
    613     }
    614
    615     @Test
    616     void testApply_shearAlongXAndY() {
    617         // arrange
    618         final double shearX = 2;
    619         final double shearY = -3;
    620         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    621                 .shear(shearX, shearY);
    622
    623         // act/assert
    624         runWithCoordinates((x, y) -> {
    625             final Vector2D vec = Vector2D.of(x, y);
    626
    627             final Vector2D expectedVec = Vector2D.of(x + (shearX * y), y + (shearY * x));
    628
    629             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
    630         });
    631     }
    632
    633     @Test
    634     void testApply_translateShear() {
    635         // arrange
    636         final Vector2D translation = Vector2D.of(7, 8);
    637         final double shearX = -4;
    638         final double shearY = 5;
    639         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    640                 .translate(translation)
    641                 .shear(shearX, shearY);
    642
    643         // act/assert
    644         runWithCoordinates((x, y) -> {
    645             final Vector2D vec = Vector2D.of(x, y);
    646
    647             final double tx = x + translation.getX();
    648             final double ty = y + translation.getY();
    649
    650             final Vector2D expectedVec = Vector2D.of(tx + (shearX * ty), ty + (shearY * tx));
    651
    652             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
    653         });
    654     }
    655
    656
    657     @Test
    658     void testApply_translateScaleRotate() {
    659         // arrange
    660         final Vector2D translation = Vector2D.of(-2.0, -3.0);
    661         final Vector2D scale = Vector2D.of(5.0, 6.0);
    662
    663         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    664                 .translate(translation)
    665                 .scale(scale)
    666                 .rotate(Angle.PI_OVER_TWO);
    667
    668         // act/assert
    669         EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(12, -5), transform.apply(Vector2D.of(1, 1)), EPS);
    670
    671         runWithCoordinates((x, y) -> {
    672             final Vector2D vec = Vector2D.of(x, y);
    673
    674             final Vector2D temp = Vector2D.of(
    675                         (x + translation.getX()) * scale.getX(),
    676                         (y + translation.getY()) * scale.getY()
    677                     );
    678             final Vector2D expectedVec = Vector2D.of(-temp.getY(), temp.getX());
    679
    680             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
    681         });
    682     }
    683
    684     @Test
    685     void testApply_scaleTranslateRotate() {
    686         // arrange
    687         final Vector2D scale = Vector2D.of(5.0, 6.0);
    688         final Vector2D translation = Vector2D.of(-2.0, -3.0);
    689
    690         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    691                 .scale(scale)
    692                 .translate(translation)
    693                 .rotate(-Angle.PI_OVER_TWO);
    694
    695         // act/assert
    696         runWithCoordinates((x, y) -> {
    697             final Vector2D vec = Vector2D.of(x, y);
    698
    699             final Vector2D temp = Vector2D.of(
    700                         (x * scale.getX()) + translation.getX(),
    701                         (y * scale.getY()) + translation.getY()
    702                     );
    703             final Vector2D expectedVec = Vector2D.of(temp.getY(), -temp.getX());
    704
    705             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
    706         });
    707     }
    708
    709     @Test
    710     void testApplyXY() {
    711         // arrange
    712         final Vector2D scale = Vector2D.of(5.0, 6.0);
    713         final Vector2D translation = Vector2D.of(-2.0, -3.0);
    714         final Vector2D shear = Vector2D.of(7, 8);
    715
    716         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    717                 .scale(scale)
    718                 .translate(translation)
    719                 .rotate(-Angle.PI_OVER_TWO)
    720                 .shear(shear.getX(), shear.getY());
    721
    722         // act/assert
    723         runWithCoordinates((x, y) -> {
    724             final Vector2D scaledAndTranslated = Vector2D.of(
    725                         (x * scale.getX()) + translation.getX(),
    726                         (y * scale.getY()) + translation.getY()
    727                     );
    728             final Vector2D rotated = Vector2D.of(scaledAndTranslated.getY(), -scaledAndTranslated.getX());
    729             final Vector2D expected = Vector2D.of(
    730                         rotated.getX() + (rotated.getY() * shear.getX()),
    731                         rotated.getY() + (rotated.getX() * shear.getY())
    732                     );
    733
    734             Assertions.assertEquals(expected.getX(), transform.applyX(x, y), EPS);
    735             Assertions.assertEquals(expected.getY(), transform.applyY(x, y), EPS);
    736         });
    737     }
    738
    739     @Test
    740     void testApplyVector_identity() {
    741         // arrange
    742         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity();
    743
    744         // act/assert
    745         runWithCoordinates((x, y) -> {
    746             final Vector2D v = Vector2D.of(x, y);
    747
    748             EuclideanTestUtils.assertCoordinatesEqual(v, transform.applyVector(v), EPS);
    749         });
    750     }
    751
    752     @Test
    753     void testApplyVector_translate() {
    754         // arrange
    755         final Vector2D translation = Vector2D.of(1.1, -Math.PI);
    756
    757         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    758                 .translate(translation);
    759
    760         // act/assert
    761         runWithCoordinates((x, y) -> {
    762             final Vector2D vec = Vector2D.of(x, y);
    763
    764             EuclideanTestUtils.assertCoordinatesEqual(vec, transform.applyVector(vec), EPS);
    765         });
    766     }
    767
    768     @Test
    769     void testApplyVector_scale() {
    770         // arrange
    771         final Vector2D factors = Vector2D.of(2.0, -3.0);
    772
    773         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    774                 .scale(factors);
    775
    776         // act/assert
    777         runWithCoordinates((x, y) -> {
    778             final Vector2D vec = Vector2D.of(x, y);
    779
    780             final Vector2D expectedVec = Vector2D.of(factors.getX() * x, factors.getY() * y);
    781
    782             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyVector(vec), EPS);
    783         });
    784     }
    785
    786     @Test
    787     void testApplyVector_representsDisplacement() {
    788         // arrange
    789         final Vector2D p1 = Vector2D.of(2, 3);
    790
    791         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    792                 .scale(1.5)
    793                 .translate(4, 6)
    794                 .rotate(Angle.PI_OVER_TWO);
    795
    796         // act/assert
    797         runWithCoordinates((x, y) -> {
    798             final Vector2D p2 = Vector2D.of(x, y);
    799             final Vector2D input = p1.subtract(p2);
    800
    801             final Vector2D expected = transform.apply(p1).subtract(transform.apply(p2));
    802
    803             EuclideanTestUtils.assertCoordinatesEqual(expected, transform.applyVector(input), EPS);
    804         });
    805     }
    806
    807     @Test
    808     void testApplyVectorXY() {
    809         // arrange
    810         final Vector2D p1 = Vector2D.of(2, 3);
    811
    812         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    813                 .scale(1.5)
    814                 .translate(4, 6)
    815                 .rotate(0.3 * Math.PI);
    816
    817         // act/assert
    818         runWithCoordinates((x, y) -> {
    819             final Vector2D p2 = p1.add(Vector2D.of(x, y));
    820
    821             final Vector2D expected = transform.apply(p1).vectorTo(transform.apply(p2));
    822
    823             Assertions.assertEquals(expected.getX(), transform.applyVectorX(x, y), EPS);
    824             Assertions.assertEquals(expected.getY(), transform.applyVectorY(x, y), EPS);
    825         });
    826     }
    827
    828     @Test
    829     void testApplyDirection_identity() {
    830         // arrange
    831         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity();
    832
    833         // act/assert
    834         EuclideanTestUtils.permuteSkipZero(-5, 5, 0.5, (x, y) -> {
    835             final Vector2D v = Vector2D.of(x, y);
    836
    837             EuclideanTestUtils.assertCoordinatesEqual(v.normalize(), transform.applyDirection(v), EPS);
    838         });
    839     }
    840
    841     @Test
    842     void testApplyDirection_translate() {
    843         // arrange
    844         final Vector2D translation = Vector2D.of(1.1, -Math.PI);
    845
    846         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    847                 .translate(translation);
    848
    849         // act/assert
    850         EuclideanTestUtils.permuteSkipZero(-5, 5, 0.5, (x, y) -> {
    851             final Vector2D vec = Vector2D.of(x, y);
    852
    853             EuclideanTestUtils.assertCoordinatesEqual(vec.normalize(), transform.applyDirection(vec), EPS);
    854         });
    855     }
    856
    857     @Test
    858     void testApplyDirection_scale() {
    859         // arrange
    860         final Vector2D factors = Vector2D.of(2.0, -3.0);
    861
    862         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    863                 .scale(factors);
    864
    865         // act/assert
    866         EuclideanTestUtils.permuteSkipZero(-5, 5, 0.5, (x, y) -> {
    867             final Vector2D vec = Vector2D.of(x, y);
    868
    869             final Vector2D expectedVec = Vector2D.of(factors.getX() * x, factors.getY() * y).normalize();
    870
    871             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyDirection(vec), EPS);
    872         });
    873     }
    874
    875     @Test
    876     void testApplyDirection_representsNormalizedDisplacement() {
    877         // arrange
    878         final Vector2D p1 = Vector2D.of(2.1, 3.2);
    879
    880         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.identity()
    881                 .scale(1.5)
    882                 .translate(4, 6)
    883                 .rotate(Angle.PI_OVER_TWO);
    884
    885         // act/assert
    886         EuclideanTestUtils.permute(-5, 5, 0.5, (x, y) -> {
    887             final Vector2D p2 = Vector2D.of(x, y);
    888             final Vector2D input = p1.subtract(p2);
    889
    890             final Vector2D expected = transform.apply(p1).subtract(transform.apply(p2)).normalize();
    891
    892             EuclideanTestUtils.assertCoordinatesEqual(expected, transform.applyDirection(input), EPS);
    893         });
    894     }
    895
    896     @Test
    897     void testApplyDirection_illegalNorm() {
    898         // act/assert
    899         Assertions.assertThrows(IllegalArgumentException.class, () -> AffineTransformMatrix2D.createScale(1, 0).applyDirection(Vector2D.Unit.PLUS_Y));
    900         Assertions.assertThrows(IllegalArgumentException.class, () -> AffineTransformMatrix2D.createScale(2).applyDirection(Vector2D.ZERO));
    901     }
    902
    903     @Test
    904     void testDeterminant() {
    905         // act/assert
    906         Assertions.assertEquals(1.0, AffineTransformMatrix2D.identity().determinant(), EPS);
    907         Assertions.assertEquals(6.0, AffineTransformMatrix2D.of(
    908                 2, 0, 4,
    909                 0, 3, 5
    910             ).determinant(), EPS);
    911         Assertions.assertEquals(-6.0, AffineTransformMatrix2D.of(
    912                 2, 0, 4,
    913                 0, -3, 5
    914             ).determinant(), EPS);
    915         Assertions.assertEquals(-5.0, AffineTransformMatrix2D.of(
    916                 1, 3, 0,
    917                 2, 1, 0
    918             ).determinant(), EPS);
    919         Assertions.assertEquals(-0.0, AffineTransformMatrix2D.of(
    920                 0, 0, 1,
    921                 0, 0, 2
    922             ).determinant(), EPS);
    923     }
    924
    925     @Test
    926     void testPreservesOrientation() {
    927         // act/assert
    928         Assertions.assertTrue(AffineTransformMatrix2D.identity().preservesOrientation());
    929         Assertions.assertTrue(AffineTransformMatrix2D.of(
    930                 2, 0, 4,
    931                 0, 3, 5
    932             ).preservesOrientation());
    933
    934         Assertions.assertFalse(AffineTransformMatrix2D.of(
    935                 2, 0, 4,
    936                 0, -3, 5
    937             ).preservesOrientation());
    938         Assertions.assertFalse(AffineTransformMatrix2D.of(
    939                 1, 3, 0,
    940                 2, 1, 0
    941             ).preservesOrientation());
    942         Assertions.assertFalse(AffineTransformMatrix2D.of(
    943                 0, 0, 1,
    944                 0, 0, 2
    945             ).preservesOrientation());
    946     }
    947
    948     @Test
    949     void testMultiply() {
    950         // arrange
    951         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    952                     1, 2, 3,
    953                     5, 6, 7
    954                 );
    955         final AffineTransformMatrix2D b = AffineTransformMatrix2D.of(
    956                     13, 14, 15,
    957                     17, 18, 19
    958                 );
    959
    960         // act
    961         final AffineTransformMatrix2D result = a.multiply(b);
    962
    963         // assert
    964         final double[] arr = result.toArray();
    965         Assertions.assertArrayEquals(new double[] {
    966             47, 50, 56,
    967             167, 178, 196
    968         }, arr, EPS);
    969     }
    970
    971     @Test
    972     void testMultiply_combinesTransformOperations() {
    973         // arrange
    974         final Vector2D translation1 = Vector2D.of(1, 2);
    975         final double scale = 2.0;
    976         final Vector2D translation2 = Vector2D.of(4, 5);
    977
    978         final AffineTransformMatrix2D a = AffineTransformMatrix2D.createTranslation(translation1);
    979         final AffineTransformMatrix2D b = AffineTransformMatrix2D.createScale(scale);
    980         final AffineTransformMatrix2D c = AffineTransformMatrix2D.identity();
    981         final AffineTransformMatrix2D d = AffineTransformMatrix2D.createTranslation(translation2);
    982
    983         // act
    984         final AffineTransformMatrix2D transform = d.multiply(c).multiply(b).multiply(a);
    985
    986         // assert
    987         runWithCoordinates((x, y) -> {
    988             final Vector2D vec = Vector2D.of(x, y);
    989
    990             final Vector2D expectedVec = vec
    991                     .add(translation1)
    992                     .multiply(scale)
    993                     .add(translation2);
    994
    995             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
    996         });
    997     }
    998
    999     @Test
    1000     void testPremultiply() {
    1001         // arrange
    1002         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    1003                     1, 2, 3,
    1004                     5, 6, 7
    1005                 );
    1006         final AffineTransformMatrix2D b = AffineTransformMatrix2D.of(
    1007                     13, 14, 15,
    1008                     17, 18, 19
    1009                 );
    1010
    1011         // act
    1012         final AffineTransformMatrix2D result = b.premultiply(a);
    1013
    1014         // assert
    1015         final double[] arr = result.toArray();
    1016         Assertions.assertArrayEquals(new double[] {
    1017             47, 50, 56,
    1018             167, 178, 196
    1019         }, arr, EPS);
    1020     }
    1021
    1022     @Test
    1023     void testPremultiply_combinesTransformOperations() {
    1024         // arrange
    1025         final Vector2D translation1 = Vector2D.of(1, 2);
    1026         final double scale = 2.0;
    1027         final Vector2D translation2 = Vector2D.of(4, 5);
    1028
    1029         final AffineTransformMatrix2D a = AffineTransformMatrix2D.createTranslation(translation1);
    1030         final AffineTransformMatrix2D b = AffineTransformMatrix2D.createScale(scale);
    1031         final AffineTransformMatrix2D c = AffineTransformMatrix2D.identity();
    1032         final AffineTransformMatrix2D d = AffineTransformMatrix2D.createTranslation(translation2);
    1033
    1034         // act
    1035         final AffineTransformMatrix2D transform = a.premultiply(b).premultiply(c).premultiply(d);
    1036
    1037         // assert
    1038         runWithCoordinates((x, y) -> {
    1039             final Vector2D vec = Vector2D.of(x, y);
    1040
    1041             final Vector2D expectedVec = vec
    1042                     .add(translation1)
    1043                     .multiply(scale)
    1044                     .add(translation2);
    1045
    1046             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
    1047         });
    1048     }
    1049
    1050     @Test
    1051     void testInverse_identity() {
    1052         // act
    1053         final AffineTransformMatrix2D inverse = AffineTransformMatrix2D.identity().inverse();
    1054
    1055         // assert
    1056         final double[] expected = {
    1057             1, 0, 0,
    1058             0, 1, 0
    1059         };
    1060         Assertions.assertArrayEquals(expected, inverse.toArray(), 0.0);
    1061     }
    1062
    1063     @Test
    1064     void testInverse_multiplyByInverse_producesIdentity() {
    1065         // arrange
    1066         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    1067                     1, 3, 7,
    1068                     2, 4, 9
    1069                 );
    1070
    1071         final AffineTransformMatrix2D inv = a.inverse();
    1072
    1073         // act
    1074         final AffineTransformMatrix2D result = inv.multiply(a);
    1075
    1076         // assert
    1077         final double[] expected = {
    1078             1, 0, 0,
    1079             0, 1, 0
    1080         };
    1081         Assertions.assertArrayEquals(expected, result.toArray(), EPS);
    1082     }
    1083
    1084     @Test
    1085     void testInverse_translate() {
    1086         // arrange
    1087         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createTranslation(1, -2);
    1088
    1089         // act
    1090         final AffineTransformMatrix2D inverse = transform.inverse();
    1091
    1092         // assert
    1093         final double[] expected = {
    1094             1, 0, -1,
    1095             0, 1, 2
    1096         };
    1097         Assertions.assertArrayEquals(expected, inverse.toArray(), 0.0);
    1098     }
    1099
    1100     @Test
    1101     void testInverse_scale() {
    1102         // arrange
    1103         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(10, -2);
    1104
    1105         // act
    1106         final AffineTransformMatrix2D inverse = transform.inverse();
    1107
    1108         // assert
    1109         final double[] expected = {
    1110             0.1, 0, 0,
    1111             0, -0.5, 0
    1112         };
    1113         Assertions.assertArrayEquals(expected, inverse.toArray(), 0.0);
    1114     }
    1115
    1116     @Test
    1117     void testInverse_rotate() {
    1118         // arrange
    1119         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(Angle.PI_OVER_TWO);
    1120
    1121         // act
    1122         final AffineTransformMatrix2D inverse = transform.inverse();
    1123
    1124         // assert
    1125         final double[] expected = {
    1126             0, 1, 0,
    1127             -1, 0, 0
    1128         };
    1129         Assertions.assertArrayEquals(expected, inverse.toArray(), EPS);
    1130     }
    1131
    1132     @Test
    1133     void testInverse_rotate_aroundCenter() {
    1134         // arrange
    1135         final Vector2D center = Vector2D.of(1, 2);
    1136         final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createRotation(center, Angle.PI_OVER_TWO);
    1137
    1138         // act
    1139         final AffineTransformMatrix2D inverse = transform.inverse();
    1140
    1141         // assert
    1142         final double[] expected = {
    1143             0, 1, -1,
    1144             -1, 0, 3
    1145         };
    1146         Assertions.assertArrayEquals(expected, inverse.toArray(), EPS);
    1147     }
    1148
    1149     @Test
    1150     void testInverse_undoesOriginalTransform() {
    1151         // arrange
    1152         final Vector2D v1 = Vector2D.ZERO;
    1153         final Vector2D v2 = Vector2D.Unit.PLUS_X;
    1154         final Vector2D v3 = Vector2D.of(1, 1);
    1155         final Vector2D v4 = Vector2D.of(-2, 3);
    1156
    1157         final Vector2D center = Vector2D.of(-0.5, 2);
    1158
    1159         // act/assert
    1160         runWithCoordinates((x, y) -> {
    1161             final AffineTransformMatrix2D transform = AffineTransformMatrix2D
    1162                         .createTranslation(x, y)
    1163                         .scale(2, 3)
    1164                         .translate(x / 3, y / 3)
    1165                         .rotate(x / 4)
    1166                         .rotate(center, y / 2);
    1167
    1168             final AffineTransformMatrix2D inverse = transform.inverse();
    1169
    1170             EuclideanTestUtils.assertCoordinatesEqual(v1, inverse.apply(transform.apply(v1)), EPS);
    1171             EuclideanTestUtils.assertCoordinatesEqual(v2, inverse.apply(transform.apply(v2)), EPS);
    1172             EuclideanTestUtils.assertCoordinatesEqual(v3, inverse.apply(transform.apply(v3)), EPS);
    1173             EuclideanTestUtils.assertCoordinatesEqual(v4, inverse.apply(transform.apply(v4)), EPS);
    1174         });
    1175     }
    1176
    1177     @Test
    1178     void testInverse_nonInvertible() {
    1179         // act/assert
    1180         GeometryTestUtils.assertThrowsWithMessage(() -> AffineTransformMatrix2D.of(
    1181                 0, 0, 0,
    1182                 0, 0, 0).inverse(), IllegalStateException.class, "Matrix is not invertible; matrix determinant is 0.0");
    1183
    1184         GeometryTestUtils.assertThrowsWithMessage(() -> AffineTransformMatrix2D.of(
    1185                 1, 0, 0,
    1186                 0, Double.NaN, 0).inverse(), IllegalStateException.class, "Matrix is not invertible; matrix determinant is NaN");
    1187
    1188         GeometryTestUtils.assertThrowsWithMessage(() -> AffineTransformMatrix2D.of(
    1189                 1, 0, 0,
    1190                 0, Double.NEGATIVE_INFINITY, 0).inverse(), IllegalStateException.class, "Matrix is not invertible; matrix determinant is -Infinity");
    1191
    1192         GeometryTestUtils.assertThrowsWithMessage(() -> AffineTransformMatrix2D.of(
    1193                 Double.POSITIVE_INFINITY, 0, 0,
    1194                 0, 1, 0).inverse(), IllegalStateException.class, "Matrix is not invertible; matrix determinant is Infinity");
    1195
    1196         GeometryTestUtils.assertThrowsWithMessage(() -> AffineTransformMatrix2D.of(
    1197                 1, 0, Double.NaN,
    1198                 0, 1, 0).inverse(), IllegalStateException.class, "Matrix is not invertible; invalid matrix element: NaN");
    1199
    1200         GeometryTestUtils.assertThrowsWithMessage(() -> AffineTransformMatrix2D.of(
    1201                 1, 0, Double.POSITIVE_INFINITY,
    1202                 0, 1, 0).inverse(), IllegalStateException.class, "Matrix is not invertible; invalid matrix element: Infinity");
    1203
    1204         GeometryTestUtils.assertThrowsWithMessage(() -> AffineTransformMatrix2D.of(
    1205                 1, 0, Double.NEGATIVE_INFINITY,
    1206                 0, 1, 0).inverse(), IllegalStateException.class, "Matrix is not invertible; invalid matrix element: -Infinity");
    1207     }
    1208
    1209     @Test
    1210     void testLinear() {
    1211         // arrange
    1212         final AffineTransformMatrix2D mat = AffineTransformMatrix2D.of(
    1213                 2, 3, 4,
    1214                 5, 6, 7);
    1215
    1216         // act
    1217         final AffineTransformMatrix2D result = mat.linear();
    1218
    1219         // assert
    1220         final double[] expected = {
    1221             2, 3, 0,
    1222             5, 6, 0
    1223         };
    1224         Assertions.assertArrayEquals(expected, result.toArray(), 0.0);
    1225     }
    1226
    1227     @Test
    1228     void testLinearTranspose() {
    1229         // arrange
    1230         final AffineTransformMatrix2D mat = AffineTransformMatrix2D.of(
    1231                 2, 3, 4,
    1232                 5, 6, 7);
    1233
    1234         // act
    1235         final AffineTransformMatrix2D result = mat.linearTranspose();
    1236
    1237         // assert
    1238         final double[] expected = {
    1239             2, 5, 0,
    1240             3, 6, 0
    1241         };
    1242         Assertions.assertArrayEquals(expected, result.toArray(), 0.0);
    1243     }
    1244
    1245     @Test
    1246     void testNormalTransform() {
    1247         // act/assert
    1248         checkNormalTransform(AffineTransformMatrix2D.identity());
    1249
    1250         checkNormalTransform(AffineTransformMatrix2D.createTranslation(2, 3));
    1251         checkNormalTransform(AffineTransformMatrix2D.createTranslation(-3, -4));
    1252
    1253         checkNormalTransform(AffineTransformMatrix2D.createScale(2, 5));
    1254         checkNormalTransform(AffineTransformMatrix2D.createScale(-3, 4));
    1255         checkNormalTransform(AffineTransformMatrix2D.createScale(-2, -5));
    1256
    1257         checkNormalTransform(AffineTransformMatrix2D.createRotation(Angle.PI_OVER_TWO));
    1258         checkNormalTransform(AffineTransformMatrix2D.createRotation(THREE_PI_OVER_TWO));
    1259
    1260         checkNormalTransform(AffineTransformMatrix2D.createRotation(Vector2D.of(3, 4), THREE_PI_OVER_TWO)
    1261                 .translate(8, 2)
    1262                 .scale(-3, -2));
    1263         checkNormalTransform(AffineTransformMatrix2D.createScale(2, -1)
    1264                 .translate(-3, -4)
    1265                 .rotate(Vector2D.of(-0.5, 0.5), 0.75 * Math.PI));
    1266     }
    1267
    1268     private void checkNormalTransform(final AffineTransformMatrix2D transform) {
    1269         final AffineTransformMatrix2D normalTransform = transform.normalTransform();
    1270
    1271         final Vector2D p1 = Vector2D.of(-0.25, 0.75);
    1272         final Vector2D t1 = transform.apply(p1);
    1273
    1274         EuclideanTestUtils.permute(-10, 10, 1, (x, y) -> {
    1275             final Vector2D p2 = Vector2D.of(x, y);
    1276             final Vector2D n = Lines.fromPoints(p1, p2, TEST_PRECISION).getOffsetDirection();
    1277
    1278             final Vector2D t2 = transform.apply(p2);
    1279
    1280             final Line tLine = transform.preservesOrientation() ?
    1281                     Lines.fromPoints(t1, t2, TEST_PRECISION) :
    1282                     Lines.fromPoints(t2, t1, TEST_PRECISION);
    1283             final Vector2D expected = tLine.getOffsetDirection();
    1284
    1285             final Vector2D actual = normalTransform.apply(n).normalize();
    1286
    1287             EuclideanTestUtils.assertCoordinatesEqual(expected, actual, EPS);
    1288         });
    1289     }
    1290
    1291     @Test
    1292     void testNormalTransform_nonInvertible() {
    1293         // act/assert
    1294         Assertions.assertThrows(IllegalStateException.class, () -> AffineTransformMatrix2D.createScale(0).normalTransform());
    1295     }
    1296
    1297     @Test
    1298     void testHashCode() {
    1299         // arrange
    1300         final double[] values = {
    1301             1, 2, 3,
    1302             5, 6, 7
    1303         };
    1304
    1305         // act/assert
    1306         final int orig = AffineTransformMatrix2D.of(values).hashCode();
    1307         final int same = AffineTransformMatrix2D.of(values).hashCode();
    1308
    1309         Assertions.assertEquals(orig, same);
    1310
    1311         double[] temp;
    1312         for (int i = 0; i < values.length; ++i) {
    1313             temp = values.clone();
    1314             temp[i] = 0;
    1315
    1316             final int modified = AffineTransformMatrix2D.of(temp).hashCode();
    1317
    1318             Assertions.assertNotEquals(orig, modified);
    1319         }
    1320     }
    1321
    1322     @Test
    1323     void testEquals() {
    1324         // arrange
    1325         final double[] values = {
    1326             1, 2, 3,
    1327             5, 6, 7
    1328         };
    1329
    1330         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(values);
    1331
    1332         // act/assert
    1333         GeometryTestUtils.assertSimpleEqualsCases(a);
    1334
    1335         double[] temp;
    1336         for (int i = 0; i < values.length; ++i) {
    1337             temp = values.clone();
    1338             temp[i] = 0;
    1339
    1340             final AffineTransformMatrix2D modified = AffineTransformMatrix2D.of(temp);
    1341
    1342             Assertions.assertNotEquals(a, modified);
    1343         }
    1344     }
    1345
    1346     @Test
    1347     void testEqualsAndHashCode_signedZeroConsistency() {
    1348         // arrange
    1349         final double[] arrWithPosZero = {
    1350             1.0, 0.0, 0.0,
    1351             0.0, 1.0, 0.0
    1352         };
    1353         final double[] arrWithNegZero = {
    1354             1.0, 0.0, 0.0,
    1355             0.0, 1.0, -0.0
    1356         };
    1357         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(arrWithPosZero);
    1358         final AffineTransformMatrix2D b = AffineTransformMatrix2D.of(arrWithNegZero);
    1359         final AffineTransformMatrix2D c = AffineTransformMatrix2D.of(arrWithPosZero);
    1360         final AffineTransformMatrix2D d = AffineTransformMatrix2D.of(arrWithNegZero);
    1361
    1362         // act/assert
    1363         Assertions.assertFalse(a.equals(b));
    1364         Assertions.assertNotEquals(a.hashCode(), b.hashCode());
    1365
    1366         Assertions.assertTrue(a.equals(c));
    1367         Assertions.assertEquals(a.hashCode(), c.hashCode());
    1368
    1369         Assertions.assertTrue(b.equals(d));
    1370         Assertions.assertEquals(b.hashCode(), d.hashCode());
    1371     }
    1372
    1373     @Test
    1374     void testToString() {
    1375         // arrange
    1376         final AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
    1377                     1, 2, 3,
    1378                     5, 6, 7
    1379                 );
    1380
    1381         // act
    1382         final String result = a.toString();
    1383
    1384         // assert
    1385         Assertions.assertEquals(
    1386                 "[ 1.0, 2.0, 3.0; " +
    1387                 "5.0, 6.0, 7.0 ]", result);
    1388     }
    1389
    1390     @FunctionalInterface
    1391     private interface Coordinate2DTest {
    1392
    1393         void run(double x, double y);
    1394     }
    1395
    1396     private static void runWithCoordinates(final Coordinate2DTest test) {
    1397         runWithCoordinates(test, -1e-2, 1e-2, 5e-3);
    1398         runWithCoordinates(test, -1e2, 1e2, 5);
    1399     }
    1400
    1401     private static void runWithCoordinates(final Coordinate2DTest test, final double min, final double max, final double step) {
    1402         for (double x = min; x <= max; x += step) {
    1403             for (double y = min; y <= max; y += step) {
    1404                 test.run(x, y);
    1405             }
    1406         }
    1407     }
    1408 }