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