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