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.ArrayList;
    20  import java.util.Arrays;
    21  import java.util.Collections;
    22  import java.util.List;
    23
    24  import org.apache.commons.geometry.core.GeometryTestUtils;
    25  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
    26  import org.apache.commons.geometry.euclidean.threed.line.Line3D;
    27  import org.apache.commons.geometry.euclidean.threed.line.Lines3D;
    28  import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
    29  import org.apache.commons.numbers.angle.Angle;
    30  import org.apache.commons.numbers.core.Precision;
    31  import org.junit.jupiter.api.Assertions;
    32  import org.junit.jupiter.api.Test;
    33
    34  class PlaneTest {
    35
    36      private static final double TEST_EPS = 1e-10;
    37
    38      private static final Precision.DoubleEquivalence TEST_PRECISION =
    39              Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
    40
    41      @Test
    42      void testFromNormal() {
    43          // act/assert
    44          checkPlane(Planes.fromNormal(Vector3D.Unit.PLUS_X, TEST_PRECISION), Vector3D.ZERO, Vector3D.Unit.PLUS_X);
    45          checkPlane(Planes.fromNormal(Vector3D.of(7, 0, 0), TEST_PRECISION), Vector3D.ZERO, Vector3D.Unit.PLUS_X);
    46
    47          checkPlane(Planes.fromNormal(Vector3D.Unit.PLUS_Y, TEST_PRECISION), Vector3D.ZERO, Vector3D.Unit.PLUS_Y);
    48          checkPlane(Planes.fromNormal(Vector3D.of(0, 5, 0), TEST_PRECISION), Vector3D.ZERO, Vector3D.Unit.PLUS_Y);
    49
    50          checkPlane(Planes.fromNormal(Vector3D.Unit.PLUS_Z, TEST_PRECISION), Vector3D.ZERO, Vector3D.Unit.PLUS_Z);
    51          checkPlane(Planes.fromNormal(Vector3D.of(0, 0, 0.01), TEST_PRECISION), Vector3D.ZERO, Vector3D.Unit.PLUS_Z);
    52      }
    53
    54      @Test
    55      void testFromNormal_illegalArguments() {
    56          // act/assert
    57          Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromNormal(Vector3D.ZERO, TEST_PRECISION));
    58      }
    59
    60      @Test
    61      void testFromPointAndNormal() {
    62          // arrange
    63          final Vector3D pt = Vector3D.of(1, 2, 3);
    64
    65          // act/assert
    66          checkPlane(Planes.fromPointAndNormal(pt, Vector3D.of(0.1, 0, 0), TEST_PRECISION),
    67                  Vector3D.of(1, 0, 0), Vector3D.Unit.PLUS_X);
    68          checkPlane(Planes.fromPointAndNormal(pt, Vector3D.of(0, 2, 0), TEST_PRECISION),
    69                  Vector3D.of(0, 2, 0), Vector3D.Unit.PLUS_Y);
    70          checkPlane(Planes.fromPointAndNormal(pt, Vector3D.of(0, 0, 5), TEST_PRECISION),
    71                  Vector3D.of(0, 0, 3), Vector3D.Unit.PLUS_Z);
    72      }
    73
    74      @Test
    75      void testFromPointAndNormal_illegalArguments() {
    76          // arrange
    77          final Vector3D pt = Vector3D.of(1, 2, 3);
    78
    79          // act/assert
    80          Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPointAndNormal(pt, Vector3D.ZERO, TEST_PRECISION));
    81      }
    82
    83      @Test
    84      void testFromPoints() {
    85          // arrange
    86          final Vector3D a = Vector3D.of(1, 1, 1);
    87          final Vector3D b = Vector3D.of(1, 1, 4.3);
    88          final Vector3D c = Vector3D.of(2.5, 1, 1);
    89
    90          // act/assert
    91          checkPlane(Planes.fromPoints(a, b, c, TEST_PRECISION),
    92                  Vector3D.of(0, 1, 0), Vector3D.Unit.PLUS_Y);
    93
    94          checkPlane(Planes.fromPoints(a, c, b, TEST_PRECISION),
    95                  Vector3D.of(0, 1, 0), Vector3D.Unit.MINUS_Y);
    96      }
    97
    98      @Test
    99      void testFromPoints_planeContainsSourcePoints() {
    100         // arrange
    101         final Vector3D p1 = Vector3D.of(1.2, 3.4, -5.8);
    102         final Vector3D p2 = Vector3D.of(3.4, -5.8, 1.2);
    103         final Vector3D p3 = Vector3D.of(-2.0, 4.3, 0.7);
    104
    105         // act
    106         final Plane plane  = Planes.fromPoints(p1, p2, p3, TEST_PRECISION);
    107
    108         // assert
    109         Assertions.assertTrue(plane.contains(p1));
    110         Assertions.assertTrue(plane.contains(p2));
    111         Assertions.assertTrue(plane.contains(p3));
    112     }
    113
    114     @Test
    115     void testFromPoints_illegalArguments() {
    116         // arrange
    117         final Vector3D a = Vector3D.of(1, 0, 0);
    118         final Vector3D b = Vector3D.of(0, 1, 0);
    119
    120         // act/assert
    121
    122         Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPoints(a, a, a, TEST_PRECISION));
    123         Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPoints(a, a, b, TEST_PRECISION));
    124         Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPoints(a, b, a, TEST_PRECISION));
    125         Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPoints(b, a, a, TEST_PRECISION));
    126     }
    127
    128     @Test
    129     void testFromPoints_collection_threePoints() {
    130         // arrange
    131         final List<Vector3D> pts = Arrays.asList(
    132                     Vector3D.of(1, 1, 0),
    133                     Vector3D.of(1, 1, -1),
    134                     Vector3D.of(0, 1, 0)
    135                 );
    136
    137         // act
    138         final Plane plane = Planes.fromPoints(pts, TEST_PRECISION);
    139
    140         // assert
    141         checkPlane(plane, Vector3D.Unit.PLUS_Y, Vector3D.Unit.PLUS_Y);
    142
    143         Assertions.assertTrue(plane.contains(pts.get(0)));
    144         Assertions.assertTrue(plane.contains(pts.get(1)));
    145         Assertions.assertTrue(plane.contains(pts.get(2)));
    146     }
    147
    148     @Test
    149     void testFromPoints_collection_someCollinearPoints() {
    150         // arrange
    151         final List<Vector3D> pts = Arrays.asList(
    152                     Vector3D.of(1, 0, 2),
    153                     Vector3D.of(2, 0, 2),
    154                     Vector3D.of(3, 0, 2),
    155                     Vector3D.of(0, 1, 2)
    156                 );
    157
    158         // act
    159         final Plane plane = Planes.fromPoints(pts, TEST_PRECISION);
    160
    161         // assert
    162         checkPlane(plane, Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z);
    163
    164         Assertions.assertTrue(plane.contains(pts.get(0)));
    165         Assertions.assertTrue(plane.contains(pts.get(1)));
    166         Assertions.assertTrue(plane.contains(pts.get(2)));
    167         Assertions.assertTrue(plane.contains(pts.get(3)));
    168     }
    169
    170     @Test
    171     void testFromPoints_collection_concaveWithCollinearAndDuplicatePoints() {
    172         // arrange
    173         final List<Vector3D> pts = Arrays.asList(
    174                     Vector3D.of(1, 0, 1),
    175                     Vector3D.of(1, 0, 0.5),
    176
    177                     Vector3D.of(1, 0, 0),
    178                     Vector3D.of(1, 1, -1),
    179                     Vector3D.of(1, 2, 0),
    180                     Vector3D.of(1, 2, 1e-15),
    181                     Vector3D.of(1, 1, -0.5),
    182                     Vector3D.of(1, 1 + 1e-15, -0.5),
    183                     Vector3D.of(1 - 1e-15, 1, -0.5),
    184                     Vector3D.of(1, 0, 0),
    185
    186                     Vector3D.of(1, 0, 0.5),
    187                     Vector3D.of(1, 0, 1)
    188                 );
    189
    190         final Vector3D origin = Vector3D.of(1, 0, 0);
    191
    192         // act
    193         checkPlane(Planes.fromPoints(pts, TEST_PRECISION), origin, Vector3D.Unit.PLUS_X);
    194
    195         for (int i = 1; i < 12; ++i) {
    196             checkPlane(Planes.fromPoints(rotate(pts, i), TEST_PRECISION), origin, Vector3D.Unit.PLUS_X);
    197         }
    198     }
    199
    200     @Test
    201     void testFromPoints_collection_choosesBestOrientation() {
    202         // act/assert
    203         checkPlane(Planes.fromPoints(Arrays.asList(
    204                 Vector3D.of(1, 0, 2),
    205                 Vector3D.of(2, 0, 2),
    206                 Vector3D.of(3, 0, 2),
    207                 Vector3D.of(3.5, 1, 2)
    208             ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z);
    209
    210         checkPlane(Planes.fromPoints(Arrays.asList(
    211                 Vector3D.of(1, 0, 2),
    212                 Vector3D.of(2, 0, 2),
    213                 Vector3D.of(3, 0, 2),
    214                 Vector3D.of(3.5, -1, 2)
    215             ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.MINUS_Z);
    216
    217         checkPlane(Planes.fromPoints(Arrays.asList(
    218                 Vector3D.of(1, 0, 2),
    219                 Vector3D.of(2, 0, 2),
    220                 Vector3D.of(3, 0, 2),
    221                 Vector3D.of(3.5, -1, 2),
    222                 Vector3D.of(4, 0, 2)
    223             ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z);
    224
    225         checkPlane(Planes.fromPoints(Arrays.asList(
    226                 Vector3D.of(1, 0, 2),
    227                 Vector3D.of(2, 0, 2),
    228                 Vector3D.of(3, 0, 2),
    229                 Vector3D.of(3.5, 1, 2),
    230                 Vector3D.of(4, -1, 2)
    231             ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.MINUS_Z);
    232
    233         checkPlane(Planes.fromPoints(Arrays.asList(
    234                 Vector3D.of(0, 0, 2),
    235                 Vector3D.of(1, 0, 2),
    236                 Vector3D.of(1, 1, 2),
    237                 Vector3D.of(0, 1, 2),
    238                 Vector3D.of(0, 0, 2)
    239             ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z);
    240
    241         checkPlane(Planes.fromPoints(Arrays.asList(
    242                 Vector3D.of(0, 0, 2),
    243                 Vector3D.of(0, 1, 2),
    244                 Vector3D.of(1, 1, 2),
    245                 Vector3D.of(1, 0, 2),
    246                 Vector3D.of(0, 0, 2)
    247             ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.MINUS_Z);
    248
    249         checkPlane(Planes.fromPoints(Arrays.asList(
    250                 Vector3D.of(0, 0, 2),
    251                 Vector3D.of(1, 0, 2),
    252                 Vector3D.of(2, 1, 2),
    253                 Vector3D.of(3, 0, 2),
    254                 Vector3D.of(2, 4, 2),
    255                 Vector3D.of(0, 0, 2)
    256             ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z);
    257
    258         checkPlane(Planes.fromPoints(Arrays.asList(
    259                 Vector3D.of(0, 0, 2),
    260                 Vector3D.of(0, 1, 2),
    261                 Vector3D.of(2, 4, 2),
    262                 Vector3D.of(3, 0, 2),
    263                 Vector3D.of(2, 1, 2),
    264                 Vector3D.of(0, 0, 2)
    265             ), TEST_PRECISION), Vector3D.of(0, 0, 2), Vector3D.Unit.MINUS_Z);
    266     }
    267
    268     @Test
    269     void testFromPoints_collection_illegalArguments() {
    270         // arrange
    271         final Vector3D a = Vector3D.ZERO;
    272         final Vector3D b = Vector3D.Unit.PLUS_X;
    273
    274         // act/assert
    275
    276         Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPoints(Collections.emptyList(), TEST_PRECISION));
    277         Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPoints(Collections.singletonList(a), TEST_PRECISION));
    278         Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPoints(Arrays.asList(a, b), TEST_PRECISION));
    279     }
    280
    281     @Test
    282     void testFromPoints_collection_allPointsCollinear() {
    283         // act/assert
    284         Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPoints(Arrays.asList(
    285                 Vector3D.ZERO,
    286                 Vector3D.Unit.PLUS_X,
    287                 Vector3D.of(2, 0, 0)
    288         ), TEST_PRECISION));
    289         Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPoints(Arrays.asList(
    290                 Vector3D.ZERO,
    291                 Vector3D.Unit.PLUS_X,
    292                 Vector3D.of(2, 0, 0),
    293                 Vector3D.of(3, 0, 0)
    294         ), TEST_PRECISION));
    295     }
    296
    297     @Test
    298     void testFromPoints_collection_notEnoughUniquePoints() {
    299         // act/assert
    300         Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPoints(Arrays.asList(
    301                 Vector3D.ZERO,
    302                 Vector3D.ZERO,
    303                 Vector3D.of(1e-12, 1e-12, 0),
    304                 Vector3D.Unit.PLUS_X
    305         ), TEST_PRECISION));
    306         Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPoints(Arrays.asList(
    307                 Vector3D.ZERO,
    308                 Vector3D.of(1e-12, 0, 0),
    309                 Vector3D.ZERO
    310         ), TEST_PRECISION));
    311     }
    312
    313     @Test
    314     void testFromPoints_collection_pointsNotOnSamePlane() {
    315         // act/assert
    316         Assertions.assertThrows(IllegalArgumentException.class, () -> Planes.fromPoints(Arrays.asList(
    317                 Vector3D.ZERO,
    318                 Vector3D.Unit.PLUS_X,
    319                 Vector3D.Unit.PLUS_Y,
    320                 Vector3D.Unit.PLUS_Z
    321         ), TEST_PRECISION));
    322     }
    323
    324     @Test
    325     void testGetEmbedding() {
    326         // arrange
    327         final Vector3D pt = Vector3D.of(1, 2, 3);
    328         EuclideanTestUtils.permuteSkipZero(-4, 4, 1, (x, y, z) -> {
    329
    330             final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.of(x, y, z), TEST_PRECISION);
    331
    332             // act
    333             final EmbeddingPlane embeddingPlane = plane.getEmbedding();
    334             final EmbeddingPlane nextEmbeddingPlane = plane.getEmbedding();
    335
    336             // assert
    337             Assertions.assertSame(plane.getNormal(), embeddingPlane.getNormal());
    338             Assertions.assertSame(plane.getNormal(), embeddingPlane.getW());
    339             Assertions.assertEquals(plane.getOriginOffset(), embeddingPlane.getOriginOffset(), TEST_EPS);
    340             Assertions.assertSame(plane.getPrecision(), embeddingPlane.getPrecision());
    341
    342             final Vector3D.Unit u = embeddingPlane.getU();
    343             final Vector3D.Unit v = embeddingPlane.getV();
    344             final Vector3D.Unit w = embeddingPlane.getW();
    345
    346             Assertions.assertEquals(0, u.dot(v), TEST_EPS);
    347             Assertions.assertEquals(0, u.dot(w), TEST_EPS);
    348             Assertions.assertEquals(0, v.dot(w), TEST_EPS);
    349
    350             Assertions.assertNotSame(embeddingPlane, nextEmbeddingPlane);
    351             Assertions.assertEquals(embeddingPlane, nextEmbeddingPlane);
    352         });
    353     }
    354
    355     @Test
    356     void testContains_point() {
    357         // arrange
    358         final Plane plane = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0, 0, 1), TEST_PRECISION);
    359         final double halfEps = 0.5 * TEST_EPS;
    360
    361         // act/assert
    362         EuclideanTestUtils.permute(-100, 100, 5, (x, y) -> {
    363
    364             Assertions.assertTrue(plane.contains(Vector3D.of(x, y, 1)));
    365             Assertions.assertTrue(plane.contains(Vector3D.of(x, y, 1 + halfEps)));
    366             Assertions.assertTrue(plane.contains(Vector3D.of(x, y, 1 - halfEps)));
    367
    368             Assertions.assertFalse(plane.contains(Vector3D.of(x, y, 0.5)));
    369             Assertions.assertFalse(plane.contains(Vector3D.of(x, y, 1.5)));
    370         });
    371     }
    372
    373     @Test
    374     void testContains_line() {
    375         // arrange
    376         final Plane plane = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    377
    378         // act/assert
    379         Assertions.assertTrue(plane.contains(
    380                 Lines3D.fromPoints(Vector3D.of(1, 0, 0), Vector3D.of(2, 0, 0), TEST_PRECISION)));
    381         Assertions.assertTrue(plane.contains(
    382                 Lines3D.fromPoints(Vector3D.of(-1, 0, 0), Vector3D.of(-2, 0, 0), TEST_PRECISION)));
    383
    384         Assertions.assertFalse(plane.contains(
    385                 Lines3D.fromPoints(Vector3D.of(1, 0, 2), Vector3D.of(2, 0, 2), TEST_PRECISION)));
    386         Assertions.assertFalse(plane.contains(
    387                 Lines3D.fromPoints(Vector3D.ZERO, Vector3D.of(2, 0, 2), TEST_PRECISION)));
    388     }
    389
    390     @Test
    391     void testContains_plane() {
    392         // arrange
    393         final Vector3D p1 = Vector3D.of(1.2, 3.4, -5.8);
    394         final Vector3D p2 = Vector3D.of(3.4, -5.8, 1.2);
    395         final Vector3D p3 = Vector3D.of(-2.0, 4.3, 0.7);
    396         final Plane planeA = Planes.fromPoints(p1, p2, p3, TEST_PRECISION);
    397
    398         // act/assert
    399         Assertions.assertTrue(planeA.contains(planeA));
    400         Assertions.assertTrue(planeA.contains(Planes.fromPoints(p1, p3, p2, TEST_PRECISION)));
    401         Assertions.assertTrue(planeA.contains(Planes.fromPoints(p3, p1, p2, TEST_PRECISION)));
    402         Assertions.assertTrue(planeA.contains(Planes.fromPoints(p3, p2, p1, TEST_PRECISION)));
    403
    404         Assertions.assertFalse(planeA.contains(Planes.fromPoints(p1, Vector3D.of(11.4, -3.8, 5.1), p2, TEST_PRECISION)));
    405
    406         final Vector3D offset = planeA.getNormal().multiply(1e-8);
    407         Assertions.assertFalse(planeA.contains(Planes.fromPoints(p1.add(offset), p2, p3, TEST_PRECISION)));
    408         Assertions.assertFalse(planeA.contains(Planes.fromPoints(p1, p2.add(offset), p3, TEST_PRECISION)));
    409         Assertions.assertFalse(planeA.contains(Planes.fromPoints(p1, p2, p3.add(offset), TEST_PRECISION)));
    410
    411         Assertions.assertFalse(planeA.contains(Planes.fromPoints(p1.add(offset),
    412                 p2.add(offset),
    413                 p3.add(offset), TEST_PRECISION)));
    414     }
    415
    416     @Test
    417     void testReverse() {
    418         // arrange
    419         final Vector3D pt = Vector3D.of(0, 0, 1);
    420         final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    421
    422         // act
    423         final Plane reversed = plane.reverse();
    424
    425         // assert
    426         checkPlane(reversed, pt, Vector3D.Unit.MINUS_Z);
    427
    428         Assertions.assertTrue(reversed.contains(Vector3D.of(1, 1, 1)));
    429         Assertions.assertTrue(reversed.contains(Vector3D.of(-1, -1, 1)));
    430         Assertions.assertFalse(reversed.contains(Vector3D.ZERO));
    431
    432         Assertions.assertEquals(1.0, reversed.offset(Vector3D.ZERO), TEST_EPS);
    433     }
    434
    435     @Test
    436     void testIsParallelAndOffset_line() {
    437         // arrange
    438         final Plane plane = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0, 0, 1), TEST_PRECISION);
    439
    440         final Line3D parallelLine = Lines3D.fromPoints(Vector3D.of(1, 0, 2), Vector3D.of(2, 0, 2), TEST_PRECISION);
    441         final Line3D nonParallelLine = Lines3D.fromPoints(Vector3D.of(1, 0, 2), Vector3D.of(2, 0, 1), TEST_PRECISION);
    442         final Line3D containedLine = Lines3D.fromPoints(Vector3D.of(2, 0, 1), Vector3D.of(1, 0, 1), TEST_PRECISION);
    443
    444         // act
    445         Assertions.assertTrue(plane.isParallel(parallelLine));
    446         Assertions.assertEquals(1.0, plane.offset(parallelLine), TEST_EPS);
    447
    448         Assertions.assertFalse(plane.isParallel(nonParallelLine));
    449         Assertions.assertEquals(0.0, plane.offset(nonParallelLine), TEST_EPS);
    450
    451         Assertions.assertTrue(plane.isParallel(containedLine));
    452         Assertions.assertEquals(0.0, plane.offset(containedLine), TEST_EPS);
    453     }
    454
    455     @Test
    456     void testIsParallelAndOffset_plane() {
    457         // arrange
    458         final Plane plane = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0, 0, 1), TEST_PRECISION);
    459         final Plane parallelPlane = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0, 0, 1), TEST_PRECISION);
    460         final Plane parallelPlane2 = Planes.fromPointAndNormal(Vector3D.of(0, 0, 2), Vector3D.of(0, 0, 1), TEST_PRECISION);
    461         final Plane parallelPlane3 = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.of(0, 0, 1), TEST_PRECISION).reverse();
    462         final Plane nonParallelPlane = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1),
    463                 Vector3D.of(1, 1.5, 1).cross(Vector3D.of(0, 1, 1)), TEST_PRECISION);
    464         final Plane reversedPlane = plane.reverse();
    465
    466         // act/assert
    467         Assertions.assertTrue(plane.isParallel(parallelPlane));
    468         Assertions.assertEquals(0.0, plane.offset(parallelPlane), TEST_EPS);
    469
    470         Assertions.assertTrue(plane.isParallel(parallelPlane2));
    471         Assertions.assertEquals(1.0, plane.offset(parallelPlane2), TEST_EPS);
    472
    473         Assertions.assertTrue(plane.isParallel(parallelPlane3));
    474         Assertions.assertEquals(-1.0, plane.offset(parallelPlane3), TEST_EPS);
    475
    476         Assertions.assertFalse(plane.isParallel(nonParallelPlane));
    477         Assertions.assertEquals(0.0, plane.offset(nonParallelPlane), TEST_EPS);
    478
    479         Assertions.assertTrue(plane.isParallel(reversedPlane));
    480         Assertions.assertEquals(0.0, plane.offset(nonParallelPlane), TEST_EPS);
    481     }
    482
    483     @Test
    484     void testOffset_point() {
    485         // arrange
    486         final Vector3D p1 = Vector3D.of(1, 1, 1);
    487         final Plane plane = Planes.fromPointAndNormal(p1, Vector3D.of(0.2, 0, 0), TEST_PRECISION);
    488
    489         // act/assert
    490         Assertions.assertEquals(-5.0, plane.offset(Vector3D.of(-4, 0, 0)), TEST_EPS);
    491         Assertions.assertEquals(+5.0, plane.offset(Vector3D.of(6, 10, -12)), TEST_EPS);
    492         Assertions.assertEquals(0.3,
    493                             plane.offset(Vector3D.Sum.of(p1).addScaled(0.3, plane.getNormal()).get()),
    494                             TEST_EPS);
    495         Assertions.assertEquals(-0.3,
    496                             plane.offset(Vector3D.Sum.of(p1).addScaled(-0.3, plane.getNormal()).get()),
    497                             TEST_EPS);
    498     }
    499
    500     @Test
    501     void testProject_point() {
    502         // arrange
    503         final Vector3D pt = Vector3D.of(-3, -2, -1);
    504         EuclideanTestUtils.permuteSkipZero(-4, 4, 1, (nx, ny, nz) -> {
    505
    506             final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.of(nx, ny, nz), TEST_PRECISION);
    507             EuclideanTestUtils.permute(-4, 4, 1, (x, y, z) -> {
    508
    509                 final Vector3D p = Vector3D.of(x, y, z);
    510
    511                 // act
    512                 final Vector3D proj = plane.project(p);
    513
    514                 // assert
    515                 Assertions.assertTrue(plane.contains(proj));
    516                 Assertions.assertEquals(0, plane.getOrigin().vectorTo(proj).dot(plane.getNormal()), TEST_EPS);
    517             });
    518         });
    519     }
    520
    521     @Test
    522     void testProject_line() {
    523         // arrange
    524         final Plane plane = Planes.fromPointAndNormal(Vector3D.Unit.PLUS_Z, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    525         final Line3D line = Lines3D.fromPoints(Vector3D.of(1, 0, 1), Vector3D.of(2, 0, 2), TEST_PRECISION);
    526
    527         // act
    528         final Line3D projected = plane.project(line);
    529
    530         // assert
    531         final Line3D expectedProjection = Lines3D.fromPoints(Vector3D.of(1, 0, 1), Vector3D.of(2, 0, 1), TEST_PRECISION);
    532         Assertions.assertEquals(expectedProjection, projected);
    533
    534         Assertions.assertTrue(plane.contains(projected));
    535
    536         Assertions.assertTrue(projected.contains(Vector3D.of(1, 0, 1)));
    537         Assertions.assertTrue(projected.contains(Vector3D.of(2, 0, 1)));
    538     }
    539
    540     @Test
    541     void testTransform_rotationAroundPoint() {
    542         // arrange
    543         final Vector3D pt = Vector3D.of(0, 0, 1);
    544         final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    545
    546         final AffineTransformMatrix3D mat = AffineTransformMatrix3D.createRotation(pt,
    547                 QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, Angle.PI_OVER_TWO));
    548
    549         // act
    550         final Plane result = plane.transform(mat);
    551
    552         // assert
    553         checkPlane(result, Vector3D.ZERO, Vector3D.Unit.PLUS_X);
    554     }
    555
    556     @Test
    557     void testTransform_asymmetricScaling() {
    558         // arrange
    559         final Vector3D pt = Vector3D.of(0, 1, 0);
    560         final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.of(1, 1, 0), TEST_PRECISION);
    561
    562         final AffineTransformMatrix3D t = AffineTransformMatrix3D.createScale(2, 1, 1);
    563
    564         // act
    565         final Plane result = plane.transform(t);
    566
    567         // assert
    568         final Vector3D expectedNormal = Vector3D.Unit.from(1, 2, 0);
    569         final Vector3D expectedOrigin = result.project(Vector3D.ZERO);
    570
    571         checkPlane(result, expectedOrigin, expectedNormal);
    572
    573         final Vector3D transformedPt = t.apply(Vector3D.of(0.5, 0.5, 1));
    574         Assertions.assertTrue(result.contains(transformedPt));
    575         Assertions.assertFalse(plane.contains(transformedPt));
    576     }
    577
    578     @Test
    579     void testTransform_negateOneComponent() {
    580         // arrange
    581         final Vector3D pt = Vector3D.of(0, 0, 1);
    582         final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    583
    584         final AffineTransformMatrix3D transform = AffineTransformMatrix3D.createScale(-1, 1,  1);
    585
    586         // act
    587         final Plane result = plane.transform(transform);
    588
    589         // assert
    590         checkPlane(result, Vector3D.of(0, 0, 1), Vector3D.Unit.MINUS_Z);
    591
    592         Assertions.assertFalse(transform.preservesOrientation());
    593         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.PLUS_Z,
    594                 transform.normalTransform().apply(plane.getNormal()), TEST_EPS);
    595     }
    596
    597     @Test
    598     void testTransform_negateTwoComponents() {
    599         // arrange
    600         final Vector3D pt = Vector3D.of(0, 0, 1);
    601         final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    602
    603         final AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(v -> Vector3D.of(-v.getX(), -v.getY(), v.getZ()));
    604
    605         // act
    606         final Plane result = plane.transform(transform);
    607
    608         // assert
    609         checkPlane(result, Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_Z);
    610     }
    611
    612     @Test
    613     void testTransform_negateAllComponents() {
    614         // arrange
    615         final Vector3D pt = Vector3D.of(0, 0, 1);
    616         final Plane plane = Planes.fromPointAndNormal(pt, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    617
    618         final AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(Vector3D::negate);
    619
    620         // act
    621         final Plane result = plane.transform(transform);
    622
    623         // assert
    624         checkPlane(result, Vector3D.of(0, 0, -1), Vector3D.Unit.PLUS_Z);
    625     }
    626
    627     @Test
    628     void testTransform_consistency() {
    629         // arrange
    630         final Vector3D pt = Vector3D.of(1, 2, 3);
    631         final Vector3D normal = Vector3D.Unit.of(1, 1, 1);
    632
    633         final Plane plane = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
    634
    635         final Vector3D p1 = plane.project(Vector3D.of(4, 5, 6));
    636         final Vector3D p2 = plane.project(Vector3D.of(-7, -8, -9));
    637         final Vector3D p3 = plane.project(Vector3D.of(10, -11, 12));
    638
    639         final Vector3D notOnPlane1 = plane.getOrigin().add(plane.getNormal());
    640         final Vector3D notOnPlane2 = plane.getOrigin().subtract(plane.getNormal());
    641
    642         EuclideanTestUtils.permuteSkipZero(-4, 4, 1, (a, b, c) -> {
    643             final AffineTransformMatrix3D t = AffineTransformMatrix3D.identity()
    644                     .rotate(Vector3D.of(-1, 2, 3),
    645                             QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_X, 0.3 * a))
    646                     .scale(Math.max(a, 1), Math.max(b, 1), Math.max(c, 1))
    647                     .translate(c, b, a);
    648
    649             // act
    650             final Plane result = plane.transform(t);
    651
    652             // assert
    653             Vector3D expectedNormal = t.normalTransform().apply(plane.getNormal()).normalize();
    654             if (!t.preservesOrientation()) {
    655                 expectedNormal = expectedNormal.negate();
    656             }
    657
    658             EuclideanTestUtils.assertCoordinatesEqual(expectedNormal, result.getNormal(), TEST_EPS);
    659
    660             Assertions.assertTrue(result.contains(t.apply(p1)));
    661             Assertions.assertTrue(result.contains(t.apply(p2)));
    662             Assertions.assertTrue(result.contains(t.apply(p3)));
    663
    664             Assertions.assertFalse(result.contains(t.apply(notOnPlane1)));
    665             Assertions.assertFalse(result.contains(t.apply(notOnPlane2)));
    666         });
    667     }
    668
    669     @Test
    670     void testRotate() {
    671         // arrange
    672         final Vector3D p1 = Vector3D.of(1.2, 3.4, -5.8);
    673         final Vector3D p2 = Vector3D.of(3.4, -5.8, 1.2);
    674         final Vector3D p3 = Vector3D.of(-2.0, 4.3, 0.7);
    675         Plane plane  = Planes.fromPoints(p1, p2, p3, TEST_PRECISION);
    676         final Vector3D oldNormal = plane.getNormal();
    677
    678         // act/assert
    679         plane = plane.rotate(p2, QuaternionRotation.fromAxisAngle(p2.subtract(p1), 1.7));
    680         Assertions.assertTrue(plane.contains(p1));
    681         Assertions.assertTrue(plane.contains(p2));
    682         Assertions.assertFalse(plane.contains(p3));
    683
    684         plane = plane.rotate(p2, QuaternionRotation.fromAxisAngle(oldNormal, 0.1));
    685         Assertions.assertFalse(plane.contains(p1));
    686         Assertions.assertTrue(plane.contains(p2));
    687         Assertions.assertFalse(plane.contains(p3));
    688
    689         plane = plane.rotate(p1, QuaternionRotation.fromAxisAngle(oldNormal, 0.1));
    690         Assertions.assertFalse(plane.contains(p1));
    691         Assertions.assertFalse(plane.contains(p2));
    692         Assertions.assertFalse(plane.contains(p3));
    693     }
    694
    695     @Test
    696     void testTranslate() {
    697         // arrange
    698         final Vector3D p1 = Vector3D.of(1.2, 3.4, -5.8);
    699         final Vector3D p2 = Vector3D.of(3.4, -5.8, 1.2);
    700         final Vector3D p3 = Vector3D.of(-2.0, 4.3, 0.7);
    701         Plane plane  = Planes.fromPoints(p1, p2, p3, TEST_PRECISION);
    702
    703         // act/assert
    704         plane = plane.translate(plane.getNormal().orthogonal().multiply(2.0));
    705         Assertions.assertTrue(plane.contains(p1));
    706         Assertions.assertTrue(plane.contains(p2));
    707         Assertions.assertTrue(plane.contains(p3));
    708
    709         plane = plane.translate(plane.getNormal().multiply(-1.2));
    710         Assertions.assertFalse(plane.contains(p1));
    711         Assertions.assertFalse(plane.contains(p2));
    712         Assertions.assertFalse(plane.contains(p3));
    713
    714         plane = plane.translate(plane.getNormal().multiply(+1.2));
    715         Assertions.assertTrue(plane.contains(p1));
    716         Assertions.assertTrue(plane.contains(p2));
    717         Assertions.assertTrue(plane.contains(p3));
    718     }
    719
    720     @Test
    721     void testIntersection_withLine() {
    722         // arrange
    723         final Plane plane = Planes.fromPointAndNormal(Vector3D.of(1, 2, 3), Vector3D.of(-4, 1, -5), TEST_PRECISION);
    724         final Line3D line = Lines3D.fromPoints(Vector3D.of(0.2, -3.5, 0.7), Vector3D.of(1.2, -2.5, -0.3), TEST_PRECISION);
    725
    726         // act
    727         final Vector3D point = plane.intersection(line);
    728
    729         // assert
    730         Assertions.assertTrue(plane.contains(point));
    731         Assertions.assertTrue(line.contains(point));
    732         Assertions.assertNull(plane.intersection(Lines3D.fromPoints(Vector3D.of(10, 10, 10),
    733                                                   Vector3D.of(10, 10, 10).add(plane.getNormal().orthogonal()),
    734                                                   TEST_PRECISION)));
    735     }
    736
    737     @Test
    738     void testIntersection_withLine_noIntersection() {
    739         // arrange
    740         final Vector3D pt = Vector3D.of(1, 2, 3);
    741         final Vector3D normal = Vector3D.of(-4, 1, -5);
    742
    743         final Plane plane = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
    744         final Vector3D u = plane.getNormal().orthogonal();
    745         final Vector3D v = plane.getNormal().cross(u);
    746
    747         // act/assert
    748         Assertions.assertNull(plane.intersection(Lines3D.fromPoints(pt, pt.add(u), TEST_PRECISION)));
    749
    750         final Vector3D offsetPt = pt.add(plane.getNormal());
    751         Assertions.assertNull(plane.intersection(Lines3D.fromPoints(offsetPt, offsetPt.add(v), TEST_PRECISION)));
    752     }
    753
    754     @Test
    755     void testIntersection_withPlane() {
    756         // arrange
    757         final Vector3D p1 = Vector3D.of(1.2, 3.4, -5.8);
    758         final Vector3D p2 = Vector3D.of(3.4, -5.8, 1.2);
    759         final Plane planeA = Planes.fromPoints(p1, p2, Vector3D.of(-2.0, 4.3, 0.7), TEST_PRECISION);
    760         final Plane planeB = Planes.fromPoints(p1, Vector3D.of(11.4, -3.8, 5.1), p2, TEST_PRECISION);
    761
    762         // act
    763         final Line3D line = planeA.intersection(planeB);
    764
    765         // assert
    766         Assertions.assertTrue(line.contains(p1));
    767         Assertions.assertTrue(line.contains(p2));
    768         EuclideanTestUtils.assertCoordinatesEqual(planeA.getNormal().cross(planeB.getNormal()).normalize(),
    769                 line.getDirection(), TEST_EPS);
    770
    771         Assertions.assertNull(planeA.intersection(planeA));
    772     }
    773
    774     @Test
    775     void testIntersection_withPlane_noIntersection() {
    776         // arrange
    777         final Plane plane = Planes.fromPointAndNormal(Vector3D.Unit.PLUS_Z, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    778
    779         // act/assert
    780         Assertions.assertNull(plane.intersection(plane));
    781         Assertions.assertNull(plane.intersection(plane.reverse()));
    782
    783         Assertions.assertNull(plane.intersection(Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION)));
    784         Assertions.assertNull(plane.intersection(Planes.fromPointAndNormal(Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z, TEST_PRECISION)));
    785     }
    786
    787     @Test
    788     void testIntersection_threePlanes() {
    789         // arrange
    790         final Vector3D pt = Vector3D.of(1.2, 3.4, -5.8);
    791         final Plane a = Planes.fromPointAndNormal(pt, Vector3D.of(1, 3, 3), TEST_PRECISION);
    792         final Plane b = Planes.fromPointAndNormal(pt, Vector3D.of(-2, 4, 0), TEST_PRECISION);
    793         final Plane c = Planes.fromPointAndNormal(pt, Vector3D.of(7, 0, -4), TEST_PRECISION);
    794
    795         // act
    796         final Vector3D result = Plane.intersection(a, b, c);
    797
    798         // assert
    799         EuclideanTestUtils.assertCoordinatesEqual(pt, result, TEST_EPS);
    800     }
    801
    802     @Test
    803     void testIntersection_threePlanes_intersectInLine() {
    804         // arrange
    805         final Plane a = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.of(1, 0, 0), TEST_PRECISION);
    806         final Plane b = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.of(1, 0.5, 0), TEST_PRECISION);
    807         final Plane c = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.of(1, 1, 0), TEST_PRECISION);
    808
    809         // act
    810         final Vector3D result = Plane.intersection(a, b, c);
    811
    812         // assert
    813         Assertions.assertNull(result);
    814     }
    815
    816     @Test
    817     void testIntersection_threePlanes_twoParallel() {
    818         // arrange
    819         final Plane a = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    820         final Plane b = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION);
    821         final Plane c = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    822
    823         // act
    824         final Vector3D result = Plane.intersection(a, b, c);
    825
    826         // assert
    827         Assertions.assertNull(result);
    828     }
    829
    830     @Test
    831     void testIntersection_threePlanes_allParallel() {
    832         // arrange
    833         final Plane a = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    834         final Plane b = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    835         final Plane c = Planes.fromPointAndNormal(Vector3D.of(0, 0, 2), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    836
    837         // act
    838         final Vector3D result = Plane.intersection(a, b, c);
    839
    840         // assert
    841         Assertions.assertNull(result);
    842     }
    843
    844     @Test
    845     void testIntersection_threePlanes_coincidentPlanes() {
    846         // arrange
    847         final Plane a = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    848         final Plane b = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    849         final Plane c = b.reverse();
    850
    851         // act
    852         final Vector3D result = Plane.intersection(a, b, c);
    853
    854         // assert
    855         Assertions.assertNull(result);
    856     }
    857
    858     @Test
    859     void testSpan() {
    860         // arrange
    861         final Plane plane = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    862
    863         // act
    864         final PlaneConvexSubset sub = plane.span();
    865
    866         // assert
    867         Assertions.assertNotSame(plane, sub.getPlane());
    868         EuclideanTestUtils.assertCoordinatesEqual(plane.getOrigin(), sub.getPlane().getOrigin(), TEST_EPS);
    869         EuclideanTestUtils.assertCoordinatesEqual(plane.getNormal(), sub.getPlane().getNormal(), TEST_EPS);
    870
    871         Assertions.assertTrue(sub.isFull());
    872
    873         Assertions.assertTrue(sub.contains(Vector3D.ZERO));
    874         Assertions.assertTrue(sub.contains(Vector3D.of(1, 1, 0)));
    875
    876         Assertions.assertFalse(sub.contains(Vector3D.of(0, 0, 1)));
    877     }
    878
    879     @Test
    880     void testSimilarOrientation() {
    881         // arrange
    882         final Plane plane = Planes.fromNormal(Vector3D.of(1, 0, 0), TEST_PRECISION);
    883
    884         // act/assert
    885         Assertions.assertTrue(plane.similarOrientation(plane));
    886         Assertions.assertTrue(plane.similarOrientation(Planes.fromNormal(Vector3D.of(1, 1, 0), TEST_PRECISION)));
    887         Assertions.assertTrue(plane.similarOrientation(Planes.fromNormal(Vector3D.of(1, -1, 0), TEST_PRECISION)));
    888
    889         Assertions.assertFalse(plane.similarOrientation(Planes.fromNormal(Vector3D.of(0, 1, 0), TEST_PRECISION)));
    890         Assertions.assertFalse(plane.similarOrientation(Planes.fromNormal(Vector3D.of(-1, 1, 0), TEST_PRECISION)));
    891         Assertions.assertFalse(plane.similarOrientation(Planes.fromNormal(Vector3D.of(-1, 1, 0), TEST_PRECISION)));
    892         Assertions.assertFalse(plane.similarOrientation(Planes.fromNormal(Vector3D.of(0, -1, 0), TEST_PRECISION)));
    893     }
    894
    895     @Test
    896     void testEq() {
    897         // arrange
    898         final double eps = 1e-3;
    899         final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(eps);
    900
    901         final Vector3D pt = Vector3D.of(1, 2, 3);
    902         final Vector3D normal = Vector3D.Unit.PLUS_X;
    903
    904         final Vector3D ptPrime = Vector3D.of(1.0001, 2.0001, 3.0001);
    905         final Vector3D normalPrime = Vector3D.Unit.of(1, 1e-4, 0);
    906
    907         final Plane a = Planes.fromPointAndNormal(pt, normal, precision);
    908
    909         final Plane b = Planes.fromPointAndNormal(Vector3D.of(2, 2, 3), normal, precision);
    910         final Plane c = Planes.fromPointAndNormal(pt, Vector3D.Unit.MINUS_X, precision);
    911         final Plane d = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
    912
    913         final Plane e = Planes.fromPointAndNormal(ptPrime, normalPrime, Precision.doubleEquivalenceOfEpsilon(eps));
    914
    915         // act/assert
    916         Assertions.assertTrue(a.eq(a, precision));
    917
    918         Assertions.assertFalse(a.eq(b, precision));
    919         Assertions.assertFalse(a.eq(c, precision));
    920
    921         Assertions.assertTrue(a.eq(d, precision));
    922         Assertions.assertTrue(a.eq(e, precision));
    923         Assertions.assertTrue(e.eq(a, precision));
    924     }
    925
    926     @Test
    927     void testHashCode() {
    928         // arrange
    929         final Vector3D pt = Vector3D.of(1, 2, 3);
    930         final Vector3D normal = Vector3D.Unit.PLUS_X;
    931
    932         final Plane a = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
    933         final Plane b = Planes.fromPointAndNormal(Vector3D.of(2, 2, 3), normal, TEST_PRECISION);
    934         final Plane c = Planes.fromPointAndNormal(pt, Vector3D.of(1, 1, 0), TEST_PRECISION);
    935         final Plane d = Planes.fromPointAndNormal(pt, normal, Precision.doubleEquivalenceOfEpsilon(1e-8));
    936         final Plane e = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
    937
    938         // act/assert
    939         final int hash = a.hashCode();
    940
    941         Assertions.assertEquals(hash, a.hashCode());
    942
    943         Assertions.assertNotEquals(hash, b.hashCode());
    944         Assertions.assertNotEquals(hash, c.hashCode());
    945         Assertions.assertNotEquals(hash, d.hashCode());
    946
    947         Assertions.assertEquals(hash, e.hashCode());
    948     }
    949
    950     @Test
    951     void testEquals() {
    952         // arrange
    953         final Vector3D pt = Vector3D.of(1, 2, 3);
    954         final Vector3D normal = Vector3D.Unit.PLUS_X;
    955
    956         final Plane a = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
    957         final Plane b = Planes.fromPointAndNormal(Vector3D.of(2, 2, 3), normal, TEST_PRECISION);
    958         final Plane c = Planes.fromPointAndNormal(pt, Vector3D.Unit.MINUS_X, TEST_PRECISION);
    959         final Plane d = Planes.fromPointAndNormal(pt, normal, Precision.doubleEquivalenceOfEpsilon(1e-8));
    960         final Plane e = Planes.fromPointAndNormal(pt, normal, TEST_PRECISION);
    961
    962         // act/assert
    963         GeometryTestUtils.assertSimpleEqualsCases(a);
    964
    965         Assertions.assertNotEquals(a, b);
    966         Assertions.assertNotEquals(a, c);
    967         Assertions.assertNotEquals(a, d);
    968
    969         Assertions.assertEquals(a, e);
    970         Assertions.assertEquals(e, a);
    971     }
    972
    973     @Test
    974     void testToString() {
    975         // arrange
    976         final Plane plane = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    977
    978         // act
    979         final String str = plane.toString();
    980
    981         // assert
    982         Assertions.assertTrue(str.startsWith("Plane["));
    983         Assertions.assertTrue(str.matches(".*origin= \\(0(\\.0)?, 0(\\.0)?\\, 0(\\.0)?\\).*"));
    984         Assertions.assertTrue(str.matches(".*normal= \\(0(\\.0)?, 0(\\.0)?\\, 1(\\.0)?\\).*"));
    985     }
    986
    987     private static void checkPlane(final Plane plane, final Vector3D origin, final Vector3D normal) {
    988         EuclideanTestUtils.assertCoordinatesEqual(origin, plane.getOrigin(), TEST_EPS);
    989         Assertions.assertTrue(plane.contains(origin));
    990
    991         EuclideanTestUtils.assertCoordinatesEqual(normal, plane.getNormal(), TEST_EPS);
    992         Assertions.assertEquals(1.0, plane.getNormal().norm(), TEST_EPS);
    993
    994         final double offset = plane.getOriginOffset();
    995         Assertions.assertEquals(Vector3D.ZERO.distance(plane.getOrigin()), Math.abs(offset), TEST_EPS);
    996         EuclideanTestUtils.assertCoordinatesEqual(origin, plane.getNormal().multiply(-offset), TEST_EPS);
    997     }
    998
    999     private static <T> List<T> rotate(final List<T> list, final int shift) {
    1000         final int size = list.size();
    1001
    1002         final List<T> result = new ArrayList<>(size);
    1003
    1004         for (int i = 0; i < size; ++i) {
    1005             result.add(list.get((i + shift) % size));
    1006         }
    1007
    1008         return result;
    1009     }
    1010 }