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.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          // act
57          final Parallelepiped p = Parallelepiped.unitCube(TEST_PRECISION);
58  
59          // assert
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          // arrange
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          // act
88          final Parallelepiped p = Parallelepiped.fromTransformedUnitCube(t, TEST_PRECISION);
89  
90          // assert
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         // arrange
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         // act
120         final Parallelepiped p = Parallelepiped.fromTransformedUnitCube(t, TEST_PRECISION);
121 
122         // assert
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         // act/assert
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         // act
158         final Parallelepiped p = Parallelepiped.axisAligned(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, 6), TEST_PRECISION);
159 
160         // assert
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         // act
180         final Parallelepiped p = Parallelepiped.axisAligned(Vector3D.of(4, 5, 6), Vector3D.of(1, 2, 3), TEST_PRECISION);
181 
182         // assert
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         // act/assert
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         // arrange
210         final Parallelepiped.Builder builder = Parallelepiped.builder(TEST_PRECISION);
211 
212         // act
213         final Parallelepiped p = builder.build();
214 
215         // assert
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         // arrange
239         final Parallelepiped.Builder builder = Parallelepiped.builder(TEST_PRECISION);
240 
241         // act
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         // assert
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         // arrange
269         final Parallelepiped.Builder builder = Parallelepiped.builder(TEST_PRECISION);
270 
271         // act
272         final Parallelepiped p = builder
273                 .setScale(0.5)
274                 .build();
275 
276         // assert
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         // arrange
297         final Parallelepiped p = Parallelepiped.axisAligned(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, 6), TEST_PRECISION);
298 
299         // act
300         final RegionBSPTree3D tree = p.toTree();
301 
302         // assert
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 }