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 }