Bounds2D.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.twod;

  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.twod.shape.Parallelogram;
  22. import org.apache.commons.numbers.core.Precision;

  23. /** Class containing minimum and maximum points defining a 2D 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 Bounds2D extends AbstractBounds<Vector2D, Bounds2D> {

  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 Bounds2D(final Vector2D min, final Vector2D max) {
  35.         super(min, max);
  36.     }

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

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

  44.     /** {@inheritDoc} */
  45.     @Override
  46.     public boolean contains(final Vector2D pt) {
  47.         final double x = pt.getX();
  48.         final double y = pt.getY();

  49.         final Vector2D min = getMin();
  50.         final Vector2D max = getMax();

  51.         return x >= min.getX() && x <= max.getX() &&
  52.                 y >= min.getY() && y <= max.getY();
  53.     }

  54.     /** {@inheritDoc} */
  55.     @Override
  56.     public boolean contains(final Vector2D pt, final Precision.DoubleEquivalence precision) {
  57.         final double x = pt.getX();
  58.         final double y = pt.getY();

  59.         final Vector2D min = getMin();
  60.         final Vector2D max = getMax();

  61.         return precision.gte(x, min.getX()) && precision.lte(x, max.getX()) &&
  62.                 precision.gte(y, min.getY()) && precision.lte(y, max.getY());
  63.     }

  64.     /** {@inheritDoc} */
  65.     @Override
  66.     public boolean intersects(final Bounds2D other) {
  67.         final Vector2D aMin = getMin();
  68.         final Vector2D aMax = getMax();

  69.         final Vector2D bMin = other.getMin();
  70.         final Vector2D bMax = other.getMax();

  71.         return aMin.getX() <= bMax.getX() && aMax.getX() >= bMin.getX() &&
  72.                 aMin.getY() <= bMax.getY() && aMax.getY() >= bMin.getY();
  73.     }

  74.     /** {@inheritDoc} */
  75.     @Override
  76.     public Bounds2D intersection(final Bounds2D other) {
  77.         if (intersects(other)) {
  78.             final Vector2D aMin = getMin();
  79.             final Vector2D aMax = getMax();

  80.             final Vector2D bMin = other.getMin();
  81.             final Vector2D bMax = other.getMax();

  82.             // get the max of the mins and the mins of the maxes
  83.             final double minX = Math.max(aMin.getX(), bMin.getX());
  84.             final double minY = Math.max(aMin.getY(), bMin.getY());

  85.             final double maxX = Math.min(aMax.getX(), bMax.getX());
  86.             final double maxY = Math.min(aMax.getY(), bMax.getY());

  87.             return new Bounds2D(
  88.                     Vector2D.of(minX, minY),
  89.                     Vector2D.of(maxX, maxY));
  90.         }

  91.         return null; // no intersection
  92.     }

  93.     /** {@inheritDoc}
  94.      *
  95.      * @throws IllegalArgumentException if any dimension of the bounding box is zero
  96.      *      as evaluated by the given precision context
  97.      */
  98.     @Override
  99.     public Parallelogram toRegion(final Precision.DoubleEquivalence precision) {
  100.         return Parallelogram.axisAligned(getMin(), getMax(), precision);
  101.     }

  102.     /** {@inheritDoc} */
  103.     @Override
  104.     public int hashCode() {
  105.         return Objects.hash(getMin(), getMax());
  106.     }

  107.     /** {@inheritDoc} */
  108.     @Override
  109.     public boolean equals(final Object obj) {
  110.         if (obj == this) {
  111.             return true;
  112.         } else if (!(obj instanceof Bounds2D)) {
  113.             return false;
  114.         }

  115.         final Bounds2D other = (Bounds2D) obj;

  116.         return getMin().equals(other.getMin()) &&
  117.                 getMax().equals(other.getMax());
  118.     }

  119.     /** Construct a new instance from the given points.
  120.      * @param first first point
  121.      * @param more additional points
  122.      * @return a new instance containing the min and max coordinates values from the input points
  123.      */
  124.     public static Bounds2D from(final Vector2D first, final Vector2D... more) {
  125.         final Builder builder = builder();

  126.         builder.add(first);
  127.         builder.addAll(Arrays.asList(more));

  128.         return builder.build();
  129.     }

  130.     /** Construct a new instance from the given points.
  131.      * @param points input points
  132.      * @return a new instance containing the min and max coordinates values from the input points
  133.      */
  134.     public static Bounds2D from(final Iterable<Vector2D> points) {
  135.         final Builder builder = builder();

  136.         builder.addAll(points);

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

  139.     /** Construct a new {@link Builder} instance for creating bounds.
  140.      * @return a new builder instance for creating bounds
  141.      */
  142.     public static Builder builder() {
  143.         return new Builder();
  144.     }

  145.     /** Class used to construct {@link Bounds2D} instances.
  146.      */
  147.     public static final class Builder {

  148.         /** Minimum x coordinate. */
  149.         private double minX = Double.POSITIVE_INFINITY;

  150.         /** Minimum y coordinate. */
  151.         private double minY = Double.POSITIVE_INFINITY;

  152.         /** Maximum x coordinate. */
  153.         private double maxX = Double.NEGATIVE_INFINITY;

  154.         /** Maximum y coordinate. */
  155.         private double maxY = Double.NEGATIVE_INFINITY;

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

  158.         /** Add a point to this instance.
  159.          * @param pt point to add
  160.          * @return this instance
  161.          */
  162.         public Builder add(final Vector2D pt) {
  163.             final double x = pt.getX();
  164.             final double y = pt.getY();

  165.             minX = Math.min(x, minX);
  166.             minY = Math.min(y, minY);

  167.             maxX = Math.max(x, maxX);
  168.             maxY = Math.max(y, maxY);

  169.             return this;
  170.         }

  171.         /** Add a collection of points to this instance.
  172.          * @param pts points to add
  173.          * @return this instance
  174.          */
  175.         public Builder addAll(final Iterable<? extends Vector2D> pts) {
  176.             for (final Vector2D pt : pts) {
  177.                 add(pt);
  178.             }

  179.             return this;
  180.         }

  181.         /** Add the min and max points from the given bounds to this instance.
  182.          * @param bounds bounds containing the min and max points to add
  183.          * @return this instance
  184.          */
  185.         public Builder add(final Bounds2D bounds) {
  186.             add(bounds.getMin());
  187.             add(bounds.getMax());

  188.             return this;
  189.         }

  190.         /** Return true if this builder contains valid min and max coordinate values.
  191.          * @return true if this builder contains valid min and max coordinate values
  192.          */
  193.         public boolean hasBounds() {
  194.             return Double.isFinite(minX) &&
  195.                     Double.isFinite(minY) &&
  196.                     Double.isFinite(maxX) &&
  197.                     Double.isFinite(maxY);
  198.         }

  199.         /** Create a new {@link Bounds2D} instance from the values in this builder.
  200.          * The builder can continue to be used to create other instances.
  201.          * @return a new bounds instance
  202.          * @throws IllegalStateException if no points were given to the builder or any of the computed
  203.          *      min and max coordinate values are NaN or infinite
  204.          * @see #hasBounds()
  205.          */
  206.         public Bounds2D build() {
  207.             final Vector2D min = Vector2D.of(minX, minY);
  208.             final Vector2D max = Vector2D.of(maxX, maxY);

  209.             if (!hasBounds()) {
  210.                 if (Double.isInfinite(minX) && minX > 0 &&
  211.                         Double.isInfinite(maxX) && maxX < 0) {
  212.                     throw new IllegalStateException("Cannot construct bounds: no points given");
  213.                 }

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

  216.             return new Bounds2D(min, max);
  217.         }
  218.     }
  219. }