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}