1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.euclidean.threed.shape;
18
19 import java.util.Arrays;
20 import java.util.Comparator;
21 import java.util.List;
22 import java.util.Set;
23 import java.util.TreeSet;
24
25 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
26 import org.apache.commons.geometry.euclidean.threed.AffineTransformMatrix3D;
27 import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
28 import org.apache.commons.geometry.euclidean.threed.RegionBSPTree3D;
29 import org.apache.commons.geometry.euclidean.threed.Vector3D;
30 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
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 ParallelepipedTest {
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 Comparator<Vector3D> VERTEX_COMPARATOR = (a, b) -> {
44 int cmp = TEST_PRECISION.compare(a.getX(), b.getX());
45 if (cmp == 0) {
46 cmp = TEST_PRECISION.compare(a.getY(), b.getY());
47 if (cmp == 0) {
48 cmp = TEST_PRECISION.compare(a.getZ(), b.getZ());
49 }
50 }
51 return cmp;
52 };
53
54 @Test
55 void testUnitCube() {
56
57 final Parallelepiped p = Parallelepiped.unitCube(TEST_PRECISION);
58
59
60 Assertions.assertEquals(1, p.getSize(), TEST_EPS);
61 Assertions.assertEquals(6, p.getBoundarySize(), TEST_EPS);
62 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, p.getCentroid(), TEST_EPS);
63
64 final List<PlaneConvexSubset> boundaries = p.getBoundaries();
65 Assertions.assertEquals(6, boundaries.size());
66
67 assertVertices(p,
68 Vector3D.of(-0.5, -0.5, -0.5),
69 Vector3D.of(0.5, -0.5, -0.5),
70 Vector3D.of(0.5, 0.5, -0.5),
71 Vector3D.of(-0.5, 0.5, -0.5),
72
73 Vector3D.of(-0.5, -0.5, 0.5),
74 Vector3D.of(0.5, -0.5, 0.5),
75 Vector3D.of(0.5, 0.5, 0.5),
76 Vector3D.of(-0.5, 0.5, 0.5)
77 );
78 }
79
80 @Test
81 void testFromTransformedUnitCube() {
82
83 final AffineTransformMatrix3D t = AffineTransformMatrix3D.createTranslation(Vector3D.of(1, 0, 2))
84 .rotate(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, Math.PI * 0.25))
85 .scale(Vector3D.of(2, 1, 1));
86
87
88 final Parallelepiped p = Parallelepiped.fromTransformedUnitCube(t, TEST_PRECISION);
89
90
91 final double sqrt2 = Math.sqrt(2);
92 final double invSqrt2 = 1 / sqrt2;
93
94 Assertions.assertEquals(2, p.getSize(), TEST_EPS);
95 Assertions.assertEquals(4 + (4 * Math.sqrt(2.5)), p.getBoundarySize(), TEST_EPS);
96 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2 * invSqrt2, invSqrt2, 2),
97 p.getCentroid(), TEST_EPS);
98
99 assertVertices(p,
100 Vector3D.of(0, invSqrt2, 1.5),
101 Vector3D.of(2 * invSqrt2, 0, 1.5),
102 Vector3D.of(2 * sqrt2, invSqrt2, 1.5),
103 Vector3D.of(2 * invSqrt2, sqrt2, 1.5),
104
105 Vector3D.of(0, invSqrt2, 2.5),
106 Vector3D.of(2 * invSqrt2, 0, 2.5),
107 Vector3D.of(2 * sqrt2, invSqrt2, 2.5),
108 Vector3D.of(2 * invSqrt2, sqrt2, 2.5)
109 );
110 }
111
112 @Test
113 void testFromTransformedUnitCube_transformDoesNotPreserveOrientation() {
114
115 final AffineTransformMatrix3D t = AffineTransformMatrix3D.createTranslation(Vector3D.of(1, 0, 2))
116 .rotate(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, Math.PI * 0.25))
117 .scale(Vector3D.of(2, 1, -1));
118
119
120 final Parallelepiped p = Parallelepiped.fromTransformedUnitCube(t, TEST_PRECISION);
121
122
123 final double sqrt2 = Math.sqrt(2);
124 final double invSqrt2 = 1 / sqrt2;
125
126 Assertions.assertEquals(2, p.getSize(), TEST_EPS);
127 Assertions.assertEquals(4 + (4 * Math.sqrt(2.5)), p.getBoundarySize(), TEST_EPS);
128 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2 * invSqrt2, invSqrt2, -2),
129 p.getCentroid(), TEST_EPS);
130
131 assertVertices(p,
132 Vector3D.of(0, invSqrt2, -1.5),
133 Vector3D.of(2 * invSqrt2, 0, -1.5),
134 Vector3D.of(2 * sqrt2, invSqrt2, -1.5),
135 Vector3D.of(2 * invSqrt2, sqrt2, -1.5),
136
137 Vector3D.of(0, invSqrt2, -2.5),
138 Vector3D.of(2 * invSqrt2, 0, -2.5),
139 Vector3D.of(2 * sqrt2, invSqrt2, -2.5),
140 Vector3D.of(2 * invSqrt2, sqrt2, -2.5)
141 );
142 }
143
144 @Test
145 void testFromTransformedUnitCube_zeroSizeRegion() {
146
147 Assertions.assertThrows(IllegalArgumentException.class, () -> Parallelepiped.fromTransformedUnitCube(AffineTransformMatrix3D.createScale(Vector3D.of(1e-16, 1, 1)),
148 TEST_PRECISION));
149 Assertions.assertThrows(IllegalArgumentException.class, () -> Parallelepiped.fromTransformedUnitCube(AffineTransformMatrix3D.createScale(Vector3D.of(1, 1e-16, 1)),
150 TEST_PRECISION));
151 Assertions.assertThrows(IllegalArgumentException.class, () -> Parallelepiped.fromTransformedUnitCube(AffineTransformMatrix3D.createScale(Vector3D.of(1, 1, 1e-16)),
152 TEST_PRECISION));
153 }
154
155 @Test
156 void testAxisAligned_minFirst() {
157
158 final Parallelepiped p = Parallelepiped.axisAligned(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, 6), TEST_PRECISION);
159
160
161 final List<PlaneConvexSubset> boundaries = p.getBoundaries();
162 Assertions.assertEquals(6, boundaries.size());
163
164 assertVertices(p,
165 Vector3D.of(1, 2, 3),
166 Vector3D.of(4, 2, 3),
167 Vector3D.of(4, 5, 3),
168 Vector3D.of(1, 5, 3),
169
170 Vector3D.of(1, 2, 6),
171 Vector3D.of(4, 2, 6),
172 Vector3D.of(4, 5, 6),
173 Vector3D.of(1, 5, 6)
174 );
175 }
176
177 @Test
178 void testAxisAligned_maxFirst() {
179
180 final Parallelepiped p = Parallelepiped.axisAligned(Vector3D.of(4, 5, 6), Vector3D.of(1, 2, 3), TEST_PRECISION);
181
182
183 final List<PlaneConvexSubset> boundaries = p.getBoundaries();
184 Assertions.assertEquals(6, boundaries.size());
185
186 assertVertices(p,
187 Vector3D.of(1, 2, 3),
188 Vector3D.of(4, 2, 3),
189 Vector3D.of(4, 5, 3),
190 Vector3D.of(1, 5, 3),
191
192 Vector3D.of(1, 2, 6),
193 Vector3D.of(4, 2, 6),
194 Vector3D.of(4, 5, 6),
195 Vector3D.of(1, 5, 6)
196 );
197 }
198
199 @Test
200 void testAxisAligned_illegalArgs() {
201
202 Assertions.assertThrows(IllegalArgumentException.class, () -> Parallelepiped.axisAligned(Vector3D.of(1, 2, 3), Vector3D.of(1, 5, 6), TEST_PRECISION));
203 Assertions.assertThrows(IllegalArgumentException.class, () -> Parallelepiped.axisAligned(Vector3D.of(1, 2, 3), Vector3D.of(4, 2, 6), TEST_PRECISION));
204 Assertions.assertThrows(IllegalArgumentException.class, () -> Parallelepiped.axisAligned(Vector3D.of(1, 2, 3), Vector3D.of(1, 5, 3), TEST_PRECISION));
205 }
206
207 @Test
208 void testBuilder_defaultValues() {
209
210 final Parallelepiped.Builder builder = Parallelepiped.builder(TEST_PRECISION);
211
212
213 final Parallelepiped p = builder.build();
214
215
216 Assertions.assertEquals(1, p.getSize(), TEST_EPS);
217 Assertions.assertEquals(6, p.getBoundarySize(), TEST_EPS);
218 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, p.getCentroid(), TEST_EPS);
219
220 final List<PlaneConvexSubset> boundaries = p.getBoundaries();
221 Assertions.assertEquals(6, boundaries.size());
222
223 assertVertices(p,
224 Vector3D.of(-0.5, -0.5, -0.5),
225 Vector3D.of(0.5, -0.5, -0.5),
226 Vector3D.of(0.5, 0.5, -0.5),
227 Vector3D.of(-0.5, 0.5, -0.5),
228
229 Vector3D.of(-0.5, -0.5, 0.5),
230 Vector3D.of(0.5, -0.5, 0.5),
231 Vector3D.of(0.5, 0.5, 0.5),
232 Vector3D.of(-0.5, 0.5, 0.5)
233 );
234 }
235
236 @Test
237 void testBuilder_withRotation() {
238
239 final Parallelepiped.Builder builder = Parallelepiped.builder(TEST_PRECISION);
240
241
242 final Parallelepiped p = builder
243 .setScale(1, 2, 3)
244 .setRotation(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, Angle.PI_OVER_TWO))
245 .setPosition(Vector3D.of(1, 2, -1))
246 .build();
247
248
249 Assertions.assertEquals(6, p.getSize(), TEST_EPS);
250 Assertions.assertEquals(22, p.getBoundarySize(), TEST_EPS);
251 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, -1), p.getCentroid(), TEST_EPS);
252
253 assertVertices(p,
254 Vector3D.of(0, 1.5, 0.5),
255 Vector3D.of(2, 1.5, 0.5),
256 Vector3D.of(2, 2.5, 0.5),
257 Vector3D.of(0, 2.5, 0.5),
258
259 Vector3D.of(0, 1.5, -2.5),
260 Vector3D.of(2, 1.5, -2.5),
261 Vector3D.of(2, 2.5, -2.5),
262 Vector3D.of(0, 2.5, -2.5)
263 );
264 }
265
266 @Test
267 void testBuilder_withUniformScale() {
268
269 final Parallelepiped.Builder builder = Parallelepiped.builder(TEST_PRECISION);
270
271
272 final Parallelepiped p = builder
273 .setScale(0.5)
274 .build();
275
276
277 Assertions.assertEquals(0.125, p.getSize(), TEST_EPS);
278 Assertions.assertEquals(1.5, p.getBoundarySize(), TEST_EPS);
279 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, p.getCentroid(), TEST_EPS);
280
281 assertVertices(p,
282 Vector3D.of(-0.25, -0.25, -0.25),
283 Vector3D.of(0.25, -0.25, -0.25),
284 Vector3D.of(0.25, 0.25, -0.25),
285 Vector3D.of(-0.25, 0.25, -0.25),
286
287 Vector3D.of(-0.25, -0.25, 0.25),
288 Vector3D.of(0.25, -0.25, 0.25),
289 Vector3D.of(0.25, 0.25, 0.25),
290 Vector3D.of(-0.25, 0.25, 0.25)
291 );
292 }
293
294 @Test
295 void testToTree() {
296
297 final Parallelepiped p = Parallelepiped.axisAligned(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, 6), TEST_PRECISION);
298
299
300 final RegionBSPTree3D tree = p.toTree();
301
302
303 Assertions.assertEquals(27, tree.getSize(), TEST_EPS);
304 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2.5, 3.5, 4.5), tree.getCentroid(), TEST_EPS);
305 }
306
307 private static void assertVertices(final Parallelepiped p, final Vector3D... vertices) {
308 final Set<Vector3D> expectedVertices = new TreeSet<>(VERTEX_COMPARATOR);
309 expectedVertices.addAll(Arrays.asList(vertices));
310
311 final Set<Vector3D> actualVertices = new TreeSet<>(VERTEX_COMPARATOR);
312 for (final PlaneConvexSubset boundary : p.getBoundaries()) {
313 actualVertices.addAll(boundary.getVertices());
314 }
315
316 Assertions.assertEquals(expectedVertices.size(), actualVertices.size());
317 for (final Vector3D expected : expectedVertices) {
318 Assertions.assertTrue(actualVertices.contains(expected), "Expected vertices to contain " + expected);
319 }
320 }
321 }