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.Arrays;
    20  import java.util.List;
    21
    22  import org.apache.commons.geometry.core.GeometryTestUtils;
    23  import org.apache.commons.geometry.core.RegionLocation;
    24  import org.apache.commons.geometry.core.partitioning.Split;
    25  import org.apache.commons.geometry.core.partitioning.SplitLocation;
    26  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
    27  import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
    28  import org.apache.commons.geometry.euclidean.twod.Vector2D;
    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 SimpleTriangle3DTest {
    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      private static final Plane XY_PLANE_Z1 = Planes.fromPointAndPlaneVectors(Vector3D.of(0, 0, 1),
    42              Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
    43
    44      @Test
    45      void testProperties() {
    46          // arrange
    47          final Vector3D p1 = Vector3D.of(1, 2, 1);
    48          final Vector3D p2 = Vector3D.of(2, 2, 1);
    49          final Vector3D p3 = Vector3D.of(2, 3, 1);
    50
    51          // act
    52          final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1, p1, p2, p3);
    53
    54          // assert
    55          Assertions.assertFalse(tri.isFull());
    56          Assertions.assertFalse(tri.isEmpty());
    57          Assertions.assertFalse(tri.isInfinite());
    58          Assertions.assertTrue(tri.isFinite());
    59
    60          Assertions.assertSame(XY_PLANE_Z1, tri.getPlane());
    61          Assertions.assertSame(p1, tri.getPoint1());
    62          Assertions.assertSame(p2, tri.getPoint2());
    63          Assertions.assertSame(p3, tri.getPoint3());
    64
    65          Assertions.assertEquals(Arrays.asList(p1, p2, p3), tri.getVertices());
    66
    67          final List<Vector2D> subspaceVertices = tri.getEmbedded().getSubspaceRegion().getVertices();
    68          Assertions.assertEquals(3, subspaceVertices.size());
    69          EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2), subspaceVertices.get(0), TEST_EPS);
    70          EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 2), subspaceVertices.get(1), TEST_EPS);
    71          EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 3), subspaceVertices.get(2), TEST_EPS);
    72
    73          Assertions.assertEquals(0.5, tri.getSize(), TEST_EPS);
    74          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(5.0 / 3.0, 7.0 / 3.0, 1), tri.getCentroid(), TEST_EPS);
    75
    76          final Bounds3D bounds = tri.getBounds();
    77          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 1), bounds.getMin(), TEST_EPS);
    78          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 3, 1), bounds.getMax(), TEST_EPS);
    79      }
    80
    81      @Test
    82      void testVertices_listIsImmutable() {
    83          // arrange
    84          final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
    85                  Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
    86
    87          // act/assert
    88          Assertions.assertThrows(UnsupportedOperationException.class, () -> tri.getVertices().add(Vector3D.of(-1, 0, 1)));
    89      }
    90
    91      @Test
    92      void testToTriangles() {
    93          // arrange
    94          final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
    95                  Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
    96
    97          // act
    98          final List<Triangle3D> triangles = tri.toTriangles();
    99
    100         // assert
    101         Assertions.assertEquals(1, triangles.size());
    102         Assertions.assertSame(tri, triangles.get(0));
    103     }
    104
    105     @Test
    106     void testGetSize() {
    107         // arrange
    108         final QuaternionRotation rot = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, 0.2);
    109
    110         // act/assert
    111         Assertions.assertEquals(0.5, new SimpleTriangle3D(XY_PLANE_Z1,
    112                 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1)).getSize(), TEST_EPS);
    113
    114         Assertions.assertEquals(1, new SimpleTriangle3D(XY_PLANE_Z1,
    115                 Vector3D.of(0, 0, 1), Vector3D.of(2, 0, 1), Vector3D.of(0, 1, 1)).getSize(), TEST_EPS);
    116
    117         Assertions.assertEquals(1.5, new SimpleTriangle3D(XY_PLANE_Z1,
    118                 Vector3D.of(1, 2, 1), Vector3D.of(4, 2, 1), Vector3D.of(2, 3, 1)).getSize(), TEST_EPS);
    119
    120         Assertions.assertEquals(1.5, new SimpleTriangle3D(XY_PLANE_Z1,
    121                 rot.applyVector(Vector3D.of(1, 2, 1)),
    122                 rot.apply(Vector3D.of(4, 2, 1)),
    123                 rot.applyVector(Vector3D.of(2, 3, 1))).getSize(), TEST_EPS);
    124     }
    125
    126     @Test
    127     void testClassify() {
    128         // arrange
    129         final Vector3D p1 = Vector3D.of(1, 2, 1);
    130         final Vector3D p2 = Vector3D.of(3, 2, 1);
    131         final Vector3D p3 = Vector3D.of(2, 3, 1);
    132
    133         final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1, p1, p2, p3);
    134
    135         // act/assert
    136         checkPoints(tri, RegionLocation.INSIDE, Vector3D.of(2, 2.5, 1), Vector3D.of(2, 2.5, 1 + 1e-15));
    137         checkPoints(tri, RegionLocation.BOUNDARY,
    138                 p1, p2, p3,
    139                 p1.lerp(p2, 0.5), p2.lerp(p3, 0.5), p3.lerp(p1,  0.5));
    140         checkPoints(tri, RegionLocation.OUTSIDE,
    141                 Vector3D.of(2, 2.5, 0), Vector3D.of(2, 2.5, 2),
    142                 Vector3D.of(0, 2, 1), Vector3D.of(4, 2, 1),
    143                 Vector3D.of(2, 4, 1), Vector3D.of(2, 1, 1));
    144     }
    145
    146     @Test
    147     void testClosest() {
    148         // arrange
    149         final Vector3D p1 = Vector3D.of(1, 2, 1);
    150         final Vector3D p2 = Vector3D.of(3, 2, 1);
    151         final Vector3D p3 = Vector3D.of(2, 3, 1);
    152
    153         final Vector3D centroid = Vector3D.centroid(p1, p2, p3);
    154
    155         final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1, p1, p2, p3);
    156
    157         // act/assert
    158         EuclideanTestUtils.assertCoordinatesEqual(centroid, tri.closest(centroid), TEST_EPS);
    159         EuclideanTestUtils.assertCoordinatesEqual(centroid, tri.closest(centroid.add(Vector3D.Unit.PLUS_Z)), TEST_EPS);
    160         EuclideanTestUtils.assertCoordinatesEqual(centroid, tri.closest(centroid.add(Vector3D.Unit.MINUS_Z)), TEST_EPS);
    161
    162         EuclideanTestUtils.assertCoordinatesEqual(p1, tri.closest(Vector3D.of(0, 2, 5)), TEST_EPS);
    163         EuclideanTestUtils.assertCoordinatesEqual(p1, tri.closest(Vector3D.of(1, 2, 5)), TEST_EPS);
    164         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 2, 1), tri.closest(Vector3D.of(2, 2, 5)), TEST_EPS);
    165         EuclideanTestUtils.assertCoordinatesEqual(p2, tri.closest(Vector3D.of(3, 2, 5)), TEST_EPS);
    166         EuclideanTestUtils.assertCoordinatesEqual(p2, tri.closest(Vector3D.of(4, 2, 5)), TEST_EPS);
    167
    168         EuclideanTestUtils.assertCoordinatesEqual(p1, tri.closest(Vector3D.of(0, 1, 5)), TEST_EPS);
    169         EuclideanTestUtils.assertCoordinatesEqual(p1, tri.closest(Vector3D.of(1, 1, 5)), TEST_EPS);
    170         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 2, 1), tri.closest(Vector3D.of(2, 1, 5)), TEST_EPS);
    171         EuclideanTestUtils.assertCoordinatesEqual(p2, tri.closest(Vector3D.of(3, 1, 5)), TEST_EPS);
    172         EuclideanTestUtils.assertCoordinatesEqual(p2, tri.closest(Vector3D.of(4, 1, 5)), TEST_EPS);
    173
    174         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1.5, 2.5, 1),
    175                 tri.closest(Vector3D.of(1, 3, -10)), TEST_EPS);
    176     }
    177
    178     @Test
    179     void testReverse() {
    180         // arrange
    181         final Vector3D p1 = Vector3D.of(1, 2, 1);
    182         final Vector3D p2 = Vector3D.of(3, 2, 1);
    183         final Vector3D p3 = Vector3D.of(2, 3, 1);
    184
    185         final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1, p1, p2, p3);
    186
    187         // act
    188         final SimpleTriangle3D result = tri.reverse();
    189
    190         // assert
    191         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_Z, result.getPlane().getNormal(), TEST_EPS);
    192
    193         Assertions.assertSame(p1, result.getPoint1());
    194         Assertions.assertSame(p3, result.getPoint2());
    195         Assertions.assertSame(p2, result.getPoint3());
    196
    197         final Vector3D v1 = result.getPoint1().vectorTo(result.getPoint2());
    198         final Vector3D v2 = result.getPoint1().vectorTo(result.getPoint3());
    199         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_Z, v1.cross(v2).normalize(), TEST_EPS);
    200
    201         Assertions.assertEquals(1, result.getSize(), TEST_EPS);
    202     }
    203
    204     @Test
    205     void testTransform() {
    206         // arrange
    207         final Vector3D p1 = Vector3D.of(1, 2, 1);
    208         final Vector3D p2 = Vector3D.of(3, 2, 1);
    209         final Vector3D p3 = Vector3D.of(2, 3, 1);
    210
    211         final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1, p1, p2, p3);
    212
    213         final AffineTransformMatrix3D t = AffineTransformMatrix3D.identity()
    214                 .rotate(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, -Angle.PI_OVER_TWO))
    215                 .scale(1, 1, 2)
    216                 .translate(Vector3D.of(1, 0, 0));
    217
    218         // act
    219         final SimpleTriangle3D result = tri.transform(t);
    220
    221         // assert
    222         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_X, result.getPlane().getNormal(), TEST_EPS);
    223
    224         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 2, 2), result.getPoint1(), TEST_EPS);
    225         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 2, 6), result.getPoint2(), TEST_EPS);
    226         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 3, 4), result.getPoint3(), TEST_EPS);
    227
    228         final Vector3D v1 = result.getPoint1().vectorTo(result.getPoint2());
    229         final Vector3D v2 = result.getPoint1().vectorTo(result.getPoint3());
    230         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_X, v1.cross(v2).normalize(), TEST_EPS);
    231
    232         Assertions.assertEquals(2, result.getSize(), TEST_EPS);
    233     }
    234
    235     @Test
    236     void testSplit_plus() {
    237         // arrange
    238         final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
    239                 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
    240
    241         final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION);
    242
    243         // act
    244         final Split<PlaneConvexSubset> split = tri.split(splitter);
    245
    246         // assert
    247         Assertions.assertEquals(SplitLocation.PLUS, split.getLocation());
    248
    249         Assertions.assertNull(split.getMinus());
    250         Assertions.assertSame(tri, split.getPlus());
    251     }
    252
    253     @Test
    254     void testSplit_minus() {
    255         // arrange
    256         final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
    257                 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
    258
    259         final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.MINUS_Z, TEST_PRECISION);
    260
    261         // act
    262         final Split<PlaneConvexSubset> split = tri.split(splitter);
    263
    264         // assert
    265         Assertions.assertEquals(SplitLocation.MINUS, split.getLocation());
    266
    267         Assertions.assertSame(tri, split.getMinus());
    268         Assertions.assertNull(split.getPlus());
    269     }
    270
    271     @Test
    272     void testSplit_both() {
    273         // arrange
    274         final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
    275                 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
    276
    277         final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.of(-1, 1, 0), TEST_PRECISION);
    278
    279         // act
    280         final Split<PlaneConvexSubset> split = tri.split(splitter);
    281
    282         // assert
    283         Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
    284
    285         final PlaneConvexSubset minus = split.getMinus();
    286         EuclideanTestUtils.assertVertexLoopSequence(
    287                 Arrays.asList(Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0.5, 0.5, 1)),
    288                 minus.getVertices(), TEST_PRECISION);
    289
    290         final PlaneConvexSubset plus = split.getPlus();
    291         EuclideanTestUtils.assertVertexLoopSequence(
    292                 Arrays.asList(Vector3D.of(0, 0, 1), Vector3D.of(0.5, 0.5, 1), Vector3D.of(0, 1, 1)),
    293                 plus.getVertices(), TEST_PRECISION);
    294     }
    295
    296     @Test
    297     void testSplit_neither() {
    298         // arrange
    299         final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
    300                 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
    301
    302         final Plane splitter = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0, 1e-15, -1), TEST_PRECISION);
    303
    304         // act
    305         final Split<PlaneConvexSubset> split = tri.split(splitter);
    306
    307         // assert
    308         Assertions.assertEquals(SplitLocation.NEITHER, split.getLocation());
    309
    310         Assertions.assertNull(split.getMinus());
    311         Assertions.assertNull(split.getPlus());
    312     }
    313
    314     @Test
    315     void testToString() {
    316         // arrange
    317         final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
    318                 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
    319
    320         // act
    321         final String str = tri.toString();
    322
    323         // assert
    324         GeometryTestUtils.assertContains("SimpleTriangle3D[normal= (", str);
    325         GeometryTestUtils.assertContains("vertices= [", str);
    326     }
    327
    328     private static void checkPoints(final ConvexPolygon3D ps, final RegionLocation loc, final Vector3D... pts) {
    329         for (final Vector3D pt : pts) {
    330             Assertions.assertEquals(loc, ps.classify(pt), "Unexpected location for point " + pt);
    331         }
    332     }
    333 }