Bounds3D.java

  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. import java.util.Arrays;
  19. import java.util.Objects;

  20. import org.apache.commons.geometry.euclidean.AbstractBounds;
  21. import org.apache.commons.geometry.euclidean.threed.shape.Parallelepiped;
  22. import org.apache.commons.numbers.core.Precision;

  23. /** Class containing minimum and maximum points defining a 3D axis-aligned bounding box. Unless otherwise
  24.  * noted, floating point comparisons used in this class are strict, meaning that values are considered equal
  25.  * if and only if they match exactly.
  26.  *
  27.  * <p>Instances of this class are guaranteed to be immutable.</p>
  28.  */
  29. public final class Bounds3D extends AbstractBounds<Vector3D, Bounds3D> {

  30.     /** Simple constructor. Callers are responsible for ensuring the min is not greater than max.
  31.      * @param min minimum point
  32.      * @param max maximum point
  33.      */
  34.     private Bounds3D(final Vector3D min, final Vector3D max) {
  35.         super(min, max);
  36.     }

  37.     /** {@inheritDoc} */
  38.     @Override
  39.     public boolean hasSize(final Precision.DoubleEquivalence precision) {
  40.         final Vector3D diag = getDiagonal();

  41.         return !precision.eqZero(diag.getX()) &&
  42.                 !precision.eqZero(diag.getY()) &&
  43.                 !precision.eqZero(diag.getZ());
  44.     }

  45.     /** {@inheritDoc} */
  46.     @Override
  47.     public boolean contains(final Vector3D pt) {
  48.         final double x = pt.getX();
  49.         final double y = pt.getY();
  50.         final double z = pt.getZ();

  51.         final Vector3D min = getMin();
  52.         final Vector3D max = getMax();

  53.         return x >= min.getX() && x <= max.getX() &&
  54.                 y >= min.getY() && y <= max.getY() &&
  55.                 z >= min.getZ() && z <= max.getZ();
  56.     }

  57.     /** {@inheritDoc} */
  58.     @Override
  59.     public boolean contains(final Vector3D pt, final Precision.DoubleEquivalence precision) {
  60.         final double x = pt.getX();
  61.         final double y = pt.getY();
  62.         final double z = pt.getZ();

  63.         final Vector3D min = getMin();
  64.         final Vector3D max = getMax();

  65.         return precision.gte(x, min.getX()) && precision.lte(x, max.getX()) &&
  66.                 precision.gte(y, min.getY()) && precision.lte(y, max.getY()) &&
  67.                 precision.gte(z, min.getZ()) && precision.lte(z, max.getZ());
  68.     }

  69.     /** {@inheritDoc} */
  70.     @Override
  71.     public boolean intersects(final Bounds3D other) {
  72.         final Vector3D aMin = getMin();
  73.         final Vector3D aMax = getMax();

  74.         final Vector3D bMin = other.getMin();
  75.         final Vector3D bMax = other.getMax();

  76.         return aMin.getX() <= bMax.getX() && aMax.getX() >= bMin.getX() &&
  77.                 aMin.getY() <= bMax.getY() && aMax.getY() >= bMin.getY() &&
  78.                 aMin.getZ() <= bMax.getZ() && aMax.getZ() >= bMin.getZ();
  79.     }

  80.     /** {@inheritDoc} */
  81.     @Override
  82.     public Bounds3D intersection(final Bounds3D other) {
  83.         if (intersects(other)) {
  84.             final Vector3D aMin = getMin();
  85.             final Vector3D aMax = getMax();

  86.             final Vector3D bMin = other.getMin();
  87.             final Vector3D bMax = other.getMax();

  88.             // get the max of the mins and the mins of the maxes
  89.             final double minX = Math.max(aMin.getX(), bMin.getX());
  90.             final double minY = Math.max(aMin.getY(), bMin.getY());
  91.             final double minZ = Math.max(aMin.getZ(), bMin.getZ());

  92.             final double maxX = Math.min(aMax.getX(), bMax.getX());
  93.             final double maxY = Math.min(aMax.getY(), bMax.getY());
  94.             final double maxZ = Math.min(aMax.getZ(), bMax.getZ());

  95.             return new Bounds3D(
  96.                     Vector3D.of(minX, minY, minZ),
  97.                     Vector3D.of(maxX, maxY, maxZ));
  98.         }

  99.         return null; // no intersection
  100.     }

  101.     /** {@inheritDoc}
  102.      *
  103.      * @throws IllegalArgumentException if any dimension of the bounding box is zero
  104.      *      as evaluated by the given precision context
  105.      */
  106.     @Override
  107.     public Parallelepiped toRegion(final Precision.DoubleEquivalence precision) {
  108.         return Parallelepiped.axisAligned(getMin(), getMax(), precision);
  109.     }

  110.     /** {@inheritDoc} */
  111.     @Override
  112.     public int hashCode() {
  113.         return Objects.hash(getMin(), getMax());
  114.     }

  115.     /** {@inheritDoc} */
  116.     @Override
  117.     public boolean equals(final Object obj) {
  118.         if (obj == this) {
  119.             return true;
  120.         } else if (!(obj instanceof Bounds3D)) {
  121.             return false;
  122.         }

  123.         final Bounds3D other = (Bounds3D) obj;

  124.         return getMin().equals(other.getMin()) &&
  125.                 getMax().equals(other.getMax());
  126.     }

  127.     /** Construct a new instance from the given points.
  128.      * @param first first point
  129.      * @param more additional points
  130.      * @return a new instance containing the min and max coordinates values from the input points
  131.      */
  132.     public static Bounds3D from(final Vector3D first, final Vector3D... more) {
  133.         final Builder builder = builder();

  134.         builder.add(first);
  135.         builder.addAll(Arrays.asList(more));

  136.         return builder.build();
  137.     }

  138.     /** Construct a new instance from the given points.
  139.      * @param points input points
  140.      * @return a new instance containing the min and max coordinates values from the input points
  141.      */
  142.     public static Bounds3D from(final Iterable<Vector3D> points) {
  143.         final Builder builder = builder();

  144.         builder.addAll(points);

  145.         return builder.build();
  146.     }

  147.     /** Construct a new {@link Builder} instance for creating bounds.
  148.      * @return a new builder instance for creating bounds
  149.      */
  150.     public static Builder builder() {
  151.         return new Builder();
  152.     }

  153.     /** Class used to construct {@link Bounds3D} instances.
  154.      */
  155.     public static final class Builder {

  156.         /** Minimum x coordinate. */
  157.         private double minX = Double.POSITIVE_INFINITY;

  158.         /** Minimum y coordinate. */
  159.         private double minY = Double.POSITIVE_INFINITY;

  160.         /** Minimum z coordinate. */
  161.         private double minZ = Double.POSITIVE_INFINITY;

  162.         /** Maximum x coordinate. */
  163.         private double maxX = Double.NEGATIVE_INFINITY;

  164.         /** Maximum y coordinate. */
  165.         private double maxY = Double.NEGATIVE_INFINITY;

  166.         /** Maximum z coordinate. */
  167.         private double maxZ = Double.NEGATIVE_INFINITY;

  168.         /** Private constructor; instantiate through factory method. */
  169.         private Builder() { }

  170.         /** Add a point to this instance.
  171.          * @param pt point to add
  172.          * @return this instance
  173.          */
  174.         public Builder add(final Vector3D pt) {
  175.             final double x = pt.getX();
  176.             final double y = pt.getY();
  177.             final double z = pt.getZ();

  178.             minX = Math.min(x, minX);
  179.             minY = Math.min(y, minY);
  180.             minZ = Math.min(z, minZ);

  181.             maxX = Math.max(x, maxX);
  182.             maxY = Math.max(y, maxY);
  183.             maxZ = Math.max(z, maxZ);

  184.             return this;
  185.         }

  186.         /** Add a collection of points to this instance.
  187.          * @param pts points to add
  188.          * @return this instance
  189.          */
  190.         public Builder addAll(final Iterable<? extends Vector3D> pts) {
  191.             for (final Vector3D pt : pts) {
  192.                 add(pt);
  193.             }

  194.             return this;
  195.         }

  196.         /** Add the min and max points from the given bounds to this instance.
  197.          * @param bounds bounds containing the min and max points to add
  198.          * @return this instance
  199.          */
  200.         public Builder add(final Bounds3D bounds) {
  201.             add(bounds.getMin());
  202.             add(bounds.getMax());

  203.             return this;
  204.         }

  205.         /** Return true if this builder contains valid min and max coordinate values.
  206.          * @return true if this builder contains valid min and max coordinate values
  207.          */
  208.         public boolean hasBounds() {
  209.             return Double.isFinite(minX) &&
  210.                     Double.isFinite(minY) &&
  211.                     Double.isFinite(minZ) &&
  212.                     Double.isFinite(maxX) &&
  213.                     Double.isFinite(maxY) &&
  214.                     Double.isFinite(maxZ);
  215.         }

  216.         /** Create a new {@link Bounds3D} instance from the values in this builder.
  217.          * The builder can continue to be used to create other instances.
  218.          * @return a new bounds instance
  219.          * @throws IllegalStateException if no points were given to the builder or any of the computed
  220.          *      min and max coordinate values are NaN or infinite
  221.          * @see #hasBounds()
  222.          */
  223.         public Bounds3D build() {
  224.             final Vector3D min = Vector3D.of(minX, minY, minZ);
  225.             final Vector3D max = Vector3D.of(maxX, maxY, maxZ);

  226.             if (!hasBounds()) {
  227.                 if (Double.isInfinite(minX) && minX > 0 &&
  228.                         Double.isInfinite(maxX) && maxX < 0) {
  229.                     throw new IllegalStateException("Cannot construct bounds: no points given");
  230.                 }

  231.                 throw new IllegalStateException("Invalid bounds: min= " + min + ", max= " + max);
  232.             }

  233.             return new Bounds3D(min, max);
  234.         }
  235.     }
  236. }