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  import java.util.regex.Pattern;
    22  import java.util.stream.Collectors;
    23
    24  import org.apache.commons.geometry.core.GeometryTestUtils;
    25  import org.apache.commons.geometry.core.RegionLocation;
    26  import org.apache.commons.geometry.core.Transform;
    27  import org.apache.commons.geometry.core.partitioning.Split;
    28  import org.apache.commons.geometry.core.partitioning.SplitLocation;
    29  import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
    30  import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
    31  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
    32  import org.apache.commons.geometry.euclidean.threed.line.Lines3D;
    33  import org.apache.commons.geometry.euclidean.twod.ConvexArea;
    34  import org.junit.Assert;
    35  import org.junit.Test;
    36
    37  public class ConvexVolumeTest {
    38
    39      private static final double TEST_EPS = 1e-10;
    40
    41      private static final DoublePrecisionContext TEST_PRECISION =
    42              new EpsilonDoublePrecisionContext(TEST_EPS);
    43
    44      @Test
    45      public void testFull() {
    46          // act
    47          final ConvexVolume vol = ConvexVolume.full();
    48
    49          // assert
    50          Assert.assertTrue(vol.isFull());
    51          Assert.assertFalse(vol.isEmpty());
    52
    53          GeometryTestUtils.assertPositiveInfinity(vol.getSize());
    54          Assert.assertNull(vol.getCentroid());
    55
    56          Assert.assertEquals(0, vol.getBoundaries().size());
    57          Assert.assertEquals(0, vol.getBoundarySize(), TEST_EPS);
    58      }
    59
    60      @Test
    61      public void testBoundaryStream() {
    62          // arrange
    63          final Plane plane = Planes.fromNormal(Vector3D.Unit.PLUS_Z, TEST_PRECISION);
    64          final ConvexVolume volume = ConvexVolume.fromBounds(plane);
    65
    66          // act
    67          final List<PlaneConvexSubset> boundaries = volume.boundaryStream().collect(Collectors.toList());
    68
    69          // assert
    70          Assert.assertEquals(1, boundaries.size());
    71
    72          final PlaneConvexSubset sp = boundaries.get(0);
    73          Assert.assertEquals(0, sp.getEmbedded().getSubspaceRegion().getBoundaries().size());
    74          EuclideanTestUtils.assertCoordinatesEqual(plane.getOrigin(), sp.getPlane().getOrigin(), TEST_EPS);
    75          EuclideanTestUtils.assertCoordinatesEqual(plane.getNormal(), sp.getPlane().getNormal(), TEST_EPS);
    76      }
    77
    78      @Test
    79      public void testBoundaryStream_noBoundaries() {
    80          // arrange
    81          final ConvexVolume volume = ConvexVolume.full();
    82
    83          // act
    84          final List<PlaneConvexSubset> boundaries = volume.boundaryStream().collect(Collectors.toList());
    85
    86          // assert
    87          Assert.assertEquals(0, boundaries.size());
    88      }
    89
    90      @Test
    91      public void testTriangleStream_noBoundaries() {
    92          // arrange
    93          final ConvexVolume full = ConvexVolume.full();
    94
    95          // act
    96          final List<Triangle3D> tris = full.triangleStream().collect(Collectors.toList());
    97
    98          // act/assert
    99          Assert.assertEquals(0, tris.size());
    100     }
    101
    102     @Test
    103     public void testTriangleStream_infinite() {
    104         // arrange
    105         final Pattern pattern = Pattern.compile("^Cannot convert infinite plane subset to triangles: .*");
    106
    107         final ConvexVolume half = ConvexVolume.fromBounds(
    108                 Planes.fromNormal(Vector3D.Unit.MINUS_X, TEST_PRECISION)
    109             );
    110
    111         final ConvexVolume quadrant = ConvexVolume.fromBounds(
    112                     Planes.fromNormal(Vector3D.Unit.MINUS_X, TEST_PRECISION),
    113                     Planes.fromNormal(Vector3D.Unit.MINUS_Y, TEST_PRECISION),
    114                     Planes.fromNormal(Vector3D.Unit.MINUS_Z, TEST_PRECISION)
    115                 );
    116
    117         // act/assert
    118         GeometryTestUtils.assertThrows(() -> {
    119             half.triangleStream().collect(Collectors.toList());
    120         }, IllegalStateException.class, pattern);
    121
    122         GeometryTestUtils.assertThrows(() -> {
    123             quadrant.triangleStream().collect(Collectors.toList());
    124         }, IllegalStateException.class, pattern);
    125     }
    126
    127     @Test
    128     public void testTriangleStream_finite() {
    129         // arrange
    130         final Vector3D min = Vector3D.ZERO;
    131         final Vector3D max = Vector3D.of(1, 1, 1);
    132
    133         final ConvexVolume box = ConvexVolume.fromBounds(
    134                     Planes.fromPointAndNormal(min, Vector3D.Unit.MINUS_X, TEST_PRECISION),
    135                     Planes.fromPointAndNormal(min, Vector3D.Unit.MINUS_Y, TEST_PRECISION),
    136                     Planes.fromPointAndNormal(min, Vector3D.Unit.MINUS_Z, TEST_PRECISION),
    137
    138                     Planes.fromPointAndNormal(max, Vector3D.Unit.PLUS_X, TEST_PRECISION),
    139                     Planes.fromPointAndNormal(max, Vector3D.Unit.PLUS_Y, TEST_PRECISION),
    140                     Planes.fromPointAndNormal(max, Vector3D.Unit.PLUS_Z, TEST_PRECISION)
    141                 );
    142
    143         // act
    144         final List<Triangle3D> tris = box.triangleStream().collect(Collectors.toList());
    145
    146         // assert
    147         Assert.assertEquals(12, tris.size());
    148
    149         final Bounds3D.Builder boundsBuilder = Bounds3D.builder();
    150         tris.forEach(t -> boundsBuilder.addAll(t.getVertices()));
    151
    152         final Bounds3D bounds = boundsBuilder.build();
    153         EuclideanTestUtils.assertCoordinatesEqual(min, bounds.getMin(), TEST_EPS);
    154         EuclideanTestUtils.assertCoordinatesEqual(max, bounds.getMax(), TEST_EPS);
    155     }
    156
    157     @Test
    158     public void testGetBounds_noBounds() {
    159         // arrange
    160         final ConvexVolume full = ConvexVolume.full();
    161         final ConvexVolume halfFull = ConvexVolume.fromBounds(Planes.fromNormal(Vector3D.Unit.PLUS_Z, TEST_PRECISION));
    162
    163         // act/assert
    164         Assert.assertNull(full.getBounds());
    165         Assert.assertNull(halfFull.getBounds());
    166     }
    167
    168     @Test
    169     public void testGetBounds_hasBounds() {
    170         // arrange
    171         final ConvexVolume vol = rect(Vector3D.of(1, 1, 1), 0.5, 1, 2);
    172
    173         // act
    174         final Bounds3D bounds = vol.getBounds();
    175
    176         // assert
    177         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 0, -1), bounds.getMin(), TEST_EPS);
    178         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1.5, 2, 3), bounds.getMax(), TEST_EPS);
    179     }
    180
    181     @Test
    182     public void testToTree_full() {
    183         // arrange
    184         final ConvexVolume volume = ConvexVolume.full();
    185
    186         // act
    187         final RegionBSPTree3D tree = volume.toTree();
    188
    189         // assert
    190         Assert.assertTrue(tree.isFull());
    191         Assert.assertFalse(tree.isEmpty());
    192     }
    193
    194     @Test
    195     public void testToTree() {
    196         // arrange
    197         final ConvexVolume volume = ConvexVolume.fromBounds(
    198                     Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.MINUS_X, TEST_PRECISION),
    199                     Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.MINUS_Y, TEST_PRECISION),
    200                     Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.MINUS_Z, TEST_PRECISION),
    201
    202                     Planes.fromPointAndNormal(Vector3D.of(1, 1, 1), Vector3D.Unit.PLUS_X, TEST_PRECISION),
    203                     Planes.fromPointAndNormal(Vector3D.of(1, 1, 1), Vector3D.Unit.PLUS_Y, TEST_PRECISION),
    204                     Planes.fromPointAndNormal(Vector3D.of(1, 1, 1), Vector3D.Unit.PLUS_Z, TEST_PRECISION)
    205                 );
    206
    207         // act
    208         final RegionBSPTree3D tree = volume.toTree();
    209
    210         // assert
    211         Assert.assertEquals(1, tree.getSize(), TEST_EPS);
    212         Assert.assertEquals(6, tree.getBoundarySize(), TEST_EPS);
    213         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 0.5, 0.5), tree.getCentroid(), TEST_EPS);
    214
    215         EuclideanTestUtils.assertRegionLocation(tree, RegionLocation.OUTSIDE,
    216                 Vector3D.of(-1, 0.5, 0.5), Vector3D.of(2, 0.5, 0.5),
    217                 Vector3D.of(0.5, -1, 0.5), Vector3D.of(0.5, 2, 0.5),
    218                 Vector3D.of(0.5, 0.5, -1), Vector3D.of(0.5, 0.5, 2));
    219         EuclideanTestUtils.assertRegionLocation(tree, RegionLocation.BOUNDARY, Vector3D.ZERO);
    220         EuclideanTestUtils.assertRegionLocation(tree, RegionLocation.INSIDE, Vector3D.of(0.5, 0.5, 0.5));
    221     }
    222
    223     @Test
    224     public void testFromBounds_noPlanes() {
    225         // act
    226         final ConvexVolume vol = ConvexVolume.fromBounds();
    227
    228         // assert
    229         Assert.assertSame(ConvexVolume.full(), vol);
    230     }
    231
    232     @Test
    233     public void testFromBounds_halfspace() {
    234         // act
    235         final ConvexVolume vol = ConvexVolume.fromBounds(Planes.fromNormal(Vector3D.Unit.PLUS_Z, TEST_PRECISION));
    236
    237         // assert
    238         Assert.assertFalse(vol.isFull());
    239         Assert.assertFalse(vol.isEmpty());
    240
    241         GeometryTestUtils.assertPositiveInfinity(vol.getSize());
    242         Assert.assertNull(vol.getCentroid());
    243
    244         Assert.assertEquals(1, vol.getBoundaries().size());
    245         GeometryTestUtils.assertPositiveInfinity(vol.getBoundarySize());
    246
    247         EuclideanTestUtils.assertRegionLocation(vol, RegionLocation.OUTSIDE, Vector3D.of(0, 0, 1));
    248         EuclideanTestUtils.assertRegionLocation(vol, RegionLocation.BOUNDARY, Vector3D.of(0, 0, 0));
    249         EuclideanTestUtils.assertRegionLocation(vol, RegionLocation.INSIDE, Vector3D.of(0, 0, -1));
    250     }
    251
    252     @Test
    253     public void testFromBounds_cube() {
    254         // act
    255         final ConvexVolume vol = rect(Vector3D.of(1, 1, 1), 0.5, 1, 2);
    256
    257         // assert
    258         Assert.assertFalse(vol.isFull());
    259         Assert.assertFalse(vol.isEmpty());
    260
    261         Assert.assertEquals(8, vol.getSize(), TEST_EPS);
    262         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 1), vol.getCentroid(), TEST_EPS);
    263
    264         Assert.assertEquals(6, vol.getBoundaries().size());
    265         Assert.assertEquals(28, vol.getBoundarySize(), TEST_EPS);
    266
    267         EuclideanTestUtils.assertRegionLocation(vol, RegionLocation.INSIDE, Vector3D.of(1, 1, 1));
    268
    269         EuclideanTestUtils.assertRegionLocation(vol, RegionLocation.BOUNDARY,
    270                 Vector3D.of(0.5, 0, -1), Vector3D.of(1.5, 2, 3));
    271
    272         EuclideanTestUtils.assertRegionLocation(vol, RegionLocation.OUTSIDE,
    273                 Vector3D.of(0, 1, 1), Vector3D.of(2, 1, 1),
    274                 Vector3D.of(1, -1, 1), Vector3D.of(1, 3, 1),
    275                 Vector3D.of(1, 1, -2), Vector3D.of(1, 1, 4));
    276     }
    277
    278     @Test
    279     public void testTrim() {
    280         // arrange
    281         final ConvexVolume vol = rect(Vector3D.ZERO, 0.5, 0.5, 0.5);
    282
    283         final PlaneConvexSubset subplane = Planes.subsetFromConvexArea(
    284                 Planes.fromNormal(Vector3D.Unit.PLUS_X, TEST_PRECISION).getEmbedding(), ConvexArea.full());
    285
    286         // act
    287         final PlaneConvexSubset trimmed = vol.trim(subplane);
    288
    289         // assert
    290         Assert.assertEquals(1, trimmed.getSize(), TEST_EPS);
    291
    292         final List<Vector3D> vertices = trimmed.getVertices();
    293
    294         Assert.assertEquals(4, vertices.size());
    295         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0.5, -0.5), vertices.get(0), TEST_EPS);
    296         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0.5, 0.5), vertices.get(1), TEST_EPS);
    297         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, -0.5, 0.5), vertices.get(2), TEST_EPS);
    298         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, -0.5, -0.5), vertices.get(3), TEST_EPS);
    299     }
    300
    301     @Test
    302     public void testSplit() {
    303         // arrange
    304         final ConvexVolume vol = rect(Vector3D.ZERO, 0.5, 0.5, 0.5);
    305
    306         final Plane splitter = Planes.fromNormal(Vector3D.Unit.PLUS_X, TEST_PRECISION);
    307
    308         // act
    309         final Split<ConvexVolume> split = vol.split(splitter);
    310
    311         // assert
    312         Assert.assertEquals(SplitLocation.BOTH, split.getLocation());
    313
    314         final ConvexVolume minus = split.getMinus();
    315         Assert.assertEquals(0.5, minus.getSize(), TEST_EPS);
    316         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-0.25, 0, 0), minus.getCentroid(), TEST_EPS);
    317
    318         final ConvexVolume plus = split.getPlus();
    319         Assert.assertEquals(0.5, plus.getSize(), TEST_EPS);
    320         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.25, 0, 0), plus.getCentroid(), TEST_EPS);
    321     }
    322
    323     @Test
    324     public void testLinecast_full() {
    325         // arrange
    326         final ConvexVolume volume = ConvexVolume.full();
    327
    328         // act/assert
    329         LinecastChecker3D.with(volume)
    330             .expectNothing()
    331             .whenGiven(Lines3D.fromPoints(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION));
    332
    333         LinecastChecker3D.with(volume)
    334             .expectNothing()
    335             .whenGiven(Lines3D.segmentFromPoints(Vector3D.Unit.MINUS_X, Vector3D.Unit.PLUS_X, TEST_PRECISION));
    336     }
    337
    338     @Test
    339     public void testLinecast() {
    340         // arrange
    341         final ConvexVolume volume = rect(Vector3D.of(0.5, 0.5, 0.5), 0.5, 0.5, 0.5);
    342
    343         // act/assert
    344         LinecastChecker3D.with(volume)
    345             .expectNothing()
    346             .whenGiven(Lines3D.fromPoints(Vector3D.of(0, 5, 5), Vector3D.of(1, 5, 5), TEST_PRECISION));
    347
    348         LinecastChecker3D.with(volume)
    349             .expect(Vector3D.ZERO, Vector3D.Unit.MINUS_X)
    350             .and(Vector3D.ZERO, Vector3D.Unit.MINUS_Y)
    351             .and(Vector3D.ZERO, Vector3D.Unit.MINUS_Z)
    352             .and(Vector3D.of(1, 1, 1), Vector3D.Unit.PLUS_Z)
    353             .and(Vector3D.of(1, 1, 1), Vector3D.Unit.PLUS_Y)
    354             .and(Vector3D.of(1, 1, 1), Vector3D.Unit.PLUS_X)
    355             .whenGiven(Lines3D.fromPoints(Vector3D.ZERO, Vector3D.of(1, 1, 1), TEST_PRECISION));
    356
    357         LinecastChecker3D.with(volume)
    358             .expect(Vector3D.of(1, 1, 1), Vector3D.Unit.PLUS_Z)
    359             .and(Vector3D.of(1, 1, 1), Vector3D.Unit.PLUS_Y)
    360             .and(Vector3D.of(1, 1, 1), Vector3D.Unit.PLUS_X)
    361             .whenGiven(Lines3D.segmentFromPoints(Vector3D.of(0.5, 0.5, 0.5), Vector3D.of(1, 1, 1), TEST_PRECISION));
    362     }
    363
    364     @Test
    365     public void testTransform() {
    366         // arrange
    367         final ConvexVolume vol = rect(Vector3D.ZERO, 0.5, 0.5, 0.5);
    368
    369         final Transform<Vector3D> transform = AffineTransformMatrix3D.identity()
    370                 .translate(Vector3D.of(1, 2, 3))
    371                 .scale(Vector3D.of(2, 1, 1));
    372
    373         // act
    374         final ConvexVolume transformed = vol.transform(transform);
    375
    376         // assert
    377         Assert.assertEquals(2, transformed.getSize(), TEST_EPS);
    378         Assert.assertEquals(10, transformed.getBoundarySize(), TEST_EPS);
    379
    380         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 2, 3), transformed.getCentroid(), TEST_EPS);
    381     }
    382
    383     private static ConvexVolume rect(final Vector3D center, final double xDelta, final double yDelta, final double zDelta) {
    384         final List<Plane> planes = Arrays.asList(
    385                     Planes.fromPointAndNormal(center.add(Vector3D.of(xDelta, 0, 0)), Vector3D.Unit.PLUS_X, TEST_PRECISION),
    386                     Planes.fromPointAndNormal(center.add(Vector3D.of(-xDelta, 0, 0)), Vector3D.Unit.MINUS_X, TEST_PRECISION),
    387
    388                     Planes.fromPointAndNormal(center.add(Vector3D.of(0, yDelta, 0)), Vector3D.Unit.PLUS_Y, TEST_PRECISION),
    389                     Planes.fromPointAndNormal(center.add(Vector3D.of(0, -yDelta, 0)), Vector3D.Unit.MINUS_Y, TEST_PRECISION),
    390
    391                     Planes.fromPointAndNormal(center.add(Vector3D.of(0, 0, zDelta)), Vector3D.Unit.PLUS_Z, TEST_PRECISION),
    392                     Planes.fromPointAndNormal(center.add(Vector3D.of(0, 0, -zDelta)), Vector3D.Unit.MINUS_Z, TEST_PRECISION)
    393                 );
    394
    395         return ConvexVolume.fromBounds(planes);
    396     }
    397 }