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  
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          // arrange
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          // act/assert
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          // arrange
66          final ConvexArea area = ConvexArea.full();
67  
68          // act
69          final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1, area);
70  
71          // assert
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          // arrange
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          // act
93          final EmbeddedAreaPlaneConvexSubset ps = new EmbeddedAreaPlaneConvexSubset(XY_PLANE_Z1, area);
94  
95          // assert
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         // arrange
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         // act
121         final List<Vector3D> vertices = sp.getVertices();
122 
123         // assert
124         Assert.assertEquals(0, vertices.size());
125     }
126 
127     @Test
128     public void testGetVertices_infiniteWithVertices() {
129         // arrange
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         // act
138         final List<Vector3D> vertices = sp.getVertices();
139 
140         // assert
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         // arrange
149         final Pattern pattern = Pattern.compile("^Cannot convert infinite plane subset to triangles: .*");
150 
151         // act/assert
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         // arrange
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         // act
188         final List<Triangle3D> tris = ps.toTriangles();
189 
190         // assert
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
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         // arrange
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         // act/assert
248         Assert.assertNull(full.getBounds());
249         Assert.assertNull(halfPlane.getBounds());
250     }
251 
252     @Test
253     public void testGetBounds_hasBounds() {
254         // arrange
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         // act
264         final Bounds3D bounds = ps.getBounds();
265 
266         // assert
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         // arrange
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         // act
286         final EmbeddedAreaPlaneConvexSubset result = ps.transform(t);
287 
288         // assert
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         // arrange
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         // act
314         final EmbeddedAreaPlaneConvexSubset result = ps.reverse();
315 
316         // assert
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         // arrange
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         // act
342         final Split<PlaneConvexSubset> split = ps.split(splitter);
343 
344         // assert
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         // arrange
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         // act
361         final Split<PlaneConvexSubset> split = ps.split(splitter);
362 
363         // assert
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         // arrange
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         // act
380         final Split<PlaneConvexSubset> split = ps.split(splitter);
381 
382         // assert
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         // arrange
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         // act
406         final Split<PlaneConvexSubset> split = ps.split(splitter);
407 
408         // assert
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         // arrange
418         // create an infinite subset
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         // act
430         final Split<PlaneConvexSubset> split = ps.split(splitter);
431 
432         // assert
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         // arrange
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         // act
456         final String str = ps.toString();
457 
458         // assert
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 }