001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.geometry.euclidean.threed;
018
019import java.util.Arrays;
020import java.util.Objects;
021
022import org.apache.commons.geometry.euclidean.AbstractBounds;
023import org.apache.commons.geometry.euclidean.threed.shape.Parallelepiped;
024import org.apache.commons.numbers.core.Precision;
025
026/** Class containing minimum and maximum points defining a 3D axis-aligned bounding box. Unless otherwise
027 * noted, floating point comparisons used in this class are strict, meaning that values are considered equal
028 * if and only if they match exactly.
029 *
030 * <p>Instances of this class are guaranteed to be immutable.</p>
031 */
032public final class Bounds3D extends AbstractBounds<Vector3D, Bounds3D> {
033
034    /** Simple constructor. Callers are responsible for ensuring the min is not greater than max.
035     * @param min minimum point
036     * @param max maximum point
037     */
038    private Bounds3D(final Vector3D min, final Vector3D max) {
039        super(min, max);
040    }
041
042    /** {@inheritDoc} */
043    @Override
044    public boolean hasSize(final Precision.DoubleEquivalence precision) {
045        final Vector3D diag = getDiagonal();
046
047        return !precision.eqZero(diag.getX()) &&
048                !precision.eqZero(diag.getY()) &&
049                !precision.eqZero(diag.getZ());
050    }
051
052    /** {@inheritDoc} */
053    @Override
054    public boolean contains(final Vector3D pt) {
055        final double x = pt.getX();
056        final double y = pt.getY();
057        final double z = pt.getZ();
058
059        final Vector3D min = getMin();
060        final Vector3D max = getMax();
061
062        return x >= min.getX() && x <= max.getX() &&
063                y >= min.getY() && y <= max.getY() &&
064                z >= min.getZ() && z <= max.getZ();
065    }
066
067    /** {@inheritDoc} */
068    @Override
069    public boolean contains(final Vector3D pt, final Precision.DoubleEquivalence precision) {
070        final double x = pt.getX();
071        final double y = pt.getY();
072        final double z = pt.getZ();
073
074        final Vector3D min = getMin();
075        final Vector3D max = getMax();
076
077        return precision.gte(x, min.getX()) && precision.lte(x, max.getX()) &&
078                precision.gte(y, min.getY()) && precision.lte(y, max.getY()) &&
079                precision.gte(z, min.getZ()) && precision.lte(z, max.getZ());
080    }
081
082    /** {@inheritDoc} */
083    @Override
084    public boolean intersects(final Bounds3D other) {
085        final Vector3D aMin = getMin();
086        final Vector3D aMax = getMax();
087
088        final Vector3D bMin = other.getMin();
089        final Vector3D bMax = other.getMax();
090
091        return aMin.getX() <= bMax.getX() && aMax.getX() >= bMin.getX() &&
092                aMin.getY() <= bMax.getY() && aMax.getY() >= bMin.getY() &&
093                aMin.getZ() <= bMax.getZ() && aMax.getZ() >= bMin.getZ();
094    }
095
096    /** {@inheritDoc} */
097    @Override
098    public Bounds3D intersection(final Bounds3D other) {
099        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}