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
22 import org.apache.commons.geometry.core.RegionLocation;
23 import org.apache.commons.geometry.core.Transform;
24 import org.apache.commons.geometry.core.partitioning.Split;
25 import org.apache.commons.geometry.core.partitioning.SplitLocation;
26 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
27 import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
28 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
29 import org.apache.commons.geometry.euclidean.threed.line.Lines3D;
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.numbers.angle.PlaneAngleRadians;
35 import org.junit.Assert;
36 import org.junit.Test;
37
38 public class PlaneConvexSubsetTest {
39
40 private static final double TEST_EPS = 1e-10;
41
42 private static final DoublePrecisionContext TEST_PRECISION =
43 new EpsilonDoublePrecisionContext(TEST_EPS);
44
45 @Test
46 public void testToConvex() {
47
48 final PlaneConvexSubset sp = Planes.convexPolygonFromVertices(
49 Arrays.asList(Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, Vector3D.Unit.PLUS_Z), TEST_PRECISION);
50
51
52 final List<PlaneConvexSubset> convex = sp.toConvex();
53
54
55 Assert.assertEquals(1, convex.size());
56 Assert.assertSame(sp, convex.get(0));
57 }
58
59 @Test
60 public void testReverse() {
61
62 final Vector3D p1 = Vector3D.of(1, 0, 1);
63 final Vector3D p2 = Vector3D.of(2, 0, 1);
64 final Vector3D p3 = Vector3D.of(1, 1, 1);
65
66 final PlaneConvexSubset sp = Planes.convexPolygonFromVertices(Arrays.asList(p1, p2, p3), TEST_PRECISION);
67
68
69 final PlaneConvexSubset reversed = sp.reverse();
70
71
72 Assert.assertEquals(sp.getPlane().reverse(), reversed.getPlane());
73 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_Z, reversed.getPlane().getNormal(), TEST_EPS);
74
75 Assert.assertEquals(0.5, reversed.getSize(), TEST_EPS);
76
77 checkVertices(reversed, p1, p3, p2);
78
79 checkPoints(reversed, RegionLocation.INSIDE, Vector3D.of(1.25, 0.25, 1));
80
81 checkPoints(reversed, RegionLocation.BOUNDARY, p1, p2, p3);
82 }
83
84 @Test
85 public void testTransform_full() {
86
87 final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(Vector3D.Unit.PLUS_Z, Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
88 final PlaneConvexSubset sp = Planes.subsetFromConvexArea(plane, ConvexArea.full());
89
90 final AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity()
91 .rotate(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_X, PlaneAngleRadians.PI_OVER_TWO))
92 .translate(Vector3D.Unit.PLUS_Y);
93
94
95 final PlaneConvexSubset transformed = sp.transform(transform);
96
97
98 Assert.assertTrue(transformed.isFull());
99 Assert.assertFalse(transformed.isEmpty());
100
101 checkPlane(transformed.getPlane(), Vector3D.ZERO, Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Z);
102 }
103
104 @Test
105 public void testTransform_halfSpace() {
106
107 final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(Vector3D.Unit.PLUS_Z, Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
108 final PlaneConvexSubset sp = Planes.subsetFromConvexArea(plane,
109 ConvexArea.fromBounds(Lines.fromPoints(Vector2D.of(1, 0), Vector2D.of(1, 1), TEST_PRECISION)));
110
111 final AffineTransformMatrix3D transform = AffineTransformMatrix3D.createRotation(Vector3D.Unit.PLUS_Z,
112 QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, PlaneAngleRadians.PI_OVER_TWO));
113
114
115 final PlaneConvexSubset transformed = sp.transform(transform);
116
117
118 Assert.assertFalse(transformed.isFull());
119 Assert.assertFalse(transformed.isEmpty());
120
121 checkPlane(transformed.getPlane(), Vector3D.ZERO, Vector3D.Unit.MINUS_Z, Vector3D.Unit.PLUS_Y);
122 }
123
124 @Test
125 public void testTransform_finite() {
126
127 final PlaneConvexSubset sp = Planes.convexPolygonFromVertices(
128 Arrays.asList(Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0), Vector3D.of(0, 0, 1)), TEST_PRECISION);
129
130 final Transform<Vector3D> transform = AffineTransformMatrix3D.createScale(2)
131 .rotate(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, PlaneAngleRadians.PI_OVER_TWO));
132
133
134 final PlaneConvexSubset transformed = sp.transform(transform);
135
136
137 final Vector3D midpt = Vector3D.of(2, 2, -2).multiply(1 / 3.0);
138 final Vector3D normal = midpt.normalize();
139 final Vector3D u = Vector3D.of(0, 2, 2).normalize();
140
141 checkPlane(transformed.getPlane(), midpt, u, normal.cross(u));
142
143 checkVertices(transformed, Vector3D.of(0, 0, -2), Vector3D.of(0, 2, 0), Vector3D.of(2, 0, 0));
144
145 checkPoints(transformed, RegionLocation.INSIDE, midpt);
146 }
147
148 @Test
149 public void testTransform_reflection() {
150
151 final PlaneConvexSubset sp = Planes.convexPolygonFromVertices(
152 Arrays.asList(Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0), Vector3D.of(0, 0, 1)), TEST_PRECISION);
153
154 final Transform<Vector3D> transform = AffineTransformMatrix3D.createScale(-1, 1, 1);
155
156
157 final PlaneConvexSubset transformed = sp.transform(transform);
158
159
160 final Vector3D midpt = Vector3D.of(-1, 1, 1).multiply(1 / 3.0);
161 final Vector3D normal = midpt.negate().normalize();
162 final Vector3D u = Vector3D.of(1, 1, 0).normalize();
163
164 checkPlane(transformed.getPlane(), midpt, u, normal.cross(u));
165
166 checkVertices(transformed, Vector3D.of(-1, 0, 0), Vector3D.of(0, 1, 0), Vector3D.of(0, 0, 1));
167
168 checkPoints(transformed, RegionLocation.INSIDE, Vector3D.of(-1, 1, 1).multiply(1 / 3.0));
169 }
170
171 @Test
172 public void testSplit_full() {
173
174 final EmbeddingPlane plane = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION)
175 .getEmbedding();
176 final PlaneConvexSubset sp = Planes.subsetFromConvexArea(plane, ConvexArea.full());
177
178 final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION);
179
180
181 final Split<PlaneConvexSubset> split = sp.split(splitter);
182
183
184 Assert.assertEquals(SplitLocation.BOTH, split.getLocation());
185
186 final PlaneConvexSubset minus = split.getMinus();
187 Assert.assertEquals(1, minus.getEmbedded().getSubspaceRegion().getBoundaries().size());
188 checkPoints(minus, RegionLocation.BOUNDARY, Vector3D.ZERO, Vector3D.Unit.PLUS_Y, Vector3D.Unit.MINUS_Y);
189 checkPoints(minus, RegionLocation.INSIDE, Vector3D.Unit.MINUS_X);
190 checkPoints(minus, RegionLocation.OUTSIDE, Vector3D.Unit.PLUS_X);
191
192 final PlaneConvexSubset plus = split.getPlus();
193 Assert.assertEquals(1, plus.getEmbedded().getSubspaceRegion().getBoundaries().size());
194 checkPoints(plus, RegionLocation.BOUNDARY, Vector3D.ZERO, Vector3D.Unit.PLUS_Y, Vector3D.Unit.MINUS_Y);
195 checkPoints(plus, RegionLocation.INSIDE, Vector3D.Unit.PLUS_X);
196 checkPoints(plus, RegionLocation.OUTSIDE, Vector3D.Unit.MINUS_X);
197 }
198
199 @Test
200 public void testSplit_both() {
201
202 final PlaneConvexSubset sp = Planes.convexPolygonFromVertices(Arrays.asList(
203 Vector3D.of(1, 1, 1), Vector3D.of(1, 1, -3), Vector3D.of(0, 2, 0)
204 ), TEST_PRECISION);
205
206 final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Z, TEST_PRECISION);
207
208
209 final Split<PlaneConvexSubset> split = sp.split(splitter);
210
211
212 Assert.assertEquals(SplitLocation.BOTH, split.getLocation());
213
214 final PlaneConvexSubset minus = split.getMinus();
215 checkVertices(minus, Vector3D.of(1, 1, 0), Vector3D.of(1, 1, -3), Vector3D.of(0, 2, 0));
216
217 final PlaneConvexSubset plus = split.getPlus();
218 checkVertices(plus, Vector3D.of(1, 1, 1), Vector3D.of(1, 1, 0), Vector3D.of(0, 2, 0));
219 }
220
221 @Test
222 public void testSplit_plusOnly() {
223
224 final PlaneConvexSubset sp = Planes.convexPolygonFromVertices(Arrays.asList(
225 Vector3D.of(1, 1, 1), Vector3D.of(1, 1, -3), Vector3D.of(0, 2, 0)
226 ), TEST_PRECISION);
227
228 final Plane splitter = Planes.fromPointAndNormal(Vector3D.of(0, 0, -3.1), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
229
230
231 final Split<PlaneConvexSubset> split = sp.split(splitter);
232
233
234 Assert.assertEquals(SplitLocation.PLUS, split.getLocation());
235
236 Assert.assertNull(split.getMinus());
237
238 final PlaneConvexSubset plus = split.getPlus();
239 checkVertices(plus, Vector3D.of(1, 1, 1), Vector3D.of(1, 1, -3), Vector3D.of(0, 2, 0));
240 }
241
242 @Test
243 public void testSplit_minusOnly() {
244
245 final PlaneConvexSubset sp = Planes.convexPolygonFromVertices(Arrays.asList(
246 Vector3D.of(1, 1, 1), Vector3D.of(1, 1, -3), Vector3D.of(0, 2, 0)
247 ), TEST_PRECISION);
248
249 final Plane splitter = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1.1), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
250
251
252 final Split<PlaneConvexSubset> split = sp.split(splitter);
253
254
255 Assert.assertEquals(SplitLocation.MINUS, split.getLocation());
256
257 final PlaneConvexSubset minus = split.getMinus();
258 checkVertices(minus, Vector3D.of(1, 1, 1), Vector3D.of(1, 1, -3), Vector3D.of(0, 2, 0));
259
260 Assert.assertNull(split.getPlus());
261 }
262
263 @Test
264 public void testSplit_parallelSplitter_on() {
265
266 final PlaneConvexSubset sp = Planes.convexPolygonFromVertices(Arrays.asList(
267 Vector3D.of(1, 1, 1), Vector3D.of(1, 1, -3), Vector3D.of(0, 2, 0)
268 ), TEST_PRECISION);
269
270 final Plane splitter = sp.getPlane();
271
272
273 final Split<PlaneConvexSubset> split = sp.split(splitter);
274
275
276 Assert.assertEquals(SplitLocation.NEITHER, split.getLocation());
277
278 Assert.assertNull(split.getMinus());
279 Assert.assertNull(split.getPlus());
280 }
281
282 @Test
283 public void testSplit_parallelSplitter_minus() {
284
285 final PlaneConvexSubset sp = Planes.convexPolygonFromVertices(Arrays.asList(
286 Vector3D.of(1, 1, 1), Vector3D.of(1, 1, -3), Vector3D.of(0, 2, 0)
287 ), TEST_PRECISION);
288
289 final Plane plane = sp.getPlane();
290 final Plane splitter = plane.translate(plane.getNormal());
291
292
293 final Split<PlaneConvexSubset> split = sp.split(splitter);
294
295
296 Assert.assertEquals(SplitLocation.MINUS, split.getLocation());
297
298 Assert.assertSame(sp, split.getMinus());
299 Assert.assertNull(split.getPlus());
300 }
301
302 @Test
303 public void testSplit_parallelSplitter_plus() {
304
305 final PlaneConvexSubset sp = Planes.convexPolygonFromVertices(Arrays.asList(
306 Vector3D.of(1, 1, 1), Vector3D.of(1, 1, -3), Vector3D.of(0, 2, 0)
307 ), TEST_PRECISION);
308
309 final Plane plane = sp.getPlane();
310 final Plane splitter = plane.translate(plane.getNormal().negate());
311
312
313 final Split<PlaneConvexSubset> split = sp.split(splitter);
314
315
316 Assert.assertEquals(SplitLocation.PLUS, split.getLocation());
317
318 Assert.assertNull(split.getMinus());
319 Assert.assertSame(sp, split.getPlus());
320 }
321
322 @Test
323 public void testSplit_antiParallelSplitter_on() {
324
325 final PlaneConvexSubset sp = Planes.convexPolygonFromVertices(Arrays.asList(
326 Vector3D.of(1, 1, 1), Vector3D.of(1, 1, -3), Vector3D.of(0, 2, 0)
327 ), TEST_PRECISION);
328
329 final Plane splitter = sp.getPlane().reverse();
330
331
332 final Split<PlaneConvexSubset> split = sp.split(splitter);
333
334
335 Assert.assertEquals(SplitLocation.NEITHER, split.getLocation());
336
337 Assert.assertNull(split.getMinus());
338 Assert.assertNull(split.getPlus());
339 }
340
341 @Test
342 public void testSplit_antiParallelSplitter_minus() {
343
344 final PlaneConvexSubset sp = Planes.convexPolygonFromVertices(Arrays.asList(
345 Vector3D.of(1, 1, 1), Vector3D.of(1, 1, -3), Vector3D.of(0, 2, 0)
346 ), TEST_PRECISION);
347
348 final Plane plane = sp.getPlane().reverse();
349 final Plane splitter = plane.translate(plane.getNormal());
350
351
352 final Split<PlaneConvexSubset> split = sp.split(splitter);
353
354
355 Assert.assertEquals(SplitLocation.MINUS, split.getLocation());
356
357 Assert.assertSame(sp, split.getMinus());
358 Assert.assertNull(split.getPlus());
359 }
360
361 @Test
362 public void testSplit_antiParallelSplitter_plus() {
363
364 final PlaneConvexSubset ps = Planes.convexPolygonFromVertices(Arrays.asList(
365 Vector3D.of(1, 1, 1), Vector3D.of(1, 1, -3), Vector3D.of(0, 2, 0)
366 ), TEST_PRECISION);
367
368 final Plane plane = ps.getPlane().reverse();
369 final Plane splitter = plane.translate(plane.getNormal().negate());
370
371
372 final Split<PlaneConvexSubset> split = ps.split(splitter);
373
374
375 Assert.assertEquals(SplitLocation.PLUS, split.getLocation());
376
377 Assert.assertNull(split.getMinus());
378 Assert.assertSame(ps, split.getPlus());
379 }
380
381 @Test
382 public void testSplit_usesVertexBasedTypesWhenPossible() {
383
384
385 final EmbeddingPlane plane = Planes.fromPointAndPlaneVectors(Vector3D.ZERO,
386 Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
387 final PlaneConvexSubset ps = Planes.subsetFromConvexArea(plane, ConvexArea.fromBounds(
388 Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION),
389 Lines.fromPointAndAngle(Vector2D.of(1, 0), PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION),
390 Lines.fromPointAndAngle(Vector2D.of(0, 1), -PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION)
391 ));
392
393 final Plane splitter = Planes.fromPointAndNormal(Vector3D.of(0, 1, 0), Vector3D.Unit.PLUS_Y, TEST_PRECISION);
394
395
396 final Split<PlaneConvexSubset> split = ps.split(splitter);
397
398
399 Assert.assertTrue(ps.isInfinite());
400
401 Assert.assertEquals(SplitLocation.BOTH, split.getLocation());
402
403 final PlaneConvexSubset plus = split.getPlus();
404 Assert.assertNotNull(plus);
405 Assert.assertTrue(plus.isInfinite());
406 Assert.assertFalse(plus instanceof ConvexPolygon3D);
407
408 final PlaneConvexSubset minus = split.getMinus();
409 Assert.assertNotNull(minus);
410 Assert.assertFalse(minus.isInfinite());
411 Assert.assertTrue(minus instanceof ConvexPolygon3D);
412 }
413
414 @Test
415 public void testIntersection_line() {
416
417 final PlaneConvexSubset ps = Planes.convexPolygonFromVertices(Arrays.asList(
418 Vector3D.of(0, 0, 2), Vector3D.of(1, 0, 2), Vector3D.of(1, 1, 2), Vector3D.of(0, 1, 2)),
419 TEST_PRECISION);
420
421
422 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 0.5, 2),
423 ps.intersection(Lines3D.fromPoints(Vector3D.of(0.5, 0.5, 2), Vector3D.ZERO, TEST_PRECISION)), TEST_EPS);
424
425 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 2),
426 ps.intersection(Lines3D.fromPoints(Vector3D.of(1, 1, 2), Vector3D.of(1, 1, 0), TEST_PRECISION)), TEST_EPS);
427
428 Assert.assertNull(ps.intersection(Lines3D.fromPoints(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION)));
429 Assert.assertNull(ps.intersection(Lines3D.fromPoints(Vector3D.of(0, 0, 2), Vector3D.of(1, 1, 2), TEST_PRECISION)));
430
431 Assert.assertNull(ps.intersection(Lines3D.fromPoints(Vector3D.of(4, 4, 2), Vector3D.of(4, 4, 0), TEST_PRECISION)));
432 }
433
434 @Test
435 public void testIntersection_segment() {
436
437 final PlaneConvexSubset sp = Planes.convexPolygonFromVertices(Arrays.asList(
438 Vector3D.of(0, 0, 2), Vector3D.of(1, 0, 2), Vector3D.of(1, 1, 2), Vector3D.of(0, 1, 2)),
439 TEST_PRECISION);
440
441
442 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 0.5, 2),
443 sp.intersection(Lines3D.segmentFromPoints(Vector3D.of(0.5, 0.5, 2), Vector3D.ZERO, TEST_PRECISION)), TEST_EPS);
444
445 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 2),
446 sp.intersection(Lines3D.segmentFromPoints(Vector3D.of(1, 1, 2), Vector3D.of(1, 1, 0), TEST_PRECISION)), TEST_EPS);
447
448 Assert.assertNull(sp.intersection(Lines3D.segmentFromPoints(Vector3D.of(0.5, 0.5, 4), Vector3D.of(0.5, 0.5, 3), TEST_PRECISION)));
449
450 Assert.assertNull(sp.intersection(Lines3D.segmentFromPoints(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION)));
451 Assert.assertNull(sp.intersection(Lines3D.segmentFromPoints(Vector3D.of(0, 0, 2), Vector3D.of(1, 1, 2), TEST_PRECISION)));
452
453 Assert.assertNull(sp.intersection(Lines3D.segmentFromPoints(Vector3D.of(4, 4, 2), Vector3D.of(4, 4, 0), TEST_PRECISION)));
454 }
455
456 private static void checkPlane(final Plane plane, final Vector3D origin, Vector3D u, Vector3D v) {
457 u = u.normalize();
458 v = v.normalize();
459 final Vector3D w = u.cross(v);
460
461 EuclideanTestUtils.assertCoordinatesEqual(origin, plane.getOrigin(), TEST_EPS);
462 Assert.assertTrue(plane.contains(origin));
463
464 EuclideanTestUtils.assertCoordinatesEqual(w, plane.getNormal(), TEST_EPS);
465 Assert.assertEquals(1.0, plane.getNormal().norm(), TEST_EPS);
466
467 final double offset = plane.getOriginOffset();
468 Assert.assertEquals(Vector3D.ZERO.distance(plane.getOrigin()), Math.abs(offset), TEST_EPS);
469 EuclideanTestUtils.assertCoordinatesEqual(origin, plane.getNormal().multiply(-offset), TEST_EPS);
470 }
471
472 private static void checkPoints(final PlaneConvexSubset sp, final RegionLocation loc, final Vector3D... pts) {
473 for (final Vector3D pt : pts) {
474 Assert.assertEquals("Unexpected location for point " + pt, loc, sp.classify(pt));
475 }
476 }
477
478 private static void checkVertices(final PlaneConvexSubset ps, final Vector3D... pts) {
479 EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(pts), ps.getVertices(), TEST_PRECISION);
480 }
481 }