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 }