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.Arrays;
    20  import java.util.Objects;
    21
    22  import org.apache.commons.geometry.euclidean.AbstractBounds;
    23  import org.apache.commons.geometry.euclidean.threed.shape.Parallelepiped;
    24  import org.apache.commons.numbers.core.Precision;
    25
    26  /** Class containing minimum and maximum points defining a 3D axis-aligned bounding box. Unless otherwise
    27   * noted, floating point comparisons used in this class are strict, meaning that values are considered equal
    28   * if and only if they match exactly.
    29   *
    30   * <p>Instances of this class are guaranteed to be immutable.</p>
    31   */
    32  public final class Bounds3D extends AbstractBounds<Vector3D, Bounds3D> {
    33
    34      /** Simple constructor. Callers are responsible for ensuring the min is not greater than max.
    35       * @param min minimum point
    36       * @param max maximum point
    37       */
    38      private Bounds3D(final Vector3D min, final Vector3D max) {
    39          super(min, max);
    40      }
    41
    42      /** {@inheritDoc} */
    43      @Override
    44      public boolean hasSize(final Precision.DoubleEquivalence precision) {
    45          final Vector3D diag = getDiagonal();
    46
    47          return !precision.eqZero(diag.getX()) &&
    48                  !precision.eqZero(diag.getY()) &&
    49                  !precision.eqZero(diag.getZ());
    50      }
    51
    52      /** {@inheritDoc} */
    53      @Override
    54      public boolean contains(final Vector3D pt) {
    55          final double x = pt.getX();
    56          final double y = pt.getY();
    57          final double z = pt.getZ();
    58
    59          final Vector3D min = getMin();
    60          final Vector3D max = getMax();
    61
    62          return x >= min.getX() && x <= max.getX() &&
    63                  y >= min.getY() && y <= max.getY() &&
    64                  z >= min.getZ() && z <= max.getZ();
    65      }
    66
    67      /** {@inheritDoc} */
    68      @Override
    69      public boolean contains(final Vector3D pt, final Precision.DoubleEquivalence precision) {
    70          final double x = pt.getX();
    71          final double y = pt.getY();
    72          final double z = pt.getZ();
    73
    74          final Vector3D min = getMin();
    75          final Vector3D max = getMax();
    76
    77          return precision.gte(x, min.getX()) && precision.lte(x, max.getX()) &&
    78                  precision.gte(y, min.getY()) && precision.lte(y, max.getY()) &&
    79                  precision.gte(z, min.getZ()) && precision.lte(z, max.getZ());
    80      }
    81
    82      /** {@inheritDoc} */
    83      @Override
    84      public boolean intersects(final Bounds3D other) {
    85          final Vector3D aMin = getMin();
    86          final Vector3D aMax = getMax();
    87
    88          final Vector3D bMin = other.getMin();
    89          final Vector3D bMax = other.getMax();
    90
    91          return aMin.getX() <= bMax.getX() && aMax.getX() >= bMin.getX() &&
    92                  aMin.getY() <= bMax.getY() && aMax.getY() >= bMin.getY() &&
    93                  aMin.getZ() <= bMax.getZ() && aMax.getZ() >= bMin.getZ();
    94      }
    95
    96      /** {@inheritDoc} */
    97      @Override
    98      public Bounds3D intersection(final Bounds3D other) {
    99          if (intersects(other)) {
    100             final Vector3D aMin = getMin();
    101             final Vector3D aMax = getMax();
    102
    103             final Vector3D bMin = other.getMin();
    104             final Vector3D bMax = other.getMax();
    105
    106             // get the max of the mins and the mins of the maxes
    107             final double minX = Math.max(aMin.getX(), bMin.getX());
    108             final double minY = Math.max(aMin.getY(), bMin.getY());
    109             final double minZ = Math.max(aMin.getZ(), bMin.getZ());
    110
    111             final double maxX = Math.min(aMax.getX(), bMax.getX());
    112             final double maxY = Math.min(aMax.getY(), bMax.getY());
    113             final double maxZ = Math.min(aMax.getZ(), bMax.getZ());
    114
    115             return new Bounds3D(
    116                     Vector3D.of(minX, minY, minZ),
    117                     Vector3D.of(maxX, maxY, maxZ));
    118         }
    119
    120         return null; // no intersection
    121     }
    122
    123     /** {@inheritDoc}
    124      *
    125      * @throws IllegalArgumentException if any dimension of the bounding box is zero
    126      *      as evaluated by the given precision context
    127      */
    128     @Override
    129     public Parallelepiped toRegion(final Precision.DoubleEquivalence precision) {
    130         return Parallelepiped.axisAligned(getMin(), getMax(), precision);
    131     }
    132
    133     /** {@inheritDoc} */
    134     @Override
    135     public int hashCode() {
    136         return Objects.hash(getMin(), getMax());
    137     }
    138
    139     /** {@inheritDoc} */
    140     @Override
    141     public boolean equals(final Object obj) {
    142         if (obj == this) {
    143             return true;
    144         } else if (!(obj instanceof Bounds3D)) {
    145             return false;
    146         }
    147
    148         final Bounds3D other = (Bounds3D) obj;
    149
    150         return getMin().equals(other.getMin()) &&
    151                 getMax().equals(other.getMax());
    152     }
    153
    154     /** Construct a new instance from the given points.
    155      * @param first first point
    156      * @param more additional points
    157      * @return a new instance containing the min and max coordinates values from the input points
    158      */
    159     public static Bounds3D from(final Vector3D first, final Vector3D... more) {
    160         final Builder builder = builder();
    161
    162         builder.add(first);
    163         builder.addAll(Arrays.asList(more));
    164
    165         return builder.build();
    166     }
    167
    168     /** Construct a new instance from the given points.
    169      * @param points input points
    170      * @return a new instance containing the min and max coordinates values from the input points
    171      */
    172     public static Bounds3D from(final Iterable<Vector3D> points) {
    173         final Builder builder = builder();
    174
    175         builder.addAll(points);
    176
    177         return builder.build();
    178     }
    179
    180     /** Construct a new {@link Builder} instance for creating bounds.
    181      * @return a new builder instance for creating bounds
    182      */
    183     public static Builder builder() {
    184         return new Builder();
    185     }
    186
    187     /** Class used to construct {@link Bounds3D} instances.
    188      */
    189     public static final class Builder {
    190
    191         /** Minimum x coordinate. */
    192         private double minX = Double.POSITIVE_INFINITY;
    193
    194         /** Minimum y coordinate. */
    195         private double minY = Double.POSITIVE_INFINITY;
    196
    197         /** Minimum z coordinate. */
    198         private double minZ = Double.POSITIVE_INFINITY;
    199
    200         /** Maximum x coordinate. */
    201         private double maxX = Double.NEGATIVE_INFINITY;
    202
    203         /** Maximum y coordinate. */
    204         private double maxY = Double.NEGATIVE_INFINITY;
    205
    206         /** Maximum z coordinate. */
    207         private double maxZ = Double.NEGATIVE_INFINITY;
    208
    209         /** Private constructor; instantiate through factory method. */
    210         private Builder() { }
    211
    212         /** Add a point to this instance.
    213          * @param pt point to add
    214          * @return this instance
    215          */
    216         public Builder add(final Vector3D pt) {
    217             final double x = pt.getX();
    218             final double y = pt.getY();
    219             final double z = pt.getZ();
    220
    221             minX = Math.min(x, minX);
    222             minY = Math.min(y, minY);
    223             minZ = Math.min(z, minZ);
    224
    225             maxX = Math.max(x, maxX);
    226             maxY = Math.max(y, maxY);
    227             maxZ = Math.max(z, maxZ);
    228
    229             return this;
    230         }
    231
    232         /** Add a collection of points to this instance.
    233          * @param pts points to add
    234          * @return this instance
    235          */
    236         public Builder addAll(final Iterable<? extends Vector3D> pts) {
    237             for (final Vector3D pt : pts) {
    238                 add(pt);
    239             }
    240
    241             return this;
    242         }
    243
    244         /** Add the min and max points from the given bounds to this instance.
    245          * @param bounds bounds containing the min and max points to add
    246          * @return this instance
    247          */
    248         public Builder add(final Bounds3D bounds) {
    249             add(bounds.getMin());
    250             add(bounds.getMax());
    251
    252             return this;
    253         }
    254
    255         /** Return true if this builder contains valid min and max coordinate values.
    256          * @return true if this builder contains valid min and max coordinate values
    257          */
    258         public boolean hasBounds() {
    259             return Double.isFinite(minX) &&
    260                     Double.isFinite(minY) &&
    261                     Double.isFinite(minZ) &&
    262                     Double.isFinite(maxX) &&
    263                     Double.isFinite(maxY) &&
    264                     Double.isFinite(maxZ);
    265         }
    266
    267         /** Create a new {@link Bounds3D} instance from the values in this builder.
    268          * The builder can continue to be used to create other instances.
    269          * @return a new bounds instance
    270          * @throws IllegalStateException if no points were given to the builder or any of the computed
    271          *      min and max coordinate values are NaN or infinite
    272          * @see #hasBounds()
    273          */
    274         public Bounds3D build() {
    275             final Vector3D min = Vector3D.of(minX, minY, minZ);
    276             final Vector3D max = Vector3D.of(maxX, maxY, maxZ);
    277
    278             if (!hasBounds()) {
    279                 if (Double.isInfinite(minX) && minX > 0 &&
    280                         Double.isInfinite(maxX) && maxX < 0) {
    281                     throw new IllegalStateException("Cannot construct bounds: no points given");
    282                 }
    283
    284                 throw new IllegalStateException("Invalid bounds: min= " + min + ", max= " + max);
    285             }
    286
    287             return new Bounds3D(min, max);
    288         }
    289     }
    290 }