1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
47 final ConvexVolume vol = ConvexVolume.full();
48
49
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
63 final Plane plane = Planes.fromNormal(Vector3D.Unit.PLUS_Z, TEST_PRECISION);
64 final ConvexVolume volume = ConvexVolume.fromBounds(plane);
65
66
67 final List<PlaneConvexSubset> boundaries = volume.boundaryStream().collect(Collectors.toList());
68
69
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
81 final ConvexVolume volume = ConvexVolume.full();
82
83
84 final List<PlaneConvexSubset> boundaries = volume.boundaryStream().collect(Collectors.toList());
85
86
87 Assert.assertEquals(0, boundaries.size());
88 }
89
90 @Test
91 public void testTriangleStream_noBoundaries() {
92
93 final ConvexVolume full = ConvexVolume.full();
94
95
96 final List<Triangle3D> tris = full.triangleStream().collect(Collectors.toList());
97
98
99 Assert.assertEquals(0, tris.size());
100 }
101
102 @Test
103 public void testTriangleStream_infinite() {
104
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
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
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
144 final List<Triangle3D> tris = box.triangleStream().collect(Collectors.toList());
145
146
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
160 final ConvexVolume full = ConvexVolume.full();
161 final ConvexVolume halfFull = ConvexVolume.fromBounds(Planes.fromNormal(Vector3D.Unit.PLUS_Z, TEST_PRECISION));
162
163
164 Assert.assertNull(full.getBounds());
165 Assert.assertNull(halfFull.getBounds());
166 }
167
168 @Test
169 public void testGetBounds_hasBounds() {
170
171 final ConvexVolume vol = rect(Vector3D.of(1, 1, 1), 0.5, 1, 2);
172
173
174 final Bounds3D bounds = vol.getBounds();
175
176
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
184 final ConvexVolume volume = ConvexVolume.full();
185
186
187 final RegionBSPTree3D tree = volume.toTree();
188
189
190 Assert.assertTrue(tree.isFull());
191 Assert.assertFalse(tree.isEmpty());
192 }
193
194 @Test
195 public void testToTree() {
196
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
208 final RegionBSPTree3D tree = volume.toTree();
209
210
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
226 final ConvexVolume vol = ConvexVolume.fromBounds();
227
228
229 Assert.assertSame(ConvexVolume.full(), vol);
230 }
231
232 @Test
233 public void testFromBounds_halfspace() {
234
235 final ConvexVolume vol = ConvexVolume.fromBounds(Planes.fromNormal(Vector3D.Unit.PLUS_Z, TEST_PRECISION));
236
237
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
255 final ConvexVolume vol = rect(Vector3D.of(1, 1, 1), 0.5, 1, 2);
256
257
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
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
287 final PlaneConvexSubset trimmed = vol.trim(subplane);
288
289
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
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
309 final Split<ConvexVolume> split = vol.split(splitter);
310
311
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
326 final ConvexVolume volume = ConvexVolume.full();
327
328
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
341 final ConvexVolume volume = rect(Vector3D.of(0.5, 0.5, 0.5), 0.5, 0.5, 0.5);
342
343
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
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
374 final ConvexVolume transformed = vol.transform(transform);
375
376
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 }