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.twod;
018
019import java.util.Arrays;
020import java.util.Objects;
021
022import org.apache.commons.geometry.euclidean.AbstractBounds;
023import org.apache.commons.geometry.euclidean.twod.shape.Parallelogram;
024import org.apache.commons.numbers.core.Precision;
025
026/** Class containing minimum and maximum points defining a 2D 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 Bounds2D extends AbstractBounds<Vector2D, Bounds2D> {
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 Bounds2D(final Vector2D min, final Vector2D max) {
039        super(min, max);
040    }
041
042    /** {@inheritDoc} */
043    @Override
044    public boolean hasSize(final Precision.DoubleEquivalence precision) {
045        final Vector2D diag = getDiagonal();
046
047        return !precision.eqZero(diag.getX()) &&
048                !precision.eqZero(diag.getY());
049    }
050
051    /** {@inheritDoc} */
052    @Override
053    public boolean contains(final Vector2D pt) {
054        final double x = pt.getX();
055        final double y = pt.getY();
056
057        final Vector2D min = getMin();
058        final Vector2D max = getMax();
059
060        return x >= min.getX() && x <= max.getX() &&
061                y >= min.getY() && y <= max.getY();
062    }
063
064    /** {@inheritDoc} */
065    @Override
066    public boolean contains(final Vector2D pt, final Precision.DoubleEquivalence precision) {
067        final double x = pt.getX();
068        final double y = pt.getY();
069
070        final Vector2D min = getMin();
071        final Vector2D max = getMax();
072
073        return precision.gte(x, min.getX()) && precision.lte(x, max.getX()) &&
074                precision.gte(y, min.getY()) && precision.lte(y, max.getY());
075    }
076
077    /** {@inheritDoc} */
078    @Override
079    public boolean intersects(final Bounds2D other) {
080        final Vector2D aMin = getMin();
081        final Vector2D aMax = getMax();
082
083        final Vector2D bMin = other.getMin();
084        final Vector2D bMax = other.getMax();
085
086        return aMin.getX() <= bMax.getX() && aMax.getX() >= bMin.getX() &&
087                aMin.getY() <= bMax.getY() && aMax.getY() >= bMin.getY();
088    }
089
090    /** {@inheritDoc} */
091    @Override
092    public Bounds2D intersection(final Bounds2D other) {
093        if (intersects(other)) {
094            final Vector2D aMin = getMin();
095            final Vector2D aMax = getMax();
096
097            final Vector2D bMin = other.getMin();
098            final Vector2D bMax = other.getMax();
099
100            // get the max of the mins and the mins of the maxes
101            final double minX = Math.max(aMin.getX(), bMin.getX());
102            final double minY = Math.max(aMin.getY(), bMin.getY());
103
104            final double maxX = Math.min(aMax.getX(), bMax.getX());
105            final double maxY = Math.min(aMax.getY(), bMax.getY());
106
107            return new Bounds2D(
108                    Vector2D.of(minX, minY),
109                    Vector2D.of(maxX, maxY));
110        }
111
112        return null; // no intersection
113    }
114
115    /** {@inheritDoc}
116     *
117     * @throws IllegalArgumentException if any dimension of the bounding box is zero
118     *      as evaluated by the given precision context
119     */
120    @Override
121    public Parallelogram toRegion(final Precision.DoubleEquivalence precision) {
122        return Parallelogram.axisAligned(getMin(), getMax(), precision);
123    }
124
125    /** {@inheritDoc} */
126    @Override
127    public int hashCode() {
128        return Objects.hash(getMin(), getMax());
129    }
130
131    /** {@inheritDoc} */
132    @Override
133    public boolean equals(final Object obj) {
134        if (obj == this) {
135            return true;
136        } else if (!(obj instanceof Bounds2D)) {
137            return false;
138        }
139
140        final Bounds2D other = (Bounds2D) obj;
141
142        return getMin().equals(other.getMin()) &&
143                getMax().equals(other.getMax());
144    }
145
146    /** Construct a new instance from the given points.
147     * @param first first point
148     * @param more additional points
149     * @return a new instance containing the min and max coordinates values from the input points
150     */
151    public static Bounds2D from(final Vector2D first, final Vector2D... more) {
152        final Builder builder = builder();
153
154        builder.add(first);
155        builder.addAll(Arrays.asList(more));
156
157        return builder.build();
158    }
159
160    /** Construct a new instance from the given points.
161     * @param points input points
162     * @return a new instance containing the min and max coordinates values from the input points
163     */
164    public static Bounds2D from(final Iterable<Vector2D> points) {
165        final Builder builder = builder();
166
167        builder.addAll(points);
168
169        return builder.build();
170    }
171
172    /** Construct a new {@link Builder} instance for creating bounds.
173     * @return a new builder instance for creating bounds
174     */
175    public static Builder builder() {
176        return new Builder();
177    }
178
179    /** Class used to construct {@link Bounds2D} instances.
180     */
181    public static final class Builder {
182
183        /** Minimum x coordinate. */
184        private double minX = Double.POSITIVE_INFINITY;
185
186        /** Minimum y coordinate. */
187        private double minY = Double.POSITIVE_INFINITY;
188
189        /** Maximum x coordinate. */
190        private double maxX = Double.NEGATIVE_INFINITY;
191
192        /** Maximum y coordinate. */
193        private double maxY = Double.NEGATIVE_INFINITY;
194
195        /** Private constructor; instantiate through factory method. */
196        private Builder() { }
197
198        /** Add a point to this instance.
199         * @param pt point to add
200         * @return this instance
201         */
202        public Builder add(final Vector2D pt) {
203            final double x = pt.getX();
204            final double y = pt.getY();
205
206            minX = Math.min(x, minX);
207            minY = Math.min(y, minY);
208
209            maxX = Math.max(x, maxX);
210            maxY = Math.max(y, maxY);
211
212            return this;
213        }
214
215        /** Add a collection of points to this instance.
216         * @param pts points to add
217         * @return this instance
218         */
219        public Builder addAll(final Iterable<? extends Vector2D> pts) {
220            for (final Vector2D pt : pts) {
221                add(pt);
222            }
223
224            return this;
225        }
226
227        /** Add the min and max points from the given bounds to this instance.
228         * @param bounds bounds containing the min and max points to add
229         * @return this instance
230         */
231        public Builder add(final Bounds2D bounds) {
232            add(bounds.getMin());
233            add(bounds.getMax());
234
235            return this;
236        }
237
238        /** Return true if this builder contains valid min and max coordinate values.
239         * @return true if this builder contains valid min and max coordinate values
240         */
241        public boolean hasBounds() {
242            return Double.isFinite(minX) &&
243                    Double.isFinite(minY) &&
244                    Double.isFinite(maxX) &&
245                    Double.isFinite(maxY);
246        }
247
248        /** Create a new {@link Bounds2D} instance from the values in this builder.
249         * The builder can continue to be used to create other instances.
250         * @return a new bounds instance
251         * @throws IllegalStateException if no points were given to the builder or any of the computed
252         *      min and max coordinate values are NaN or infinite
253         * @see #hasBounds()
254         */
255        public Bounds2D build() {
256            final Vector2D min = Vector2D.of(minX, minY);
257            final Vector2D max = Vector2D.of(maxX, maxY);
258
259            if (!hasBounds()) {
260                if (Double.isInfinite(minX) && minX > 0 &&
261                        Double.isInfinite(maxX) && maxX < 0) {
262                    throw new IllegalStateException("Cannot construct bounds: no points given");
263                }
264
265                throw new IllegalStateException("Invalid bounds: min= " + min + ", max= " + max);
266            }
267
268            return new Bounds2D(min, max);
269        }
270    }
271}