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 }