/* * Copyright (C) 2011-2012 Dr. John Lindsay <jlindsay@uoguelph.ca> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package whitebox.structures; /** * * @author Dr. John Lindsay email: jlindsay@uoguelph.ca */ public class BoundingBox implements Comparable<BoundingBox>, java.io.Serializable { public BoundingBox() { this.maxY = Float.NEGATIVE_INFINITY; this.maxX = Float.NEGATIVE_INFINITY; this.minY = Float.POSITIVE_INFINITY; this.minX = Float.POSITIVE_INFINITY; } public BoundingBox(Double minX, Double minY, Double maxX, Double maxY) { this.maxY = maxY; this.minY = minY; this.maxX = maxX; this.minX = minX; } private double maxY = -1; public double getMaxY() { return maxY; } public void setMaxY(double value) { maxY = value; } private double maxX = -1; public double getMaxX() { return maxX; } public void setMaxX(double value) { maxX = value; } private double minY = 0; public double getMinY() { return minY; } public void setMinY(double value) { minY = value; } private double minX = 0; public double getMinX() { return minX; } public void setMinX(double value) { minX = value; } public boolean isNull() { return maxX < minX; } public double getWidth() { return maxX - minX; } public double getHeight() { return maxY - minY; } public double getMinExtent() { if (isNull()) { return 0.0; } double w = getWidth(); double h = getHeight(); if (w < h) { return w; } return h; } public double getMaxExtent() { if (isNull()) { return 0.0; } double w = getWidth(); double h = getHeight(); if (w > h) { return w; } return h; } public boolean near(BoundingBox other, double distance) { if (overlaps(other)) { return true; } if (intersectsAnEdgeOf(other)) { return true; } if (within(other)) { return true; } if (entirelyContains(other)) { return true; } if (Math.abs(other.minY - maxY) <= distance) { return true; } // just south of if (Math.abs(other.maxY - minY) <= distance) { return true; } // just north of if (Math.abs(other.minX - maxX) <= distance) { return true; } // just west of if (Math.abs(other.maxX - minX) <= distance) { return true; } // just east of return false; } public boolean overlaps(BoundingBox other) { if (isNull()) { return false; } if (this.maxY < other.getMinY() || this.maxX < other.getMinX() || this.minY > other.getMaxY() || this.minX > other.getMaxX()) { return false; } else { return true; } } public boolean intersectsAnEdgeOf(BoundingBox other) { if (isNull()) { return false; } double x, y; boolean oneInsideFound = false; boolean oneOutsideFound = false; // at least one of the coordinates has to be within and at least one of them has to be outside for (int a = 0; a < 4; a++) { switch (a) { case 0: x = minX; y = maxY; break; case 1: x = minX; y = minY; break; case 2: x = maxX; y = maxY; break; default: // 3 x = maxX; y = minY; break; } if (!oneInsideFound) { if (y <= other.getMaxY() && y >= other.getMinY() && x <= other.getMaxX() && x >= other.getMinX()) { oneInsideFound = true; } } if (!oneOutsideFound) { if (!(y <= other.getMaxY() && y >= other.getMinY()) || !(x <= other.getMaxX() && x >= other.getMinX())) { oneOutsideFound = true; } } if (oneInsideFound && oneOutsideFound) { return true; } } return false; } public boolean entirelyContainedWithin(BoundingBox other) { if (isNull()) { return false; } if (this.maxY < other.getMaxY() && this.maxX < other.getMaxX() && this.minY > other.getMinY() && this.minX > other.getMinX()) { return true; } else { return false; } } public boolean within(BoundingBox other) { if (isNull()) { return false; } if (this.maxY <= other.getMaxY() && this.maxX <= other.getMaxX() && this.minY >= other.getMinY() && this.minX >= other.getMinX()) { return true; } else { return false; } } public boolean entirelyContains(BoundingBox other) { if (isNull()) { return false; } if (other.getMaxY() < this.maxY && other.getMaxX() < this.maxX && other.getMinY() > this.minY && other.getMinX() > this.minX) { return true; } else { return false; } } public boolean entirelyContains(double x, double y) { if (isNull()) { return false; } return y < this.maxY && x < this.maxX && y > this.minY && x > this.minX; } public boolean contains(BoundingBox other) { if (isNull()) { return false; } return other.getMaxY() <= this.maxY && other.getMaxX() <= this.maxX && other.getMinY() >= this.minY && other.getMinX() >= this.minX; } public BoundingBox intersect(BoundingBox other) { BoundingBox bb = new BoundingBox(); if (!isNull()) { // some performance tests have shown this to be better than Math.min // and Math.max bb.setMaxY((this.maxY <= other.getMaxY()) ? this.maxY : other.getMaxY()); bb.setMaxX((this.maxX <= other.getMaxX()) ? this.maxX : other.getMaxX()); bb.setMinY((this.minY >= other.getMinY()) ? this.minY : other.getMinY()); bb.setMinX((this.minX >= other.getMinX()) ? this.minX : other.getMinX()); } return bb; } public boolean isPointInBox(double x, double y) { if (isNull()) { return false; } return !(this.maxY < y || this.maxX < x || this.minY > y || this.minX > x); } public void expandTo(BoundingBox other) { if (!isNull()) { this.setMaxY((this.maxY >= other.getMaxY()) ? this.maxY : other.getMaxY()); this.setMaxX((this.maxX >= other.getMaxX()) ? this.maxX : other.getMaxX()); this.setMinY((this.minY <= other.getMinY()) ? this.minY : other.getMinY()); this.setMinX((this.minX <= other.getMinX()) ? this.minX : other.getMinX()); } else { this.setMaxY(other.getMaxY()); this.setMaxX(other.getMaxX()); this.setMinY(other.getMinY()); this.setMinX(other.getMinX()); } } public void contractTo(BoundingBox other) { if (!isNull()) { this.setMaxY((this.maxY <= other.getMaxY()) ? this.maxY : other.getMaxY()); this.setMaxX((this.maxX <= other.getMaxX()) ? this.maxX : other.getMaxX()); this.setMinY((this.minY >= other.getMinY()) ? this.minY : other.getMinY()); this.setMinX((this.minX >= other.getMinX()) ? this.minX : other.getMinX()); } else { this.setMaxY(other.getMaxY()); this.setMaxX(other.getMaxX()); this.setMinY(other.getMinY()); this.setMinX(other.getMinX()); } } @Override public BoundingBox clone() { try { //super.clone(); BoundingBox db = new BoundingBox(minX, minY, maxX, maxY); return db; } catch (Exception e) { // do nothing return null; } } @Override public int compareTo(BoundingBox other) { final int BEFORE = -1; final int EQUAL = 0; final int AFTER = 1; if (this.maxY < other.maxY) { return BEFORE; } else if (this.maxY > other.maxY) { return AFTER; } if (this.maxX < other.maxX) { return BEFORE; } else if (this.maxX > other.maxX) { return AFTER; } if (this.minY < other.minY) { return BEFORE; } else if (this.minY > other.minY) { return AFTER; } if (this.minX < other.minX) { return BEFORE; } else if (this.minX > other.minX) { return AFTER; } return EQUAL; } /** * Define equality of state. * @param other BoundingBox for comparison. */ @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof BoundingBox)) { return false; } BoundingBox that = (BoundingBox) other; return (this.maxY == that.maxY) && (this.maxX == that.maxX) && (this.minY == that.minY) && (this.minX == that.minX); } /** * A class that overrides equals must also override hashCode. */ @Override public int hashCode() { int result = HashCodeUtil.SEED; result = HashCodeUtil.hash(result, maxY); result = HashCodeUtil.hash(result, maxX); result = HashCodeUtil.hash(result, minY); result = HashCodeUtil.hash(result, minX); return result; } }