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
23 import org.apache.commons.geometry.core.GeometryTestUtils;
24 import org.apache.commons.geometry.core.RegionLocation;
25 import org.apache.commons.geometry.core.partitioning.Split;
26 import org.apache.commons.geometry.core.partitioning.SplitLocation;
27 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
28 import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
29 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
30 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
31 import org.apache.commons.geometry.euclidean.twod.ConvexArea;
32 import org.apache.commons.geometry.euclidean.twod.Lines;
33 import org.apache.commons.geometry.euclidean.twod.Vector2D;
34 import org.apache.commons.geometry.euclidean.twod.path.LinePath;
35 import org.apache.commons.geometry.euclidean.twod.shape.Parallelogram;
36 import org.apache.commons.numbers.angle.PlaneAngleRadians;
37 import org.junit.Assert;
38 import org.junit.Test;
39
40 public class EmbeddedAreaPlaneConvexSubsetTest {
41
42 private static final double TEST_EPS = 1e-10;
43
44 private static final DoublePrecisionContext TEST_PRECISION =
45 new EpsilonDoublePrecisionContext(TEST_EPS);
46
47 private static final EmbeddingPlane XY_PLANE_Z1 = Planes.fromPointAndPlaneVectors(Vector3D.of(0, 0, 1),
48 Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
49
50 @Test
51 public void testSpaceConversion() {
52
53 final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(Vector3D.of(1, 0, 0),
54 Vector3D.Unit.PLUS_Y, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
55
56 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(plane, ConvexArea.full());
57
58
59 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2), ps.toSubspace(Vector3D.of(-5, 1, 2)), TEST_EPS);
60 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, -2, 4), ps.toSpace(Vector2D.of(-2, 4)), TEST_EPS);
61 }
62
63 @Test
64 public void testProperties_infinite() {
65
66 final ConvexArea area = ConvexArea.full();
67
68
69 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1, area);
70
71
72 Assert.assertTrue(ps.isFull());
73 Assert.assertFalse(ps.isEmpty());
74 Assert.assertFalse(ps.isFinite());
75 Assert.assertTrue(ps.isInfinite());
76
77 GeometryTestUtils.assertPositiveInfinity(ps.getSize());
78
79 Assert.assertSame(XY_PLANE_Z1, ps.getPlane());
80 Assert.assertSame(area, ps.getSubspaceRegion());
81
82 Assert.assertEquals(0, ps.getVertices().size());
83 }
84
85 @Test
86 public void testProperties_finite() {
87
88 final ConvexArea area = ConvexArea.convexPolygonFromPath(LinePath.builder(TEST_PRECISION)
89 .appendVertices(Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(0, 1))
90 .build(true));
91
92
93 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1, area);
94
95
96 Assert.assertFalse(ps.isFull());
97 Assert.assertFalse(ps.isEmpty());
98 Assert.assertTrue(ps.isFinite());
99 Assert.assertFalse(ps.isInfinite());
100
101 Assert.assertEquals(0.5, ps.getSize(), TEST_EPS);
102
103 Assert.assertSame(XY_PLANE_Z1, ps.getPlane());
104 Assert.assertSame(area, ps.getSubspaceRegion());
105
106 EuclideanTestUtils.assertVertexLoopSequence(
107 Arrays.asList(Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1)),
108 ps.getVertices(), TEST_PRECISION);
109 }
110
111 @Test
112 public void testGetVertices_twoParallelLines() {
113
114 final EmbeddingPlane plane = Planes.fromNormal(Vector3D.Unit.PLUS_Z, TEST_PRECISION).getEmbedding();
115 final PlaneConvexSubset sp = new EmbeddedAreaPlaneConvexSubset(plane, ConvexArea.fromBounds(
116 Lines.fromPointAndAngle(Vector2D.of(0, 1), PlaneAngleRadians.PI, TEST_PRECISION),
117 Lines.fromPointAndAngle(Vector2D.of(0, -1), 0.0, TEST_PRECISION)
118 ));
119
120
121 final List<Vector3D> vertices = sp.getVertices();
122
123
124 Assert.assertEquals(0, vertices.size());
125 }
126
127 @Test
128 public void testGetVertices_infiniteWithVertices() {
129
130 final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
131 final PlaneConvexSubset sp = new EmbeddedAreaPlaneConvexSubset(plane, ConvexArea.fromBounds(
132 Lines.fromPointAndAngle(Vector2D.of(0, 1), PlaneAngleRadians.PI, TEST_PRECISION),
133 Lines.fromPointAndAngle(Vector2D.of(0, -1), 0.0, TEST_PRECISION),
134 Lines.fromPointAndAngle(Vector2D.of(1, 0), PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION)
135 ));
136
137
138 final List<Vector3D> vertices = sp.getVertices();
139
140
141 Assert.assertEquals(2, vertices.size());
142
143 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, -1, 1), vertices.get(0), TEST_EPS);
144 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 1), vertices.get(1), TEST_EPS);
145 }
146 @Test
147 public void testToTriangles_infinite() {
148
149 final Pattern pattern = Pattern.compile("^Cannot convert infinite plane subset to triangles: .*");
150
151
152 GeometryTestUtils.assertThrows(() -> {
153 new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1, ConvexArea.full()).toTriangles();
154 }, IllegalStateException.class, pattern);
155
156 GeometryTestUtils.assertThrows(() -> {
157 final ConvexArea area = ConvexArea.fromBounds(Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION));
158 final EmbeddedAreaPlaneConvexSubset halfSpace = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1, area);
159
160 halfSpace.toTriangles();
161 }, IllegalStateException.class, pattern);
162
163 GeometryTestUtils.assertThrows(() -> {
164 final ConvexArea area = ConvexArea.fromBounds(
165 Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION),
166 Lines.fromPointAndAngle(Vector2D.ZERO, 0.5 * Math.PI, TEST_PRECISION));
167
168 final EmbeddedAreaPlaneConvexSubset halfSpaceWithVertices = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1, area);
169
170 halfSpaceWithVertices.toTriangles();
171 }, IllegalStateException.class, pattern);
172 }
173
174 @Test
175 public void testToTriangles_finite() {
176
177 final Vector3D p1 = Vector3D.of(0, 0, 1);
178 final Vector3D p2 = Vector3D.of(1, 0, 1);
179 final Vector3D p3 = Vector3D.of(2, 1, 1);
180 final Vector3D p4 = Vector3D.of(1.5, 1, 1);
181
182 final List<Vector2D> subPts = XY_PLANE_Z1.toSubspace(Arrays.asList(p1, p2, p3, p4));
183
184 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1,
185 ConvexArea.convexPolygonFromVertices(subPts, TEST_PRECISION));
186
187
188 final List<Triangle3D> tris = ps.toTriangles();
189
190
191 Assert.assertEquals(2, tris.size());
192
193 EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p4, p1, p2),
194 tris.get(0).getVertices(), TEST_PRECISION);
195 EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p4, p2, p3),
196 tris.get(1).getVertices(), TEST_PRECISION);
197 }
198
199 @Test
200 public void testClassify() {
201
202 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1,
203 Parallelogram.builder(TEST_PRECISION)
204 .setPosition(Vector2D.of(2, 3))
205 .setScale(2, 2)
206 .build());
207
208
209 checkPoints(ps, RegionLocation.INSIDE, Vector3D.of(2, 3, 1));
210 checkPoints(ps, RegionLocation.BOUNDARY,
211 Vector3D.of(1, 3, 1), Vector3D.of(3, 3, 1),
212 Vector3D.of(2, 2, 1), Vector3D.of(2, 4, 1));
213 checkPoints(ps, RegionLocation.OUTSIDE,
214 Vector3D.of(2, 3, 0), Vector3D.of(2, 3, 2),
215 Vector3D.of(0, 3, 1), Vector3D.of(4, 3, 1),
216 Vector3D.of(2, 1, 1), Vector3D.of(2, 5, 1));
217 }
218
219 @Test
220 public void testClosest() {
221
222 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1,
223 Parallelogram.builder(TEST_PRECISION)
224 .setPosition(Vector2D.of(2, 3))
225 .setScale(2, 2)
226 .build());
227
228
229 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 3, 1), ps.closest(Vector3D.of(2, 3, 1)), TEST_EPS);
230 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 3, 1), ps.closest(Vector3D.of(2, 3, 100)), TEST_EPS);
231 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 1),
232 ps.closest(Vector3D.of(-100, -100, -100)), TEST_EPS);
233 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 3.5, 1),
234 ps.closest(Vector3D.of(100, 3.5, 100)), TEST_EPS);
235 }
236
237 @Test
238 public void testGetBounds_noBounds() {
239
240 final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(Vector3D.of(0, 0, 1),
241 Vector3D.Unit.PLUS_Y, Vector3D.Unit.MINUS_X, TEST_PRECISION);
242
243 final EmbeddedAreaPlaneConvexSubset full = new EmbeddedAreaPlaneConvexSubset(plane, ConvexArea.full());
244 final EmbeddedAreaPlaneConvexSubset halfPlane = new EmbeddedAreaPlaneConvexSubset(plane,
245 ConvexArea.fromBounds(Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION)));
246
247
248 Assert.assertNull(full.getBounds());
249 Assert.assertNull(halfPlane.getBounds());
250 }
251
252 @Test
253 public void testGetBounds_hasBounds() {
254
255 final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(Vector3D.of(0, 0, 1),
256 Vector3D.Unit.PLUS_Y, Vector3D.Unit.MINUS_X, TEST_PRECISION);
257
258 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(plane,
259 ConvexArea.convexPolygonFromVertices(Arrays.asList(
260 Vector2D.of(1, 1), Vector2D.of(2, 1), Vector2D.of(1, 2)
261 ), TEST_PRECISION));
262
263
264 final Bounds3D bounds = ps.getBounds();
265
266
267 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-2, 1, 1), bounds.getMin(), TEST_EPS);
268 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 2, 1), bounds.getMax(), TEST_EPS);
269 }
270
271 @Test
272 public void testTransform() {
273
274 final AffineTransformMatrix3D t = AffineTransformMatrix3D.identity()
275 .rotate(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, -PlaneAngleRadians.PI_OVER_TWO))
276 .scale(1, 1, 2)
277 .translate(Vector3D.of(1, 0, 0));
278
279 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1,
280 Parallelogram.builder(TEST_PRECISION)
281 .setPosition(Vector2D.of(2, 3))
282 .setScale(2, 2)
283 .build());
284
285
286 final EmbeddedAreaPlaneConvexSubset result = ps.transform(t);
287
288
289 Assert.assertFalse(result.isFull());
290 Assert.assertFalse(result.isEmpty());
291 Assert.assertTrue(result.isFinite());
292 Assert.assertFalse(result.isInfinite());
293
294 Assert.assertEquals(8, result.getSize(), TEST_EPS);
295
296 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_X, result.getPlane().getNormal(), TEST_EPS);
297 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.PLUS_Z, result.getPlane().getU(), TEST_EPS);
298
299 EuclideanTestUtils.assertVertexLoopSequence(
300 Arrays.asList(Vector3D.of(0, 2, 2), Vector3D.of(0, 2, 6), Vector3D.of(0, 4, 6), Vector3D.of(0, 4, 2)),
301 result.getVertices(), TEST_PRECISION);
302 }
303
304 @Test
305 public void testReverse() {
306
307 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1,
308 Parallelogram.builder(TEST_PRECISION)
309 .setPosition(Vector2D.of(2, 3))
310 .setScale(2, 2)
311 .build());
312
313
314 final EmbeddedAreaPlaneConvexSubset result = ps.reverse();
315
316
317 Assert.assertFalse(result.isFull());
318 Assert.assertFalse(result.isEmpty());
319 Assert.assertTrue(result.isFinite());
320 Assert.assertFalse(result.isInfinite());
321
322 Assert.assertEquals(4, result.getSize(), TEST_EPS);
323
324 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_Z, result.getPlane().getNormal(), TEST_EPS);
325 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.PLUS_Y, result.getPlane().getU(), TEST_EPS);
326
327 EuclideanTestUtils.assertVertexLoopSequence(
328 Arrays.asList(Vector3D.of(1, 4, 1), Vector3D.of(3, 4, 1), Vector3D.of(3, 2, 1), Vector3D.of(1, 2, 1)),
329 result.getVertices(), TEST_PRECISION);
330 }
331
332 @Test
333 public void testSplit_plus() {
334
335 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1,
336 ConvexArea.convexPolygonFromVertices(Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(0, 1)),
337 TEST_PRECISION));
338
339 final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION);
340
341
342 final Split<PlaneConvexSubset> split = ps.split(splitter);
343
344
345 Assert.assertEquals(SplitLocation.PLUS, split.getLocation());
346
347 Assert.assertNull(split.getMinus());
348 Assert.assertSame(ps, split.getPlus());
349 }
350
351 @Test
352 public void testSplit_minus() {
353
354 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1,
355 ConvexArea.convexPolygonFromVertices(Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(0, 1)),
356 TEST_PRECISION));
357
358 final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.MINUS_Z, TEST_PRECISION);
359
360
361 final Split<PlaneConvexSubset> split = ps.split(splitter);
362
363
364 Assert.assertEquals(SplitLocation.MINUS, split.getLocation());
365
366 Assert.assertSame(ps, split.getMinus());
367 Assert.assertNull(split.getPlus());
368 }
369
370 @Test
371 public void testSplit_both() {
372
373 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1,
374 ConvexArea.convexPolygonFromVertices(Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(0, 1)),
375 TEST_PRECISION));
376
377 final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.of(-1, 1, 0), TEST_PRECISION);
378
379
380 final Split<PlaneConvexSubset> split = ps.split(splitter);
381
382
383 Assert.assertEquals(SplitLocation.BOTH, split.getLocation());
384
385 final PlaneConvexSubset minus = split.getMinus();
386 EuclideanTestUtils.assertVertexLoopSequence(
387 Arrays.asList(Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0.5, 0.5, 1)),
388 minus.getVertices(), TEST_PRECISION);
389
390 final PlaneConvexSubset plus = split.getPlus();
391 EuclideanTestUtils.assertVertexLoopSequence(
392 Arrays.asList(Vector3D.of(0, 0, 1), Vector3D.of(0.5, 0.5, 1), Vector3D.of(0, 1, 1)),
393 plus.getVertices(), TEST_PRECISION);
394 }
395
396 @Test
397 public void testSplit_neither() {
398
399 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1,
400 ConvexArea.convexPolygonFromVertices(Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(0, 1)),
401 TEST_PRECISION));
402
403 final Plane splitter = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0, 1e-15, -1), TEST_PRECISION);
404
405
406 final Split<PlaneConvexSubset> split = ps.split(splitter);
407
408
409 Assert.assertEquals(SplitLocation.NEITHER, split.getLocation());
410
411 Assert.assertNull(split.getMinus());
412 Assert.assertNull(split.getPlus());
413 }
414
415 @Test
416 public void testSplit_usesVertexBasedSubsetsWhenPossible() {
417
418
419 final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(Vector3D.ZERO,
420 Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
421 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(plane, ConvexArea.fromBounds(
422 Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION),
423 Lines.fromPointAndAngle(Vector2D.of(1, 0), PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION),
424 Lines.fromPointAndAngle(Vector2D.of(0, 1), -PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION)
425 ));
426
427 final Plane splitter = Planes.fromPointAndNormal(Vector3D.of(0.5, 0.5, 0), Vector3D.of(-1, 1, 0), TEST_PRECISION);
428
429
430 final Split<PlaneConvexSubset> split = ps.split(splitter);
431
432
433 Assert.assertTrue(ps.isInfinite());
434
435 Assert.assertEquals(SplitLocation.BOTH, split.getLocation());
436
437 final PlaneConvexSubset plus = split.getPlus();
438 Assert.assertNotNull(plus);
439 Assert.assertTrue(plus.isInfinite());
440 Assert.assertTrue(plus instanceof EmbeddedAreaPlaneConvexSubset);
441
442 final PlaneConvexSubset minus = split.getMinus();
443 Assert.assertNotNull(minus);
444 Assert.assertFalse(minus.isInfinite());
445 Assert.assertTrue(minus instanceof SimpleTriangle3D);
446 }
447
448 @Test
449 public void testToString() {
450
451 final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1,
452 ConvexArea.convexPolygonFromVertices(Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(0, 1)),
453 TEST_PRECISION));
454
455
456 final String str = ps.toString();
457
458
459 GeometryTestUtils.assertContains("EmbeddedAreaPlaneConvexSubset[plane= EmbeddingPlane[", str);
460 GeometryTestUtils.assertContains("subspaceRegion= ConvexArea[", str);
461 }
462
463 private static void checkPoints(final EmbeddedAreaPlaneConvexSubset ps, final RegionLocation loc, final Vector3D... pts) {
464 for (final Vector3D pt : pts) {
465 Assert.assertEquals("Unexpected location for point " + pt, loc, ps.classify(pt));
466 }
467 }
468 }