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.ArrayList;
20 import java.util.Arrays;
21 import java.util.List;
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.euclidean.EuclideanTestUtils;
28 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
29 import org.apache.commons.geometry.euclidean.twod.ConvexArea;
30 import org.apache.commons.geometry.euclidean.twod.Vector2D;
31 import org.apache.commons.numbers.angle.Angle;
32 import org.apache.commons.numbers.core.Precision;
33 import org.junit.jupiter.api.Assertions;
34 import org.junit.jupiter.api.Test;
35
36 class VertexListConvexPolygon3DTest {
37
38 private static final double TEST_EPS = 1e-10;
39
40 private static final Precision.DoubleEquivalence TEST_PRECISION =
41 Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
42
43 private static final Plane XY_PLANE_Z1 = Planes.fromPointAndPlaneVectors(Vector3D.of(0, 0, 1),
44 Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
45
46 private static final List<Vector3D> TRIANGLE_VERTICES =
47 Arrays.asList(Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
48
49 @Test
50 void testProperties() {
51
52 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, TRIANGLE_VERTICES);
53
54
55 Assertions.assertFalse(p.isFull());
56 Assertions.assertFalse(p.isEmpty());
57 Assertions.assertTrue(p.isFinite());
58 Assertions.assertFalse(p.isInfinite());
59
60 Assertions.assertEquals(0.5, p.getSize(), TEST_EPS);
61 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1.0 / 3.0, 1.0 / 3.0, 1), p.getCentroid(), TEST_EPS);
62
63 Assertions.assertSame(XY_PLANE_Z1, p.getPlane());
64
65 EuclideanTestUtils.assertVertexLoopSequence(
66 Arrays.asList(Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1)),
67 p.getVertices(), TEST_PRECISION);
68
69
70 final Bounds3D bounds = p.getBounds();
71 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 1), bounds.getMin(), TEST_EPS);
72 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 1), bounds.getMax(), TEST_EPS);
73 }
74
75 @Test
76 void testCtor_validatesVertexListSize() {
77
78 GeometryTestUtils.assertThrowsWithMessage(() -> {
79 new VertexListConvexPolygon3D(XY_PLANE_Z1, Arrays.asList(Vector3D.ZERO, Vector3D.Unit.PLUS_X));
80 }, IllegalArgumentException.class, "Convex polygon requires at least 3 points; found 2");
81 }
82
83 @Test
84 void testVertices_listIsImmutable() {
85
86 final List<Vector3D> vertices = new ArrayList<>(TRIANGLE_VERTICES);
87 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, vertices);
88
89
90 Assertions.assertThrows(UnsupportedOperationException.class, () -> p.getVertices().add(Vector3D.of(-1, 0, 1)));
91 }
92
93 @Test
94 void testGetCentroid_linearVertices() {
95
96
97
98
99
100 final List<Vector3D> vertices = Arrays.asList(Vector3D.ZERO, Vector3D.of(0.5, 0, 0), Vector3D.of(2, 0, 0));
101 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, vertices);
102
103
104 final Vector3D center = p.getCentroid();
105
106
107 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), center, TEST_EPS);
108 }
109
110 @Test
111 void testGetSubspaceRegion() {
112
113 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, TRIANGLE_VERTICES);
114
115
116 final ConvexArea area = p.getEmbedded().getSubspaceRegion();
117
118
119 Assertions.assertFalse(area.isFull());
120 Assertions.assertFalse(area.isEmpty());
121 Assertions.assertTrue(area.isFinite());
122 Assertions.assertFalse(area.isInfinite());
123
124 Assertions.assertEquals(0.5, area.getSize(), TEST_EPS);
125
126 final List<Vector2D> vertices = area.getVertices();
127 Assertions.assertEquals(3, vertices.size());
128 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, vertices.get(0), TEST_EPS);
129 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 0), vertices.get(1), TEST_EPS);
130 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 1), vertices.get(2), TEST_EPS);
131 }
132
133 @Test
134 void testToTriangles_threeVertices() {
135
136 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, TRIANGLE_VERTICES);
137
138
139 final List<Triangle3D> tris = p.toTriangles();
140
141
142 Assertions.assertEquals(1, tris.size());
143
144 final Triangle3D a = tris.get(0);
145 Assertions.assertSame(XY_PLANE_Z1, a.getPlane());
146 EuclideanTestUtils.assertVertexLoopSequence(TRIANGLE_VERTICES, a.getVertices(), TEST_PRECISION);
147 }
148
149 @Test
150 void testToTriangles_fiveVertices() {
151
152 final Vector3D p1 = Vector3D.of(1, 1, 1);
153 final Vector3D p2 = Vector3D.of(2, 1.2, 1);
154 final Vector3D p3 = Vector3D.of(3, 2, 1);
155 final Vector3D p4 = Vector3D.of(1, 4, 1);
156 final Vector3D p5 = Vector3D.of(0, 2, 1);
157
158 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, Arrays.asList(p1, p2, p3, p4, p5));
159
160
161 final List<Triangle3D> tris = p.toTriangles();
162
163
164 Assertions.assertEquals(3, tris.size());
165
166 final Triangle3D a = tris.get(0);
167 Assertions.assertSame(XY_PLANE_Z1, a.getPlane());
168 EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p2, p3, p4), a.getVertices(), TEST_PRECISION);
169
170 final Triangle3D b = tris.get(1);
171 Assertions.assertSame(XY_PLANE_Z1, b.getPlane());
172 EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p2, p4, p5), b.getVertices(), TEST_PRECISION);
173
174 final Triangle3D c = tris.get(2);
175 Assertions.assertSame(XY_PLANE_Z1, c.getPlane());
176 EuclideanTestUtils.assertVertexLoopSequence(Arrays.asList(p2, p5, p1), c.getVertices(), TEST_PRECISION);
177 }
178
179 @Test
180 void testClassify() {
181
182 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, Arrays.asList(
183 Vector3D.of(1, 2, 1), Vector3D.of(3, 2, 1),
184 Vector3D.of(3, 4, 1), Vector3D.of(1, 4, 1)
185 ));
186
187
188 checkPoints(p, RegionLocation.INSIDE, Vector3D.of(2, 3, 1));
189 checkPoints(p, RegionLocation.BOUNDARY,
190 Vector3D.of(1, 3, 1), Vector3D.of(3, 3, 1),
191 Vector3D.of(2, 2, 1), Vector3D.of(2, 4, 1));
192 checkPoints(p, RegionLocation.OUTSIDE,
193 Vector3D.of(2, 3, 0), Vector3D.of(2, 3, 2),
194 Vector3D.of(0, 3, 1), Vector3D.of(4, 3, 1),
195 Vector3D.of(2, 1, 1), Vector3D.of(2, 5, 1));
196 }
197
198 @Test
199 void testClosest() {
200
201 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, Arrays.asList(
202 Vector3D.of(1, 2, 1), Vector3D.of(3, 2, 1),
203 Vector3D.of(3, 4, 1), Vector3D.of(1, 4, 1)
204 ));
205
206
207 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 3, 1), p.closest(Vector3D.of(2, 3, 1)), TEST_EPS);
208 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 3, 1), p.closest(Vector3D.of(2, 3, 100)), TEST_EPS);
209
210 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 4, 1), p.closest(Vector3D.of(3, 5, 10)), TEST_EPS);
211 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 4, 1), p.closest(Vector3D.of(3, 4, 10)), TEST_EPS);
212 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 3, 1), p.closest(Vector3D.of(3, 3, 10)), TEST_EPS);
213 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 2, 1), p.closest(Vector3D.of(3, 2, 10)), TEST_EPS);
214 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 2, 1), p.closest(Vector3D.of(3, 1, 10)), TEST_EPS);
215
216 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 4, 1), p.closest(Vector3D.of(0, 5, -10)), TEST_EPS);
217 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 4, 1), p.closest(Vector3D.of(1, 5, -10)), TEST_EPS);
218 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 4, 1), p.closest(Vector3D.of(2, 5, -10)), TEST_EPS);
219 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 4, 1), p.closest(Vector3D.of(3, 5, -10)), TEST_EPS);
220 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 4, 1), p.closest(Vector3D.of(4, 5, -10)), TEST_EPS);
221
222 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 1), p.closest(Vector3D.of(0, 2, 1)), TEST_EPS);
223 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 1), p.closest(Vector3D.of(1, 2, 1)), TEST_EPS);
224 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 2, 1), p.closest(Vector3D.of(2, 2, 1)), TEST_EPS);
225 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 2, 1), p.closest(Vector3D.of(3, 2, 1)), TEST_EPS);
226 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 2, 1), p.closest(Vector3D.of(4, 2, 1)), TEST_EPS);
227
228 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 3, 1), p.closest(Vector3D.of(0, 3, -10)), TEST_EPS);
229 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 3, 1), p.closest(Vector3D.of(1, 3, -10)), TEST_EPS);
230 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 3, 1), p.closest(Vector3D.of(2, 3, -10)), TEST_EPS);
231 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 3, 1), p.closest(Vector3D.of(3, 3, -10)), TEST_EPS);
232 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 3, 1), p.closest(Vector3D.of(4, 3, -10)), TEST_EPS);
233
234 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 1),
235 p.closest(Vector3D.of(-100, -100, -100)), TEST_EPS);
236 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 3.5, 1),
237 p.closest(Vector3D.of(100, 3.5, 100)), TEST_EPS);
238 }
239
240 @Test
241 void testTransform() {
242
243 final AffineTransformMatrix3D t = AffineTransformMatrix3D.identity()
244 .rotate(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, -Angle.PI_OVER_TWO))
245 .scale(1, 1, 2)
246 .translate(Vector3D.of(1, 0, 0));
247
248 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, Arrays.asList(
249 Vector3D.of(1, 2, 1), Vector3D.of(3, 2, 1),
250 Vector3D.of(3, 4, 1), Vector3D.of(1, 4, 1)
251 ));
252
253
254 final VertexListConvexPolygon3D result = p.transform(t);
255
256
257 Assertions.assertFalse(result.isFull());
258 Assertions.assertFalse(result.isEmpty());
259 Assertions.assertTrue(result.isFinite());
260 Assertions.assertFalse(result.isInfinite());
261
262 Assertions.assertEquals(8, result.getSize(), TEST_EPS);
263
264 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_X, result.getPlane().getNormal(), TEST_EPS);
265
266 EuclideanTestUtils.assertVertexLoopSequence(
267 Arrays.asList(Vector3D.of(0, 2, 2), Vector3D.of(0, 2, 6), Vector3D.of(0, 4, 6), Vector3D.of(0, 4, 2)),
268 result.getVertices(), TEST_PRECISION);
269 }
270
271 @Test
272 void testReverse() {
273
274 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, Arrays.asList(
275 Vector3D.of(1, 2, 1), Vector3D.of(3, 2, 1),
276 Vector3D.of(3, 4, 1), Vector3D.of(1, 4, 1)
277 ));
278
279
280 final VertexListConvexPolygon3D result = p.reverse();
281
282
283 Assertions.assertFalse(result.isFull());
284 Assertions.assertFalse(result.isEmpty());
285 Assertions.assertTrue(result.isFinite());
286 Assertions.assertFalse(result.isInfinite());
287
288 Assertions.assertEquals(4, result.getSize(), TEST_EPS);
289
290 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_Z, result.getPlane().getNormal(), TEST_EPS);
291
292 EuclideanTestUtils.assertVertexLoopSequence(
293 Arrays.asList(Vector3D.of(1, 4, 1), Vector3D.of(3, 4, 1), Vector3D.of(3, 2, 1), Vector3D.of(1, 2, 1)),
294 result.getVertices(), TEST_PRECISION);
295 }
296
297 @Test
298 void testSplit_plus() {
299
300 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, TRIANGLE_VERTICES);
301
302 final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION);
303
304
305 final Split<PlaneConvexSubset> split = p.split(splitter);
306
307
308 Assertions.assertEquals(SplitLocation.PLUS, split.getLocation());
309
310 Assertions.assertNull(split.getMinus());
311 Assertions.assertSame(p, split.getPlus());
312 }
313
314 @Test
315 void testSplit_minus() {
316
317 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, TRIANGLE_VERTICES);
318
319 final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.MINUS_Z, TEST_PRECISION);
320
321
322 final Split<PlaneConvexSubset> split = p.split(splitter);
323
324
325 Assertions.assertEquals(SplitLocation.MINUS, split.getLocation());
326
327 Assertions.assertSame(p, split.getMinus());
328 Assertions.assertNull(split.getPlus());
329 }
330
331 @Test
332 void testSplit_both() {
333
334 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, TRIANGLE_VERTICES);
335
336 final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.of(-1, 1, 0), TEST_PRECISION);
337
338
339 final Split<PlaneConvexSubset> split = p.split(splitter);
340
341
342 Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
343
344 final PlaneConvexSubset minus = split.getMinus();
345 EuclideanTestUtils.assertVertexLoopSequence(
346 Arrays.asList(Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0.5, 0.5, 1)),
347 minus.getVertices(), TEST_PRECISION);
348
349 final PlaneConvexSubset plus = split.getPlus();
350 EuclideanTestUtils.assertVertexLoopSequence(
351 Arrays.asList(Vector3D.of(0, 0, 1), Vector3D.of(0.5, 0.5, 1), Vector3D.of(0, 1, 1)),
352 plus.getVertices(), TEST_PRECISION);
353 }
354
355 @Test
356 void testSplit_neither() {
357
358 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, TRIANGLE_VERTICES);
359
360 final Plane splitter = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0, 1e-15, -1), TEST_PRECISION);
361
362
363 final Split<PlaneConvexSubset> split = p.split(splitter);
364
365
366 Assertions.assertEquals(SplitLocation.NEITHER, split.getLocation());
367
368 Assertions.assertNull(split.getMinus());
369 Assertions.assertNull(split.getPlus());
370 }
371
372 @Test
373 void testToString() {
374
375 final VertexListConvexPolygon3D p = new VertexListConvexPolygon3D(XY_PLANE_Z1, TRIANGLE_VERTICES);
376
377
378 final String str = p.toString();
379
380
381 GeometryTestUtils.assertContains("VertexListConvexPolygon3D[normal= (", str);
382 GeometryTestUtils.assertContains("vertices= [", str);
383 }
384
385 private static void checkPoints(final ConvexPolygon3D ps, final RegionLocation loc, final Vector3D... pts) {
386 for (final Vector3D pt : pts) {
387 Assertions.assertEquals(loc, ps.classify(pt), "Unexpected location for point " + pt);
388 }
389 }
390 }