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 }