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