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