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.GeometryTestUtils;
23 import org.apache.commons.geometry.core.RegionLocation;
24 import org.apache.commons.geometry.core.partitioning.Split;
25 import org.apache.commons.geometry.core.partitioning.SplitLocation;
26 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
27 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
28 import org.apache.commons.geometry.euclidean.twod.Vector2D;
29 import org.apache.commons.numbers.angle.Angle;
30 import org.apache.commons.numbers.core.Precision;
31 import org.junit.jupiter.api.Assertions;
32 import org.junit.jupiter.api.Test;
33
34 class SimpleTriangle3DTest {
35
36 private static final double TEST_EPS = 1e-10;
37
38 private static final Precision.DoubleEquivalence TEST_PRECISION =
39 Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
40
41 private static final Plane XY_PLANE_Z1 = Planes.fromPointAndPlaneVectors(Vector3D.of(0, 0, 1),
42 Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
43
44 @Test
45 void testProperties() {
46
47 final Vector3D p1 = Vector3D.of(1, 2, 1);
48 final Vector3D p2 = Vector3D.of(2, 2, 1);
49 final Vector3D p3 = Vector3D.of(2, 3, 1);
50
51
52 final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1, p1, p2, p3);
53
54
55 Assertions.assertFalse(tri.isFull());
56 Assertions.assertFalse(tri.isEmpty());
57 Assertions.assertFalse(tri.isInfinite());
58 Assertions.assertTrue(tri.isFinite());
59
60 Assertions.assertSame(XY_PLANE_Z1, tri.getPlane());
61 Assertions.assertSame(p1, tri.getPoint1());
62 Assertions.assertSame(p2, tri.getPoint2());
63 Assertions.assertSame(p3, tri.getPoint3());
64
65 Assertions.assertEquals(Arrays.asList(p1, p2, p3), tri.getVertices());
66
67 final List<Vector2D> subspaceVertices = tri.getEmbedded().getSubspaceRegion().getVertices();
68 Assertions.assertEquals(3, subspaceVertices.size());
69 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2), subspaceVertices.get(0), TEST_EPS);
70 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 2), subspaceVertices.get(1), TEST_EPS);
71 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 3), subspaceVertices.get(2), TEST_EPS);
72
73 Assertions.assertEquals(0.5, tri.getSize(), TEST_EPS);
74 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(5.0 / 3.0, 7.0 / 3.0, 1), tri.getCentroid(), TEST_EPS);
75
76 final Bounds3D bounds = tri.getBounds();
77 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 1), bounds.getMin(), TEST_EPS);
78 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 3, 1), bounds.getMax(), TEST_EPS);
79 }
80
81 @Test
82 void testVertices_listIsImmutable() {
83
84 final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
85 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
86
87
88 Assertions.assertThrows(UnsupportedOperationException.class, () -> tri.getVertices().add(Vector3D.of(-1, 0, 1)));
89 }
90
91 @Test
92 void testToTriangles() {
93
94 final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
95 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
96
97
98 final List<Triangle3D> triangles = tri.toTriangles();
99
100
101 Assertions.assertEquals(1, triangles.size());
102 Assertions.assertSame(tri, triangles.get(0));
103 }
104
105 @Test
106 void testGetSize() {
107
108 final QuaternionRotation rot = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, 0.2);
109
110
111 Assertions.assertEquals(0.5, new SimpleTriangle3D(XY_PLANE_Z1,
112 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1)).getSize(), TEST_EPS);
113
114 Assertions.assertEquals(1, new SimpleTriangle3D(XY_PLANE_Z1,
115 Vector3D.of(0, 0, 1), Vector3D.of(2, 0, 1), Vector3D.of(0, 1, 1)).getSize(), TEST_EPS);
116
117 Assertions.assertEquals(1.5, new SimpleTriangle3D(XY_PLANE_Z1,
118 Vector3D.of(1, 2, 1), Vector3D.of(4, 2, 1), Vector3D.of(2, 3, 1)).getSize(), TEST_EPS);
119
120 Assertions.assertEquals(1.5, new SimpleTriangle3D(XY_PLANE_Z1,
121 rot.applyVector(Vector3D.of(1, 2, 1)),
122 rot.apply(Vector3D.of(4, 2, 1)),
123 rot.applyVector(Vector3D.of(2, 3, 1))).getSize(), TEST_EPS);
124 }
125
126 @Test
127 void testClassify() {
128
129 final Vector3D p1 = Vector3D.of(1, 2, 1);
130 final Vector3D p2 = Vector3D.of(3, 2, 1);
131 final Vector3D p3 = Vector3D.of(2, 3, 1);
132
133 final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1, p1, p2, p3);
134
135
136 checkPoints(tri, RegionLocation.INSIDE, Vector3D.of(2, 2.5, 1), Vector3D.of(2, 2.5, 1 + 1e-15));
137 checkPoints(tri, RegionLocation.BOUNDARY,
138 p1, p2, p3,
139 p1.lerp(p2, 0.5), p2.lerp(p3, 0.5), p3.lerp(p1, 0.5));
140 checkPoints(tri, RegionLocation.OUTSIDE,
141 Vector3D.of(2, 2.5, 0), Vector3D.of(2, 2.5, 2),
142 Vector3D.of(0, 2, 1), Vector3D.of(4, 2, 1),
143 Vector3D.of(2, 4, 1), Vector3D.of(2, 1, 1));
144 }
145
146 @Test
147 void testClosest() {
148
149 final Vector3D p1 = Vector3D.of(1, 2, 1);
150 final Vector3D p2 = Vector3D.of(3, 2, 1);
151 final Vector3D p3 = Vector3D.of(2, 3, 1);
152
153 final Vector3D centroid = Vector3D.centroid(p1, p2, p3);
154
155 final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1, p1, p2, p3);
156
157
158 EuclideanTestUtils.assertCoordinatesEqual(centroid, tri.closest(centroid), TEST_EPS);
159 EuclideanTestUtils.assertCoordinatesEqual(centroid, tri.closest(centroid.add(Vector3D.Unit.PLUS_Z)), TEST_EPS);
160 EuclideanTestUtils.assertCoordinatesEqual(centroid, tri.closest(centroid.add(Vector3D.Unit.MINUS_Z)), TEST_EPS);
161
162 EuclideanTestUtils.assertCoordinatesEqual(p1, tri.closest(Vector3D.of(0, 2, 5)), TEST_EPS);
163 EuclideanTestUtils.assertCoordinatesEqual(p1, tri.closest(Vector3D.of(1, 2, 5)), TEST_EPS);
164 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 2, 1), tri.closest(Vector3D.of(2, 2, 5)), TEST_EPS);
165 EuclideanTestUtils.assertCoordinatesEqual(p2, tri.closest(Vector3D.of(3, 2, 5)), TEST_EPS);
166 EuclideanTestUtils.assertCoordinatesEqual(p2, tri.closest(Vector3D.of(4, 2, 5)), TEST_EPS);
167
168 EuclideanTestUtils.assertCoordinatesEqual(p1, tri.closest(Vector3D.of(0, 1, 5)), TEST_EPS);
169 EuclideanTestUtils.assertCoordinatesEqual(p1, tri.closest(Vector3D.of(1, 1, 5)), TEST_EPS);
170 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 2, 1), tri.closest(Vector3D.of(2, 1, 5)), TEST_EPS);
171 EuclideanTestUtils.assertCoordinatesEqual(p2, tri.closest(Vector3D.of(3, 1, 5)), TEST_EPS);
172 EuclideanTestUtils.assertCoordinatesEqual(p2, tri.closest(Vector3D.of(4, 1, 5)), TEST_EPS);
173
174 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1.5, 2.5, 1),
175 tri.closest(Vector3D.of(1, 3, -10)), TEST_EPS);
176 }
177
178 @Test
179 void testReverse() {
180
181 final Vector3D p1 = Vector3D.of(1, 2, 1);
182 final Vector3D p2 = Vector3D.of(3, 2, 1);
183 final Vector3D p3 = Vector3D.of(2, 3, 1);
184
185 final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1, p1, p2, p3);
186
187
188 final SimpleTriangle3D result = tri.reverse();
189
190
191 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_Z, result.getPlane().getNormal(), TEST_EPS);
192
193 Assertions.assertSame(p1, result.getPoint1());
194 Assertions.assertSame(p3, result.getPoint2());
195 Assertions.assertSame(p2, result.getPoint3());
196
197 final Vector3D v1 = result.getPoint1().vectorTo(result.getPoint2());
198 final Vector3D v2 = result.getPoint1().vectorTo(result.getPoint3());
199 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_Z, v1.cross(v2).normalize(), TEST_EPS);
200
201 Assertions.assertEquals(1, result.getSize(), TEST_EPS);
202 }
203
204 @Test
205 void testTransform() {
206
207 final Vector3D p1 = Vector3D.of(1, 2, 1);
208 final Vector3D p2 = Vector3D.of(3, 2, 1);
209 final Vector3D p3 = Vector3D.of(2, 3, 1);
210
211 final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1, p1, p2, p3);
212
213 final AffineTransformMatrix3D t = AffineTransformMatrix3D.identity()
214 .rotate(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Y, -Angle.PI_OVER_TWO))
215 .scale(1, 1, 2)
216 .translate(Vector3D.of(1, 0, 0));
217
218
219 final SimpleTriangle3D result = tri.transform(t);
220
221
222 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_X, result.getPlane().getNormal(), TEST_EPS);
223
224 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 2, 2), result.getPoint1(), TEST_EPS);
225 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 2, 6), result.getPoint2(), TEST_EPS);
226 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 3, 4), result.getPoint3(), TEST_EPS);
227
228 final Vector3D v1 = result.getPoint1().vectorTo(result.getPoint2());
229 final Vector3D v2 = result.getPoint1().vectorTo(result.getPoint3());
230 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.Unit.MINUS_X, v1.cross(v2).normalize(), TEST_EPS);
231
232 Assertions.assertEquals(2, result.getSize(), TEST_EPS);
233 }
234
235 @Test
236 void testSplit_plus() {
237
238 final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
239 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
240
241 final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_X, TEST_PRECISION);
242
243
244 final Split<PlaneConvexSubset> split = tri.split(splitter);
245
246
247 Assertions.assertEquals(SplitLocation.PLUS, split.getLocation());
248
249 Assertions.assertNull(split.getMinus());
250 Assertions.assertSame(tri, split.getPlus());
251 }
252
253 @Test
254 void testSplit_minus() {
255
256 final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
257 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
258
259 final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.MINUS_Z, TEST_PRECISION);
260
261
262 final Split<PlaneConvexSubset> split = tri.split(splitter);
263
264
265 Assertions.assertEquals(SplitLocation.MINUS, split.getLocation());
266
267 Assertions.assertSame(tri, split.getMinus());
268 Assertions.assertNull(split.getPlus());
269 }
270
271 @Test
272 void testSplit_both() {
273
274 final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
275 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
276
277 final Plane splitter = Planes.fromPointAndNormal(Vector3D.ZERO, Vector3D.of(-1, 1, 0), TEST_PRECISION);
278
279
280 final Split<PlaneConvexSubset> split = tri.split(splitter);
281
282
283 Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
284
285 final PlaneConvexSubset minus = split.getMinus();
286 EuclideanTestUtils.assertVertexLoopSequence(
287 Arrays.asList(Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0.5, 0.5, 1)),
288 minus.getVertices(), TEST_PRECISION);
289
290 final PlaneConvexSubset plus = split.getPlus();
291 EuclideanTestUtils.assertVertexLoopSequence(
292 Arrays.asList(Vector3D.of(0, 0, 1), Vector3D.of(0.5, 0.5, 1), Vector3D.of(0, 1, 1)),
293 plus.getVertices(), TEST_PRECISION);
294 }
295
296 @Test
297 void testSplit_neither() {
298
299 final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
300 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
301
302 final Plane splitter = Planes.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0, 1e-15, -1), TEST_PRECISION);
303
304
305 final Split<PlaneConvexSubset> split = tri.split(splitter);
306
307
308 Assertions.assertEquals(SplitLocation.NEITHER, split.getLocation());
309
310 Assertions.assertNull(split.getMinus());
311 Assertions.assertNull(split.getPlus());
312 }
313
314 @Test
315 void testToString() {
316
317 final SimpleTriangle3D tri = new SimpleTriangle3D(XY_PLANE_Z1,
318 Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1));
319
320
321 final String str = tri.toString();
322
323
324 GeometryTestUtils.assertContains("SimpleTriangle3D[normal= (", str);
325 GeometryTestUtils.assertContains("vertices= [", str);
326 }
327
328 private static void checkPoints(final ConvexPolygon3D ps, final RegionLocation loc, final Vector3D... pts) {
329 for (final Vector3D pt : pts) {
330 Assertions.assertEquals(loc, ps.classify(pt), "Unexpected location for point " + pt);
331 }
332 }
333 }