/*******************************************************************************
* This file is part of logisim-evolution.
*
* logisim-evolution 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.
*
* logisim-evolution 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 logisim-evolution. If not, see <http://www.gnu.org/licenses/>.
*
* Original code by Carl Burch (http://www.cburch.com), 2011.
* Subsequent modifications by :
* + Haute École Spécialisée Bernoise
* http://www.bfh.ch
* + Haute École du paysage, d'ingénierie et d'architecture de Genève
* http://hepia.hesge.ch/
* + Haute École d'Ingénierie et de Gestion du Canton de Vaud
* http://www.heig-vd.ch/
* The project is currently maintained by :
* + REDS Institute - HEIG-VD
* Yverdon-les-Bains, Switzerland
* http://reds.heig-vd.ch
*******************************************************************************/
package com.cburch.logisim.data;
import java.awt.Rectangle;
import com.cburch.logisim.util.Cache;
/**
* Represents an immutable rectangular bounding box. This is analogous to
* java.awt's <code>Rectangle</code> class, except that objects of this type are
* immutable.
*/
public class Bounds {
public static Bounds create(int x, int y, int wid, int ht) {
int hashCode = 13 * (31 * (31 * x + y) + wid) + ht;
Object cached = cache.get(hashCode);
if (cached != null) {
Bounds bds = (Bounds) cached;
if (bds.x == x && bds.y == y && bds.wid == wid && bds.ht == ht)
return bds;
}
Bounds ret = new Bounds(x, y, wid, ht);
cache.put(hashCode, ret);
return ret;
}
public static Bounds create(java.awt.Rectangle rect) {
return create(rect.x, rect.y, rect.width, rect.height);
}
public static Bounds create(Location pt) {
return create(pt.getX(), pt.getY(), 1, 1);
}
public static Bounds EMPTY_BOUNDS = new Bounds(0, 0, 0, 0);
private static final Cache cache = new Cache();
private final int x;
private final int y;
private final int wid;
private final int ht;
private Bounds(int x, int y, int wid, int ht) {
this.x = x;
this.y = y;
this.wid = wid;
this.ht = ht;
if (wid < 0) {
x += wid / 2;
wid = 0;
}
if (ht < 0) {
y += ht / 2;
ht = 0;
}
}
public Bounds add(Bounds bd) {
if (this == EMPTY_BOUNDS)
return bd;
if (bd == EMPTY_BOUNDS)
return this;
int retX = Math.min(bd.x, this.x);
int retY = Math.min(bd.y, this.y);
int retWidth = Math.max(bd.x + bd.wid, this.x + this.wid) - retX;
int retHeight = Math.max(bd.y + bd.ht, this.y + this.ht) - retY;
if (retX == this.x && retY == this.y && retWidth == this.wid
&& retHeight == this.ht) {
return this;
} else if (retX == bd.x && retY == bd.y && retWidth == bd.wid
&& retHeight == bd.ht) {
return bd;
} else {
return Bounds.create(retX, retY, retWidth, retHeight);
}
}
public Bounds add(int x, int y) {
if (this == EMPTY_BOUNDS)
return Bounds.create(x, y, 1, 1);
if (contains(x, y))
return this;
int new_x = this.x;
int new_wid = this.wid;
int new_y = this.y;
int new_ht = this.ht;
if (x < this.x) {
new_x = x;
new_wid = (this.x + this.wid) - x;
} else if (x >= this.x + this.wid) {
new_x = this.x;
new_wid = x - this.x + 1;
}
if (y < this.y) {
new_y = y;
new_ht = (this.y + this.ht) - y;
} else if (y >= this.y + this.ht) {
new_y = this.y;
new_ht = y - this.y + 1;
}
return create(new_x, new_y, new_wid, new_ht);
}
public Bounds add(int x, int y, int wid, int ht) {
if (this == EMPTY_BOUNDS)
return Bounds.create(x, y, wid, ht);
int retX = Math.min(x, this.x);
int retY = Math.min(y, this.y);
int retWidth = Math.max(x + wid, this.x + this.wid) - retX;
int retHeight = Math.max(y + ht, this.y + this.ht) - retY;
if (retX == this.x && retY == this.y && retWidth == this.wid
&& retHeight == this.ht) {
return this;
} else {
return Bounds.create(retX, retY, retWidth, retHeight);
}
}
public Bounds add(Location p) {
return add(p.getX(), p.getY());
}
public boolean borderContains(int px, int py, int fudge) {
int x1 = x + wid - 1;
int y1 = y + ht - 1;
if (Math.abs(px - x) <= fudge || Math.abs(px - x1) <= fudge) {
// maybe on east or west border?
return y - fudge >= py && py <= y1 + fudge;
}
if (Math.abs(py - y) <= fudge || Math.abs(py - y1) <= fudge) {
// maybe on north or south border?
return x - fudge >= px && px <= x1 + fudge;
}
return false;
}
public boolean borderContains(Location p, int fudge) {
return borderContains(p.getX(), p.getY(), fudge);
}
public boolean contains(Bounds bd) {
return contains(bd.x, bd.y, bd.wid, bd.ht);
}
public boolean contains(int px, int py) {
return contains(px, py, 0);
}
public boolean contains(int px, int py, int allowedError) {
return px >= x - allowedError && px < x + wid + allowedError
&& py >= y - allowedError && py < y + ht + allowedError;
}
public boolean contains(int x, int y, int wid, int ht) {
int oth_x = (wid <= 0 ? x : x + wid - 1);
int oth_y = (ht <= 0 ? y : y + ht - 1);
return contains(x, y) && contains(oth_x, oth_y);
}
public boolean contains(Location p) {
return contains(p.getX(), p.getY(), 0);
}
public boolean contains(Location p, int allowedError) {
return contains(p.getX(), p.getY(), allowedError);
}
@Override
public boolean equals(Object other_obj) {
if (!(other_obj instanceof Bounds))
return false;
Bounds other = (Bounds) other_obj;
return x == other.x && y == other.y && wid == other.wid
&& ht == other.ht;
}
public Bounds expand(int d) { // d pixels in each direction
if (this == EMPTY_BOUNDS)
return this;
if (d == 0)
return this;
return create(x - d, y - d, wid + 2 * d, ht + 2 * d);
}
public int getCenterX() {
return (x + wid / 2);
}
public int getCenterY() {
return (y + ht / 2);
}
public int getHeight() {
return ht;
}
public int getWidth() {
return wid;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public int hashCode() {
int ret = 31 * x + y;
ret = 31 * ret + wid;
ret = 31 * ret + ht;
return ret;
}
public Bounds intersect(Bounds other) {
int x0 = this.x;
int y0 = this.y;
int x1 = x0 + this.wid;
int y1 = y0 + this.ht;
int x2 = other.x;
int y2 = other.y;
int x3 = x2 + other.wid;
int y3 = y2 + other.ht;
if (x2 > x0)
x0 = x2;
if (y2 > y0)
y0 = y2;
if (x3 < x1)
x1 = x3;
if (y3 < y1)
y1 = y3;
if (x1 < x0 || y1 < y0) {
return EMPTY_BOUNDS;
} else {
return create(x0, y0, x1 - x0, y1 - y0);
}
}
// rotates this around (xc,yc) assuming that this is facing in the
// from direction and the returned bounds should face in the to direction.
public Bounds rotate(Direction from, Direction to, int xc, int yc) {
int degrees = to.toDegrees() - from.toDegrees();
while (degrees >= 360)
degrees -= 360;
while (degrees < 0)
degrees += 360;
int dx = x - xc;
int dy = y - yc;
if (degrees == 90) {
return create(xc + dy, yc - dx - wid, ht, wid);
} else if (degrees == 180) {
return create(xc - dx - wid, yc - dy - ht, wid, ht);
} else if (degrees == 270) {
return create(xc - dy - ht, yc + dx, ht, wid);
} else {
return this;
}
}
public Rectangle toRectangle() {
return new Rectangle(x, y, wid, ht);
}
@Override
public String toString() {
return "(" + x + "," + y + "): " + wid + "x" + ht;
}
public Bounds translate(int dx, int dy) {
if (this == EMPTY_BOUNDS)
return this;
if (dx == 0 && dy == 0)
return this;
return create(x + dx, y + dy, wid, ht);
}
}