1 /* 2 * Copyright (c) 1998, 2015, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.javafx.experiments.utils3d.geom; 34 35 /** 36 * A simple object which carries bounds information as floats, and 37 * has no Z components. 38 */ 39 public final class RectBounds extends BaseBounds { 40 // minimum x value of bounding box 41 private float minX; 42 // maximum x value of bounding box 43 private float maxX; 44 // minimum y value of bounding box 45 private float minY; 46 // maximum y value of bounding box 47 private float maxY; 48 49 /** 50 * Create an axis aligned bounding rectangle object, with an empty bounds 51 * where maxX < minX and maxY < minY. 52 */ 53 public RectBounds() { 54 minX = minY = 0.0f; 55 maxX = maxY = -1.0f; 56 } 57 58 @Override public BaseBounds copy() { 59 return new RectBounds(minX, minY, maxX, maxY); 60 } 61 62 /** 63 * Creates a RectBounds based on the minX, minY, maxX, and maxY values specified. 64 */ 65 public RectBounds(float minX, float minY, float maxX, float maxY) { 66 setBounds(minX, minY, maxX, maxY); 67 } 68 69 /** 70 * Creates a RectBounds object as a copy of the specified RectBounds object. 71 */ 72 public RectBounds(RectBounds other) { 73 setBounds(other); 74 } 75 76 /** 77 * Creates a RectBounds object as a copy of the specified RECTANGLE. 78 */ 79 public RectBounds(Rectangle other) { 80 setBounds(other.x, other.y, 81 other.x + other.width, other.y + other.height); 82 } 83 84 @Override public BoundsType getBoundsType() { 85 return BoundsType.RECTANGLE; 86 } 87 88 @Override public boolean is2D() { 89 return true; 90 } 91 92 /** 93 * Convenience function for getting the width of this RectBounds. 94 * The dimension along the X-Axis. 95 */ 96 @Override public float getWidth() { 97 return maxX - minX; 98 } 99 100 /** 101 * Convenience function for getting the height of this RectBounds 102 * The dimension along the Y-Axis. 103 */ 104 @Override public float getHeight() { 105 return maxY - minY; 106 } 107 108 /** 109 * Convenience function for getting the depth of this RectBounds 110 * The dimension along the Z-Axis, since this is a 2D bounds the return 111 * value is always 0.0f. 112 */ 113 @Override public float getDepth() { 114 return 0.0f; 115 } 116 117 @Override public float getMinX() { 118 return minX; 119 } 120 121 public void setMinX(float minX) { 122 this.minX = minX; 123 } 124 125 @Override public float getMinY() { 126 return minY; 127 } 128 129 public void setMinY(float minY) { 130 this.minY = minY; 131 } 132 133 @Override public float getMinZ() { 134 return 0.0f; 135 } 136 137 @Override public float getMaxX() { 138 return maxX; 139 } 140 141 public void setMaxX(float maxX) { 142 this.maxX = maxX; 143 } 144 145 @Override public float getMaxY() { 146 return maxY; 147 } 148 149 public void setMaxY(float maxY) { 150 this.maxY = maxY; 151 } 152 153 @Override public float getMaxZ() { 154 return 0.0f; 155 } 156 157 @Override public Vec2f getMin(Vec2f min) { 158 if (min == null) { 159 min = new Vec2f(); 160 } 161 min.x = minX; 162 min.y = minY; 163 return min; 164 } 165 166 @Override public Vec2f getMax(Vec2f max) { 167 if (max == null) { 168 max = new Vec2f(); 169 } 170 max.x = maxX; 171 max.y = maxY; 172 return max; 173 } 174 175 @Override public Vec3f getMin(Vec3f min) { 176 if (min == null) { 177 min = new Vec3f(); 178 } 179 min.x = minX; 180 min.y = minY; 181 min.z = 0.0f; 182 return min; 183 184 } 185 186 @Override public Vec3f getMax(Vec3f max) { 187 if (max == null) { 188 max = new Vec3f(); 189 } 190 max.x = maxX; 191 max.y = maxY; 192 max.z = 0.0f; 193 return max; 194 195 } 196 197 @Override public BaseBounds deriveWithUnion(BaseBounds other) { 198 if (other.getBoundsType() == BoundsType.RECTANGLE) { 199 RectBounds rb = (RectBounds) other; 200 unionWith(rb); 201 } else if (other.getBoundsType() == BoundsType.BOX) { 202 BoxBounds bb = new BoxBounds((BoxBounds) other); 203 bb.unionWith(this); 204 return bb; 205 } else { 206 throw new UnsupportedOperationException("Unknown BoundsType"); 207 } 208 return this; 209 } 210 211 @Override public BaseBounds deriveWithNewBounds(Rectangle other) { 212 if (other.width < 0 || other.height < 0) return makeEmpty(); 213 setBounds(other.x, other.y, 214 other.x + other.width, other.y + other.height); 215 return this; 216 } 217 218 @Override public BaseBounds deriveWithNewBounds(BaseBounds other) { 219 if (other.isEmpty()) return makeEmpty(); 220 if (other.getBoundsType() == BoundsType.RECTANGLE) { 221 RectBounds rb = (RectBounds) other; 222 minX = rb.getMinX(); 223 minY = rb.getMinY(); 224 maxX = rb.getMaxX(); 225 maxY = rb.getMaxY(); 226 } else if (other.getBoundsType() == BoundsType.BOX) { 227 return new BoxBounds((BoxBounds) other); 228 } else { 229 throw new UnsupportedOperationException("Unknown BoundsType"); 230 } 231 return this; 232 } 233 234 @Override public BaseBounds deriveWithNewBounds(float minX, float minY, float minZ, 235 float maxX, float maxY, float maxZ) { 236 if ((maxX < minX) || (maxY < minY) || (maxZ < minZ)) return makeEmpty(); 237 if ((minZ == 0) && (maxZ == 0)) { 238 this.minX = minX; 239 this.minY = minY; 240 this.maxX = maxX; 241 this.maxY = maxY; 242 return this; 243 } 244 return new BoxBounds(minX, minY, minZ, maxX, maxY, maxZ); 245 } 246 247 @Override public BaseBounds deriveWithNewBoundsAndSort(float minX, float minY, float minZ, 248 float maxX, float maxY, float maxZ) { 249 if ((minZ == 0) && (maxZ == 0)) { 250 setBoundsAndSort(minX, minY, minZ, maxX, maxY, maxZ); 251 return this; 252 } 253 254 BaseBounds bb = new BoxBounds(); 255 bb.setBoundsAndSort(minX, minY, minZ, maxX, maxY, maxZ); 256 return bb; 257 } 258 259 /** 260 * Set the bounds to match that of the RectBounds object specified. The 261 * specified bounds object must not be null. 262 */ 263 public final void setBounds(RectBounds other) { 264 minX = other.getMinX(); 265 minY = other.getMinY(); 266 maxX = other.getMaxX(); 267 maxY = other.getMaxY(); 268 } 269 270 /** 271 * Set the bounds to the given values. 272 */ 273 public final void setBounds(float minX, float minY, float maxX, float maxY) { 274 this.minX = minX; 275 this.minY = minY; 276 this.maxX = maxX; 277 this.maxY = maxY; 278 } 279 280 /** 281 * Sets the bounds based on the given coords, and also ensures that after 282 * having done so that this RectBounds instance is normalized. 283 */ 284 public void setBoundsAndSort(float minX, float minY, float maxX, float maxY) { 285 setBounds(minX, minY, maxX, maxY); 286 sortMinMax(); 287 } 288 289 @Override public void setBoundsAndSort(float minX, float minY, float minZ, 290 float maxX, float maxY, float maxZ) { 291 if (minZ != 0 || maxZ != 0) { 292 throw new UnsupportedOperationException("Unknown BoundsType"); 293 } 294 setBounds(minX, minY, maxX, maxY); 295 sortMinMax(); 296 } 297 298 @Override public void setBoundsAndSort(Point2D p1, Point2D p2) { 299 setBoundsAndSort(p1.x, p1.y, p2.x, p2.y); 300 } 301 302 // Note: this implementation is exactly the same as BoxBounds. I could put a default 303 // implementation in BaseBounds which calls the getters, or I could move the minX, minY 304 // etc up to BaseBounds, or I could (maybe?) have BoxBounds extend from RectBounds or 305 // have both extend a common parent. In the end I wanted direct access to the fields 306 // but this was the only way to get it without making a more major change. 307 @Override public RectBounds flattenInto(RectBounds bounds) { 308 // Create the bounds if we need to 309 if (bounds == null) bounds = new RectBounds(); 310 // Make it empty if we need to 311 if (isEmpty()) return bounds.makeEmpty(); 312 // Populate it with values otherwise 313 bounds.setBounds(minX, minY, maxX, maxY); 314 return bounds; 315 } 316 317 public void unionWith(RectBounds other) { 318 // Short circuit union if either bounds is empty. 319 if (other.isEmpty()) return; 320 if (this.isEmpty()) { 321 setBounds(other); 322 return; 323 } 324 325 minX = Math.min(minX, other.getMinX()); 326 minY = Math.min(minY, other.getMinY()); 327 maxX = Math.max(maxX, other.getMaxX()); 328 maxY = Math.max(maxY, other.getMaxY()); 329 } 330 331 public void unionWith(float minX, float minY, float maxX, float maxY) { 332 // Short circuit union if either bounds is empty. 333 if ((maxX < minX) || (maxY < minY)) return; 334 if (this.isEmpty()) { 335 setBounds(minX, minY, maxX, maxY); 336 return; 337 } 338 339 this.minX = Math.min(this.minX, minX); 340 this.minY = Math.min(this.minY, minY); 341 this.maxX = Math.max(this.maxX, maxX); 342 this.maxY = Math.max(this.maxY, maxY); 343 } 344 345 @Override public void add(float x, float y, float z) { 346 if (z != 0) { 347 throw new UnsupportedOperationException("Unknown BoundsType"); 348 } 349 unionWith(x, y, x, y); 350 } 351 352 public void add(float x, float y) { 353 unionWith(x, y, x, y); 354 } 355 356 @Override public void add(Point2D p) { 357 add(p.x, p.y); 358 } 359 360 @Override public void intersectWith(BaseBounds other) { 361 // Short circuit intersect if either bounds is empty. 362 if (this.isEmpty()) return; 363 if (other.isEmpty()) { 364 makeEmpty(); 365 return; 366 } 367 368 minX = Math.max(minX, other.getMinX()); 369 minY = Math.max(minY, other.getMinY()); 370 maxX = Math.min(maxX, other.getMaxX()); 371 maxY = Math.min(maxY, other.getMaxY()); 372 } 373 374 @Override public void intersectWith(Rectangle other) { 375 float x = other.x; 376 float y = other.y; 377 intersectWith(x, y, x + other.width, y + other.height); 378 } 379 380 public void intersectWith(float minX, float minY, float maxX, float maxY) { 381 // Short circuit intersect if either bounds is empty. 382 if (this.isEmpty()) return; 383 if ((maxX < minX) || (maxY < minY)) { 384 makeEmpty(); 385 return; 386 } 387 388 this.minX = Math.max(this.minX, minX); 389 this.minY = Math.max(this.minY, minY); 390 this.maxX = Math.min(this.maxX, maxX); 391 this.maxY = Math.min(this.maxY, maxY); 392 } 393 394 @Override public void intersectWith(float minX, float minY, float minZ, 395 float maxX, float maxY, float maxZ) { 396 // Short circuit intersect if either bounds is empty. 397 if (this.isEmpty()) return; 398 if ((maxX < minX) || (maxY < minY) || (maxZ < minZ)) { 399 makeEmpty(); 400 return; 401 } 402 403 this.minX = Math.max(this.minX, minX); 404 this.minY = Math.max(this.minY, minY); 405 this.maxX = Math.min(this.maxX, maxX); 406 this.maxY = Math.min(this.maxY, maxY); 407 } 408 409 @Override public boolean contains(Point2D p) { 410 if ((p == null) || isEmpty()) return false; 411 return (p.x >= minX && p.x <= maxX && p.y >= minY && p.y <= maxY); 412 } 413 414 @Override public boolean contains(float x, float y) { 415 if (isEmpty()) return false; 416 return (x >= minX && x <= maxX && y >= minY && y <= maxY); 417 } 418 419 /** 420 * Determines whether the given <code>other</code> RectBounds is completely 421 * contained within this RectBounds. Equivalent RectBounds will return true. 422 * 423 * @param other The other rect bounds to check against. 424 * @return Whether the other rect bounds is contained within this one, which also 425 * includes equivalence. 426 */ 427 public boolean contains(RectBounds other) { 428 if (isEmpty() || other.isEmpty()) return false; 429 return minX <= other.minX && maxX >= other.maxX && minY <= other.minY && maxY >= other.maxY; 430 } 431 432 @Override public boolean intersects(float x, float y, float width, float height) { 433 if (isEmpty()) return false; 434 return (x + width >= minX && 435 y + height >= minY && 436 x <= maxX && 437 y <= maxY); 438 } 439 440 public boolean intersects(BaseBounds other) { 441 if ((other == null) || other.isEmpty() || isEmpty()) { 442 return false; 443 } 444 return (other.getMaxX() >= minX && 445 other.getMaxY() >= minY && 446 other.getMaxZ() >= getMinZ() && 447 other.getMinX() <= maxX && 448 other.getMinY() <= maxY && 449 other.getMinZ() <= getMaxZ()); 450 } 451 452 @Override public boolean disjoint(float x, float y, float width, float height) { 453 if (isEmpty()) return true; 454 return (x + width < minX || 455 y + height < minY || 456 x > maxX || 457 y > maxY); 458 } 459 460 public boolean disjoint(RectBounds other) { 461 if ((other == null) || other.isEmpty() || isEmpty()) { 462 return true; 463 } 464 return (other.getMaxX() < minX || 465 other.getMaxY() < minY || 466 other.getMinX() > maxX || 467 other.getMinY() > maxY); 468 } 469 470 @Override public boolean isEmpty() { 471 // NaN values will cause the comparisons to fail and return "empty" 472 return !(maxX >= minX && maxY >= minY); 473 } 474 475 /** 476 * Adjusts the edges of this RectBounds "outward" toward integral boundaries, 477 * such that the rounded bounding box will always full enclose the original 478 * bounding box. 479 */ 480 @Override public void roundOut() { 481 minX = (float) Math.floor(minX); 482 minY = (float) Math.floor(minY); 483 maxX = (float) Math.ceil(maxX); 484 maxY = (float) Math.ceil(maxY); 485 } 486 487 public void grow(float h, float v) { 488 minX -= h; 489 maxX += h; 490 minY -= v; 491 maxY += v; 492 } 493 494 @Override public BaseBounds deriveWithPadding(float h, float v, float d) { 495 if (d == 0) { 496 grow(h, v); 497 return this; 498 } 499 BoxBounds bb = new BoxBounds(minX, minY, 0, maxX, maxY, 0); 500 bb.grow(h, v, d); 501 return bb; 502 } 503 504 // for convenience, this function returns a reference to itself, so we can 505 // change from using "bounds.makeEmpty(); return bounds;" to just 506 // "return bounds.makeEmpty()" 507 @Override public RectBounds makeEmpty() { 508 minX = minY = 0.0f; 509 maxX = maxY = -1.0f; 510 return this; 511 } 512 513 @Override protected void sortMinMax() { 514 if (minX > maxX) { 515 float tmp = maxX; 516 maxX = minX; 517 minX = tmp; 518 } 519 if (minY > maxY) { 520 float tmp = maxY; 521 maxY = minY; 522 minY = tmp; 523 } 524 } 525 526 @Override public void translate(float x, float y, float z) { 527 setMinX(getMinX() + x); 528 setMinY(getMinY() + y); 529 setMaxX(getMaxX() + x); 530 setMaxY(getMaxY() + y); 531 } 532 533 @Override public boolean equals(Object obj) { 534 if (obj == null) return false; 535 if (getClass() != obj.getClass()) return false; 536 537 final RectBounds other = (RectBounds) obj; 538 if (minX != other.getMinX()) return false; 539 if (minY != other.getMinY()) return false; 540 if (maxX != other.getMaxX()) return false; 541 if (maxY != other.getMaxY()) return false; 542 return true; 543 } 544 545 @Override public int hashCode() { 546 int hash = 7; 547 hash = 79 * hash + Float.floatToIntBits(minX); 548 hash = 79 * hash + Float.floatToIntBits(minY); 549 hash = 79 * hash + Float.floatToIntBits(maxX); 550 hash = 79 * hash + Float.floatToIntBits(maxY); 551 return hash; 552 } 553 554 @Override public String toString() { 555 return "RectBounds { minX:" + minX + ", minY:" + minY + ", maxX:" + maxX + ", maxY:" + maxY + "} (w:" + (maxX-minX) + ", h:" + (maxY-minY) +")"; 556 } 557 }