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;
18  
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.function.BiFunction;
23  import java.util.function.ToDoubleFunction;
24  import java.util.regex.Pattern;
25  
26  import org.apache.commons.geometry.core.GeometryTestUtils;
27  import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
28  import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
29  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
30  import org.apache.commons.geometry.euclidean.threed.shape.Parallelepiped;
31  import org.junit.Assert;
32  import org.junit.Test;
33  
34  public class Bounds3DTest {
35  
36      private static final double TEST_EPS = 1e-10;
37  
38      private static final DoublePrecisionContext TEST_PRECISION =
39              new EpsilonDoublePrecisionContext(TEST_EPS);
40  
41      private static final String NO_POINTS_MESSAGE = "Cannot construct bounds: no points given";
42  
43      private static final Pattern INVALID_BOUNDS_PATTERN =
44              Pattern.compile("^Invalid bounds: min= \\([^\\)]+\\), max= \\([^\\)]+\\)");
45  
46      @Test
47      public void testFrom_varargs_singlePoint() {
48          // arrange
49          final Vector3D p1 = Vector3D.of(-1, 2, -3);
50  
51          // act
52          final Bounds3D b = Bounds3D.from(p1);
53  
54          // assert
55          EuclideanTestUtils.assertCoordinatesEqual(p1, b.getMin(), TEST_EPS);
56          EuclideanTestUtils.assertCoordinatesEqual(p1, b.getMax(), TEST_EPS);
57          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, b.getDiagonal(), TEST_EPS);
58          EuclideanTestUtils.assertCoordinatesEqual(p1, b.getCentroid(), TEST_EPS);
59      }
60  
61      @Test
62      public void testFrom_varargs_multiplePoints() {
63          // arrange
64          final Vector3D p1 = Vector3D.of(1, 6, 7);
65          final Vector3D p2 = Vector3D.of(0, 5, 11);
66          final Vector3D p3 = Vector3D.of(3, 6, 8);
67  
68          // act
69          final Bounds3D b = Bounds3D.from(p1, p2, p3);
70  
71          // assert
72          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 5, 7), b.getMin(), TEST_EPS);
73          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 6, 11), b.getMax(), TEST_EPS);
74          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 1, 4), b.getDiagonal(), TEST_EPS);
75          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1.5, 5.5, 9), b.getCentroid(), TEST_EPS);
76      }
77  
78      @Test
79      public void testFrom_iterable_singlePoint() {
80          // arrange
81          final Vector3D p1 = Vector3D.of(-1, 2, -3);
82  
83          // act
84          final Bounds3D b = Bounds3D.from(Collections.singletonList(p1));
85  
86          // assert
87          EuclideanTestUtils.assertCoordinatesEqual(p1, b.getMin(), TEST_EPS);
88          EuclideanTestUtils.assertCoordinatesEqual(p1, b.getMax(), TEST_EPS);
89          EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, b.getDiagonal(), TEST_EPS);
90          EuclideanTestUtils.assertCoordinatesEqual(p1, b.getCentroid(), TEST_EPS);
91      }
92  
93      @Test
94      public void testFrom_iterable_multiplePoints() {
95          // arrange
96          final Vector3D p1 = Vector3D.of(1, 6, 7);
97          final Vector3D p2 = Vector3D.of(2, 5, 9);
98          final Vector3D p3 = Vector3D.of(3, 4, 8);
99  
100         // act
101         final Bounds3D b = Bounds3D.from(Arrays.asList(p1, p2, p3));
102 
103         // assert
104         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 4, 7), b.getMin(), TEST_EPS);
105         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 6, 9), b.getMax(), TEST_EPS);
106         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 2, 2), b.getDiagonal(), TEST_EPS);
107         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2, 5, 8), b.getCentroid(), TEST_EPS);
108     }
109 
110     @Test
111     public void testFrom_iterable_noPoints() {
112         // act/assert
113         GeometryTestUtils.assertThrows(() -> {
114             Bounds3D.from(new ArrayList<>());
115         }, IllegalStateException.class, NO_POINTS_MESSAGE);
116     }
117 
118     @Test
119     public void testFrom_invalidBounds() {
120         // arrange
121         final Vector3D good = Vector3D.of(1, 1, 1);
122 
123         final Vector3D nan = Vector3D.of(Double.NaN, 1, 1);
124         final Vector3D posInf = Vector3D.of(1, Double.POSITIVE_INFINITY, 1);
125         final Vector3D negInf = Vector3D.of(1, 1, Double.NEGATIVE_INFINITY);
126 
127         // act/assert
128         GeometryTestUtils.assertThrows(() -> {
129             Bounds3D.from(Vector3D.NaN);
130         }, IllegalStateException.class, INVALID_BOUNDS_PATTERN);
131 
132         GeometryTestUtils.assertThrows(() -> {
133             Bounds3D.from(Vector3D.POSITIVE_INFINITY);
134         }, IllegalStateException.class, INVALID_BOUNDS_PATTERN);
135 
136         GeometryTestUtils.assertThrows(() -> {
137             Bounds3D.from(Vector3D.NEGATIVE_INFINITY);
138         }, IllegalStateException.class, INVALID_BOUNDS_PATTERN);
139 
140         GeometryTestUtils.assertThrows(() -> {
141             Bounds3D.from(good, nan);
142         }, IllegalStateException.class, INVALID_BOUNDS_PATTERN);
143 
144         GeometryTestUtils.assertThrows(() -> {
145             Bounds3D.from(posInf, good);
146         }, IllegalStateException.class, INVALID_BOUNDS_PATTERN);
147 
148         GeometryTestUtils.assertThrows(() -> {
149             Bounds3D.from(good, negInf, good);
150         }, IllegalStateException.class, INVALID_BOUNDS_PATTERN);
151     }
152 
153     @Test
154     public void testHasSize() {
155         // arrange
156         final DoublePrecisionContext low = new EpsilonDoublePrecisionContext(1e-2);
157         final DoublePrecisionContext high = new EpsilonDoublePrecisionContext(1e-10);
158 
159         final Vector3D p1 = Vector3D.ZERO;
160 
161         final Vector3D p2 = Vector3D.of(1e-5, 1, 1);
162         final Vector3D p3 = Vector3D.of(1, 1e-5, 1);
163         final Vector3D p4 = Vector3D.of(1, 1, 1e-5);
164 
165         final Vector3D p5 = Vector3D.of(1, 1, 1);
166 
167         // act/assert
168         Assert.assertFalse(Bounds3D.from(p1).hasSize(high));
169         Assert.assertFalse(Bounds3D.from(p1).hasSize(low));
170 
171         Assert.assertTrue(Bounds3D.from(p1, p2).hasSize(high));
172         Assert.assertFalse(Bounds3D.from(p1, p2).hasSize(low));
173 
174         Assert.assertTrue(Bounds3D.from(p1, p3).hasSize(high));
175         Assert.assertFalse(Bounds3D.from(p1, p3).hasSize(low));
176 
177         Assert.assertTrue(Bounds3D.from(p1, p4).hasSize(high));
178         Assert.assertFalse(Bounds3D.from(p1, p4).hasSize(low));
179 
180         Assert.assertTrue(Bounds3D.from(p1, p5).hasSize(high));
181         Assert.assertTrue(Bounds3D.from(p1, p5).hasSize(low));
182     }
183 
184     @Test
185     public void testContains_strict() {
186         // arrange
187         final Bounds3D b = Bounds3D.from(
188                 Vector3D.of(0, 4, 8),
189                 Vector3D.of(2, 6, 10));
190 
191         // act/assert
192         assertContainsStrict(b, true,
193                 b.getCentroid(),
194                 Vector3D.of(0, 4, 8), Vector3D.of(2, 6, 10),
195                 Vector3D.of(1, 5, 9),
196                 Vector3D.of(0, 5, 9), Vector3D.of(2, 5, 9),
197                 Vector3D.of(1, 4, 9), Vector3D.of(1, 6, 9),
198                 Vector3D.of(1, 5, 8), Vector3D.of(1, 5, 10));
199 
200         assertContainsStrict(b, false,
201                 Vector3D.ZERO,
202                 Vector3D.of(-1, 5, 9), Vector3D.of(3, 5, 9),
203                 Vector3D.of(1, 3, 9), Vector3D.of(1, 7, 9),
204                 Vector3D.of(1, 5, 7), Vector3D.of(1, 5, 11),
205                 Vector3D.of(-1e-15, 4, 8), Vector3D.of(2, 6 + 1e-15, 10), Vector3D.of(0, 4, 10 + 1e-15));
206     }
207 
208     @Test
209     public void testContains_precision() {
210         // arrange
211         final Bounds3D b = Bounds3D.from(
212                 Vector3D.of(0, 4, 8),
213                 Vector3D.of(2, 6, 10));
214 
215         // act/assert
216         assertContainsWithPrecision(b, true,
217                 b.getCentroid(),
218                 Vector3D.of(0, 4, 8), Vector3D.of(2, 6, 10),
219                 Vector3D.of(1, 5, 9),
220                 Vector3D.of(0, 5, 9), Vector3D.of(2, 5, 9),
221                 Vector3D.of(1, 4, 9), Vector3D.of(1, 6, 9),
222                 Vector3D.of(1, 5, 8), Vector3D.of(1, 5, 10),
223                 Vector3D.of(-1e-15, 4, 8), Vector3D.of(2, 6 + 1e-15, 10), Vector3D.of(0, 4, 10 + 1e-15));
224 
225         assertContainsWithPrecision(b, false,
226                 Vector3D.ZERO,
227                 Vector3D.of(-1, 5, 9), Vector3D.of(3, 5, 9),
228                 Vector3D.of(1, 3, 9), Vector3D.of(1, 7, 9),
229                 Vector3D.of(1, 5, 7), Vector3D.of(1, 5, 11));
230     }
231 
232     @Test
233     public void testIntersects() {
234         // arrange
235         final Bounds3D b = Bounds3D.from(Vector3D.ZERO, Vector3D.of(1, 1, 1));
236 
237         // act/assert
238         checkIntersects(b, Vector3D::getX, (v, x) -> Vector3D.of(x, v.getY(), v.getZ()));
239         checkIntersects(b, Vector3D::getY, (v, y) -> Vector3D.of(v.getX(), y, v.getZ()));
240         checkIntersects(b, Vector3D::getZ, (v, z) -> Vector3D.of(v.getX(), v.getY(), z));
241     }
242 
243     private void checkIntersects(final Bounds3D b, final ToDoubleFunction<Vector3D> getter,
244                                  final BiFunction<Vector3D, Double, Vector3D> setter) {
245 
246         final Vector3D min = b.getMin();
247         final Vector3D max = b.getMax();
248 
249         final double minValue = getter.applyAsDouble(min);
250         final double maxValue = getter.applyAsDouble(max);
251         final double midValue = (0.5 * (maxValue - minValue)) + minValue;
252 
253         // check all possible interval relationships
254 
255         // start below minValue
256         Assert.assertFalse(b.intersects(Bounds3D.from(
257                 setter.apply(min, minValue - 2), setter.apply(max, minValue - 1))));
258 
259         Assert.assertTrue(b.intersects(Bounds3D.from(
260                 setter.apply(min, minValue - 2), setter.apply(max, minValue))));
261         Assert.assertTrue(b.intersects(Bounds3D.from(
262                 setter.apply(min, minValue - 2), setter.apply(max, midValue))));
263         Assert.assertTrue(b.intersects(Bounds3D.from(
264                 setter.apply(min, minValue - 2), setter.apply(max, maxValue))));
265         Assert.assertTrue(b.intersects(Bounds3D.from(
266                 setter.apply(min, minValue - 2), setter.apply(max, maxValue + 1))));
267 
268         // start on minValue
269         Assert.assertTrue(b.intersects(Bounds3D.from(
270                 setter.apply(min, minValue), setter.apply(max, minValue))));
271         Assert.assertTrue(b.intersects(Bounds3D.from(
272                 setter.apply(min, minValue), setter.apply(max, midValue))));
273         Assert.assertTrue(b.intersects(Bounds3D.from(
274                 setter.apply(min, minValue), setter.apply(max, maxValue))));
275         Assert.assertTrue(b.intersects(Bounds3D.from(
276                 setter.apply(min, minValue), setter.apply(max, maxValue + 1))));
277 
278         // start on midValue
279         Assert.assertTrue(b.intersects(Bounds3D.from(
280                 setter.apply(min, midValue), setter.apply(max, midValue))));
281         Assert.assertTrue(b.intersects(Bounds3D.from(
282                 setter.apply(min, midValue), setter.apply(max, maxValue))));
283         Assert.assertTrue(b.intersects(Bounds3D.from(
284                 setter.apply(min, midValue), setter.apply(max, maxValue + 1))));
285 
286         // start on maxValue
287         Assert.assertTrue(b.intersects(Bounds3D.from(
288                 setter.apply(min, maxValue), setter.apply(max, maxValue))));
289         Assert.assertTrue(b.intersects(Bounds3D.from(
290                 setter.apply(min, maxValue), setter.apply(max, maxValue + 1))));
291 
292         // start above maxValue
293         Assert.assertFalse(b.intersects(Bounds3D.from(
294                 setter.apply(min, maxValue + 1), setter.apply(max, maxValue + 2))));
295     }
296 
297     @Test
298     public void testIntersection() {
299         // -- arrange
300         final Bounds3D b = Bounds3D.from(Vector3D.ZERO, Vector3D.of(1, 1, 1));
301 
302         // -- act/assert
303 
304         // move along x-axis
305         Assert.assertNull(b.intersection(Bounds3D.from(Vector3D.of(-2, 0, 0), Vector3D.of(-1, 1, 1))));
306         checkIntersection(b, Vector3D.of(-1, 0, 0), Vector3D.of(0, 1, 1),
307                 Vector3D.of(0, 0, 0), Vector3D.of(0, 1, 1));
308         checkIntersection(b, Vector3D.of(-1, 0, 0), Vector3D.of(0.5, 1, 1),
309                 Vector3D.of(0, 0, 0), Vector3D.of(0.5, 1, 1));
310         checkIntersection(b, Vector3D.of(-1, 0, 0), Vector3D.of(1, 1, 1),
311                 Vector3D.of(0, 0, 0), Vector3D.of(1, 1, 1));
312         checkIntersection(b, Vector3D.of(-1, 0, 0), Vector3D.of(2, 1, 1),
313                 Vector3D.of(0, 0, 0), Vector3D.of(1, 1, 1));
314         checkIntersection(b, Vector3D.of(0, 0, 0), Vector3D.of(2, 1, 1),
315                 Vector3D.of(0, 0, 0), Vector3D.of(1, 1, 1));
316         checkIntersection(b, Vector3D.of(0.5, 0, 0), Vector3D.of(2, 1, 1),
317                 Vector3D.of(0.5, 0, 0), Vector3D.of(1, 1, 1));
318         checkIntersection(b, Vector3D.of(1, 0, 0), Vector3D.of(2, 1, 1),
319                 Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 1));
320         Assert.assertNull(b.intersection(Bounds3D.from(Vector3D.of(2, 0, 0), Vector3D.of(3, 1, 1))));
321 
322         // move along y-axis
323         Assert.assertNull(b.intersection(Bounds3D.from(Vector3D.of(0, -2, 0), Vector3D.of(1, -1, 1))));
324         checkIntersection(b, Vector3D.of(0, -1, 0), Vector3D.of(1, 0, 1),
325                 Vector3D.of(0, 0, 0), Vector3D.of(1, 0, 1));
326         checkIntersection(b, Vector3D.of(0, -1, 0), Vector3D.of(1, 0.5, 1),
327                 Vector3D.of(0, 0, 0), Vector3D.of(1, 0.5, 1));
328         checkIntersection(b, Vector3D.of(0, -1, 0), Vector3D.of(1, 1, 1),
329                 Vector3D.of(0, 0, 0), Vector3D.of(1, 1, 1));
330         checkIntersection(b, Vector3D.of(0, -1, 0), Vector3D.of(1, 2, 1),
331                 Vector3D.of(0, 0, 0), Vector3D.of(1, 1, 1));
332         checkIntersection(b, Vector3D.of(0, 0, 0), Vector3D.of(1, 2, 1),
333                 Vector3D.of(0, 0, 0), Vector3D.of(1, 1, 1));
334         checkIntersection(b, Vector3D.of(0, 0.5, 0), Vector3D.of(1, 2, 1),
335                 Vector3D.of(0, 0.5, 0), Vector3D.of(1, 1, 1));
336         checkIntersection(b, Vector3D.of(0, 1, 0), Vector3D.of(1, 2, 1),
337                 Vector3D.of(0, 1, 0), Vector3D.of(1, 1, 1));
338         Assert.assertNull(b.intersection(Bounds3D.from(Vector3D.of(0, 2, 0), Vector3D.of(1, 3, 1))));
339 
340         // move along z-axis
341         Assert.assertNull(b.intersection(Bounds3D.from(Vector3D.of(0, 0, -2), Vector3D.of(1, 1, -1))));
342         checkIntersection(b, Vector3D.of(0, 0, -1), Vector3D.of(1, 1, 0),
343                 Vector3D.of(0, 0, 0), Vector3D.of(1, 1, 0));
344         checkIntersection(b, Vector3D.of(0, 0, -1), Vector3D.of(1, 1, 0.5),
345                 Vector3D.of(0, 0, 0), Vector3D.of(1, 1, 0.5));
346         checkIntersection(b, Vector3D.of(0, 0, -1), Vector3D.of(1, 1, 1),
347                 Vector3D.of(0, 0, 0), Vector3D.of(1, 1, 1));
348         checkIntersection(b, Vector3D.of(0, 0, -1), Vector3D.of(1, 1, 2),
349                 Vector3D.of(0, 0, 0), Vector3D.of(1, 1, 1));
350         checkIntersection(b, Vector3D.of(0, 0, 0), Vector3D.of(1, 1, 2),
351                 Vector3D.of(0, 0, 0), Vector3D.of(1, 1, 1));
352         checkIntersection(b, Vector3D.of(0, 0, 0.5), Vector3D.of(1, 1, 2),
353                 Vector3D.of(0, 0, 0.5), Vector3D.of(1, 1, 1));
354         checkIntersection(b, Vector3D.of(0, 0, 1), Vector3D.of(1, 1, 2),
355                 Vector3D.of(0, 0, 1), Vector3D.of(1, 1, 1));
356         Assert.assertNull(b.intersection(Bounds3D.from(Vector3D.of(0, 0, 2), Vector3D.of(1, 1, 3))));
357     }
358 
359     private void checkIntersection(final Bounds3D b, final Vector3D a1, final Vector3D a2, final Vector3D r1, final Vector3D r2) {
360         final Bounds3D a = Bounds3D.from(a1, a2);
361         final Bounds3D result = b.intersection(a);
362 
363         checkBounds(result, r1, r2);
364     }
365 
366     @Test
367     public void toRegion() {
368         // arrange
369         final Bounds3D b = Bounds3D.from(
370                 Vector3D.of(0, 4, 8),
371                 Vector3D.of(2, 6, 10));
372 
373         // act
374         final Parallelepiped p = b.toRegion(TEST_PRECISION);
375 
376         // assert
377         Assert.assertEquals(8, p.getSize(), TEST_EPS);
378         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 5, 9), p.getCentroid(), TEST_EPS);
379     }
380 
381     @Test
382     public void toRegion_boundingBoxTooSmall() {
383         // act/assert
384         GeometryTestUtils.assertThrows(() -> {
385             Bounds3D.from(Vector3D.ZERO, Vector3D.of(1e-12, 1e-12, 1e-12))
386                 .toRegion(TEST_PRECISION);
387         }, IllegalArgumentException.class);
388     }
389 
390     @Test
391     public void testEq() {
392         // arrange
393         final DoublePrecisionContext low = new EpsilonDoublePrecisionContext(1e-2);
394         final DoublePrecisionContext high = new EpsilonDoublePrecisionContext(1e-10);
395 
396         final Bounds3D b1 = Bounds3D.from(Vector3D.of(1, 1, 1), Vector3D.of(2, 2, 2));
397 
398         final Bounds3D b2 = Bounds3D.from(Vector3D.of(1.1, 1, 1), Vector3D.of(2, 2, 2));
399         final Bounds3D b3 = Bounds3D.from(Vector3D.of(1, 1, 1), Vector3D.of(1.9, 2, 2));
400 
401         final Bounds3D b4 = Bounds3D.from(Vector3D.of(1.001, 1.001, 1.001), Vector3D.of(2.001, 2.001, 2.001));
402 
403         // act/assert
404         Assert.assertTrue(b1.eq(b1, low));
405 
406         Assert.assertFalse(b1.eq(b2, low));
407         Assert.assertFalse(b1.eq(b3, low));
408 
409         Assert.assertTrue(b1.eq(b4, low));
410         Assert.assertTrue(b4.eq(b1, low));
411 
412         Assert.assertFalse(b1.eq(b4, high));
413         Assert.assertFalse(b4.eq(b1, high));
414     }
415 
416     @Test
417     public void testHashCode() {
418         // arrange
419         final Bounds3D b1 = Bounds3D.from(Vector3D.of(1, 1, 1), Vector3D.of(2, 2, 2));
420 
421         final Bounds3D b2 = Bounds3D.from(Vector3D.of(-2, 1, 1), Vector3D.of(2, 2, 2));
422         final Bounds3D b3 = Bounds3D.from(Vector3D.of(1, 1, 1), Vector3D.of(3, 2, 2));
423         final Bounds3D b4 = Bounds3D.from(Vector3D.of(1 + 1e-15, 1, 1), Vector3D.of(2, 2, 2));
424         final Bounds3D b5 = Bounds3D.from(Vector3D.of(1, 1, 1), Vector3D.of(2 + 1e-15, 2, 2));
425 
426         final Bounds3D b6 = Bounds3D.from(Vector3D.of(1, 1, 1), Vector3D.of(2, 2, 2));
427 
428         // act
429         final int hash = b1.hashCode();
430 
431         // assert
432         Assert.assertEquals(hash, b1.hashCode());
433 
434         Assert.assertNotEquals(hash, b2.hashCode());
435         Assert.assertNotEquals(hash, b3.hashCode());
436         Assert.assertNotEquals(hash, b4.hashCode());
437         Assert.assertNotEquals(hash, b5.hashCode());
438 
439         Assert.assertEquals(hash, b6.hashCode());
440     }
441 
442     @Test
443     public void testEquals() {
444         // arrange
445         final Bounds3D b1 = Bounds3D.from(Vector3D.of(1, 1, 1), Vector3D.of(2, 2, 2));
446 
447         final Bounds3D b2 = Bounds3D.from(Vector3D.of(-1, 1, 1), Vector3D.of(2, 2, 2));
448         final Bounds3D b3 = Bounds3D.from(Vector3D.of(1, 1, 1), Vector3D.of(3, 2, 2));
449         final Bounds3D b4 = Bounds3D.from(Vector3D.of(1 + 1e-15, 1, 1), Vector3D.of(2, 2, 2));
450         final Bounds3D b5 = Bounds3D.from(Vector3D.of(1, 1, 1), Vector3D.of(2 + 1e-15, 2, 2));
451 
452         final Bounds3D b6 = Bounds3D.from(Vector3D.of(1, 1, 1), Vector3D.of(2, 2, 2));
453 
454         // act/assert
455         Assert.assertEquals(b1, b1);
456 
457         Assert.assertFalse(b1.equals(null));
458         Assert.assertFalse(b1.equals(new Object()));
459 
460         Assert.assertNotEquals(b1, b2);
461         Assert.assertNotEquals(b1, b3);
462         Assert.assertNotEquals(b1, b4);
463         Assert.assertNotEquals(b1, b5);
464 
465         Assert.assertEquals(b1, b6);
466     }
467 
468     @Test
469     public void testToString() {
470         // arrange
471         final Bounds3D b = Bounds3D.from(Vector3D.of(1, 1, 1), Vector3D.of(2, 2, 2));
472 
473         // act
474         final String str = b.toString();
475 
476         // assert
477         GeometryTestUtils.assertContains("Bounds3D[min= (1", str);
478         GeometryTestUtils.assertContains(", max= (2", str);
479     }
480 
481     @Test
482     public void testBuilder_addMethods() {
483         // arrange
484         final Vector3D p1 = Vector3D.of(1, 10, 11);
485         final Vector3D p2 = Vector3D.of(2, 9, 12);
486         final Vector3D p3 = Vector3D.of(3, 8, 13);
487         final Vector3D p4 = Vector3D.of(4, 7, 14);
488         final Vector3D p5 = Vector3D.of(5, 6, 15);
489 
490         // act
491         final Bounds3D b = Bounds3D.builder()
492                 .add(p1)
493                 .addAll(Arrays.asList(p2, p3))
494                 .add(Bounds3D.from(p4, p5))
495                 .build();
496 
497         // assert
498         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 6, 11), b.getMin(), TEST_EPS);
499         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(5, 10, 15), b.getMax(), TEST_EPS);
500         EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 8, 13), b.getCentroid(), TEST_EPS);
501     }
502 
503     @Test
504     public void testBuilder_hasBounds() {
505         // act/assert
506         Assert.assertFalse(Bounds3D.builder().hasBounds());
507 
508         Assert.assertFalse(Bounds3D.builder().add(Vector3D.of(Double.NaN, 1, 1)).hasBounds());
509         Assert.assertFalse(Bounds3D.builder().add(Vector3D.of(1, Double.NaN, 1)).hasBounds());
510         Assert.assertFalse(Bounds3D.builder().add(Vector3D.of(1, 1, Double.NaN)).hasBounds());
511 
512         Assert.assertFalse(Bounds3D.builder().add(Vector3D.of(Double.POSITIVE_INFINITY, 1, 1)).hasBounds());
513         Assert.assertFalse(Bounds3D.builder().add(Vector3D.of(1, Double.POSITIVE_INFINITY, 1)).hasBounds());
514         Assert.assertFalse(Bounds3D.builder().add(Vector3D.of(1, 1, Double.POSITIVE_INFINITY)).hasBounds());
515 
516         Assert.assertFalse(Bounds3D.builder().add(Vector3D.of(Double.NEGATIVE_INFINITY, 1, 1)).hasBounds());
517         Assert.assertFalse(Bounds3D.builder().add(Vector3D.of(1, Double.NEGATIVE_INFINITY, 1)).hasBounds());
518         Assert.assertFalse(Bounds3D.builder().add(Vector3D.of(1, 1, Double.NEGATIVE_INFINITY)).hasBounds());
519 
520         Assert.assertTrue(Bounds3D.builder().add(Vector3D.ZERO).hasBounds());
521     }
522 
523     private static void checkBounds(final Bounds3D b, final Vector3D min, final Vector3D max) {
524         EuclideanTestUtils.assertCoordinatesEqual(min, b.getMin(), TEST_EPS);
525         EuclideanTestUtils.assertCoordinatesEqual(max, b.getMax(), TEST_EPS);
526     }
527 
528     private static void assertContainsStrict(final Bounds3D bounds, final boolean contains, final Vector3D... pts) {
529         for (final Vector3D pt : pts) {
530             Assert.assertEquals("Unexpected location for point " + pt, contains, bounds.contains(pt));
531         }
532     }
533 
534     private static void assertContainsWithPrecision(final Bounds3D bounds, final boolean contains, final Vector3D... pts) {
535         for (final Vector3D pt : pts) {
536             Assert.assertEquals("Unexpected location for point " + pt, contains, bounds.contains(pt, TEST_PRECISION));
537         }
538     }
539 }