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}