package org.expeditee.core.bounds; import org.expeditee.Util; import org.expeditee.core.Dimension; import org.expeditee.core.Line; import org.expeditee.core.Point; /** * A bounds that represents a rectangular area where the left and right sides are parallel * to the y-axis and the top and bottom sides are parallel to the x-axis. * * @author cts16 */ public class AxisAlignedBoxBounds extends Bounds { /** The top-left corner of the bounds. */ private Point _topLeft; /** The width and height of the rectangular bounds. */ private Dimension _size; /** Creates an AxisAlignedBoxBounds at the given location with the given size. */ public AxisAlignedBoxBounds(Point topLeft, Dimension size) { setTopLeft(topLeft); setSize(size); } /** Creates an AxisAlignedBoxBounds at the given location with the given size. */ public AxisAlignedBoxBounds(int x, int y, int width, int height) { this(new Point(x, y), new Dimension(width, height)); } /** Creates an AxisAlignedBoxBounds with the same location and size as other. */ public AxisAlignedBoxBounds(AxisAlignedBoxBounds other) { if (other != null) { setTopLeft(other._topLeft); setSize(other._size); } else { setTopLeft(null); setSize(null); } } /** Creates an AxisAlignedBoxBounds that completely encloses the given bounds. */ public static AxisAlignedBoxBounds getEnclosing(Bounds b) { if (b == null) return null; int maxX = b.getMaxX(); int minX = b.getMinX(); int maxY = b.getMaxY(); int minY = b.getMinY(); Point topLeft = new Point(minX, minY); Dimension size = new Dimension(maxX - minX + 1, maxY - minY + 1); return new AxisAlignedBoxBounds(topLeft, size); } /** Gets the top-left corner position of this AxisAlignedBoxBounds. */ public Point getTopLeft() { return _topLeft; } /** Gets the top-left corner position of this AxisAlignedBoxBounds. */ public Point getBottomRight() { return _topLeft.clone().add(_size.width - 1, _size.height - 1); } /** Sets the top-left corner of this AxisAlignedBoxBounds to the given point. */ public void setTopLeft(Point topLeft) { if (topLeft == null) topLeft = new Point(0, 0); this._topLeft = topLeft; } /** Gets the size of this Bounds. */ public Dimension getSize() { return _size; } /** Sets the size of this Bounds (null sets to 1x1). */ public void setSize(Dimension size) { if (size == null) size = new Dimension(1, 1); this._size = size; } /** Gets the width of this AxisAlignedBoxBounds. */ public int getWidth() { return _size.width; } /** Gets the height of this AxisAlignedBoxBounds. */ public int getHeight() { return _size.height; } /** Gets the x-coordinate of the centre of this AxisAlignedBoxBounds. */ public int getCentreX() { return _topLeft.getX() + _size.width / 2; } /** Gets the y-coordinate of the centre of this AxisAlignedBoxBounds. */ public int getCentreY() { return _topLeft.getY() + _size.height / 2; } /** Gets the coordinates of the centre of this AxisAlignedBoxBounds. */ public Point getCentre() { return new Point(getCentreX(), getCentreY()); } /** * Updates this Bounds so it covers both its original area and * the area of the given bounds (plus some possible extra area). * * E.g. /////////********** * / / * * / other / \\\\\\ * ///////// \ \ * * \this\ * * result \ \ * *************\\\\\\ */ public AxisAlignedBoxBounds combineWith(AxisAlignedBoxBounds other) { // Can't combine with nothing if (other == null) return this; int top = Math.min(this.getMinY(), other.getMinY()); int bottom = Math.max(this.getMaxY(), other.getMaxY()); int left = Math.min(this.getMinX(), other.getMinX()); int right = Math.max(this.getMaxX(), other.getMaxX()); _topLeft.set(left, top); _size.width = right - left + 1; _size.height = bottom - top + 1; return this; } /** * Expands the bounds to encompass the given point. * * @param p * The point to cover. */ public AxisAlignedBoxBounds combineWith(Point p) { if (p == null) return this; return combineWith(new AxisAlignedBoxBounds(p, new Dimension(1, 1))); } @Override public boolean contains(int x, int y) { return (x >= _topLeft.getX() && x < (_topLeft.getX() + _size.width) && y >= _topLeft.getY() && y < (_topLeft.getY() + _size.height)); } @Override public int getMaxX() { return _topLeft.getX() + _size.width - 1; } @Override public int getMinX() { return _topLeft.getX(); } @Override public int getMaxY() { return _topLeft.getY() + _size.height - 1; } @Override public int getMinY() { return _topLeft.getY(); } @Override public AxisAlignedBoxBounds translate(int x, int y) { _topLeft.add(x, y); return this; } @Override public boolean equals(Bounds other) { if (other instanceof AxisAlignedBoxBounds) { return this.getTopLeft().equals(((AxisAlignedBoxBounds) other).getTopLeft()) && this.getSize().equals(((AxisAlignedBoxBounds) other).getSize()); } return false; } @Override public boolean intersects(AxisAlignedBoxBounds other) { if (other == null) return false; boolean completelyAbove = other.getMaxY() < this.getMinY(); boolean completelyBelow = other.getMinY() > this.getMaxY(); boolean completelyLeft = other.getMaxX() < this.getMinX(); boolean completelyRight = other.getMinX() > this.getMaxX(); return !(completelyAbove || completelyBelow || completelyLeft || completelyRight); } @Override public boolean intersects(CombinationBounds other) { // Defer to the CombinationBounds routine return other.intersects(this); } @Override public boolean intersects(EllipticalBounds other) { // Defer to the EllipticalBounds routine return other.intersects(this); } @Override public boolean intersects(PolygonBounds other) { // Defer to the PolygonBounds routine return other.intersects(this); } /** Gets an array of four points representing the corners of the box. */ public Point[] getCorners() { Point[] corners = new Point[4]; corners[0] = new Point(getMinX(), getMinY()); corners[1] = new Point(getMaxX(), getMinY()); corners[2] = new Point(getMaxX(), getMaxY()); corners[3] = new Point(getMinX(), getMaxY()); return corners; } /** Gets an array of four lines representing the border of this Bounds. */ public Line[] getBorderLines() { Line[] lines = new Line[4]; Point[] corners = getCorners(); lines[0] = new Line(corners[0], corners[1]); lines[1] = new Line(corners[1], corners[2]); lines[2] = new Line(corners[2], corners[3]); lines[3] = new Line(corners[3], corners[0]); return lines; } @Override public double getArea() { return _size.width * _size.height; } @Override public boolean perimeterContains(Point p, double error) { Line[] borderLines = getBorderLines(); for (Line borderLine : borderLines) { if (borderLine.contains(p, error)) return true; } return false; } @Override public boolean completelyContains(AxisAlignedBoxBounds other) { if (other == null) return false; return contains(other.getTopLeft()) && contains(other.getBottomRight()); } @Override public boolean completelyContains(CombinationBounds other) { if (other == null) return false; return completelyContains(getEnclosing(other)); } @Override public boolean completelyContains(EllipticalBounds other) { if (other == null) return false; return completelyContains(getEnclosing(other)); } @Override public boolean completelyContains(PolygonBounds other) { if (other == null) return false; return completelyContains(getEnclosing(other)); } /** Gets a polygon that represents the same area as this box. */ public PolygonBounds getPolygon() { return PolygonBounds.fromBox(this); } /** Gets the area of intersection between this box and the other. */ public AxisAlignedBoxBounds intersectionWith(AxisAlignedBoxBounds other) { if (intersects(other)) { int minX = getMinX() > other.getMinX() ? getMinX() : other.getMinX(); int minY = getMinY() > other.getMinY() ? getMinY() : other.getMinY(); int maxX = getMaxX() < other.getMaxX() ? getMaxX() : other.getMaxX(); int maxY = getMaxY() < other.getMaxY() ? getMaxY() : other.getMaxY(); return new AxisAlignedBoxBounds(minX, minY, maxX - minX + 1, maxY - minY + 1); } else { return null; } } public Point distanceFrom(AxisAlignedBoxBounds other) { boolean left = other.getMaxX() < this.getMinX(); boolean right = this.getMaxX() < other.getMinX(); boolean top = this.getMaxY() < other.getMinY(); boolean bottom = other.getMaxY() < this.getMinY(); if (top && left) { Point thisP = new Point(this.getMinX(), this.getMaxY()); Point otherP = new Point(other.getMaxX(), other.getMinY()); return Point.difference(thisP, otherP); } else if (left && bottom) { Point thisP = new Point(this.getMinX(), this.getMinY()); Point otherP = new Point(other.getMaxX(), other.getMaxY()); return Point.difference(thisP, otherP); } else if (bottom && right) { Point thisP = new Point(this.getMaxX(), this.getMinY()); Point otherP = new Point(other.getMinX(), other.getMaxY()); return Point.difference(thisP, otherP); } else if (right && top) { Point thisP = new Point(this.getMaxX(), this.getMaxY()); Point otherP = new Point(other.getMinX(), other.getMinY()); return Point.difference(thisP, otherP); } else if (left) { return new Point(this.getMinX() - other.getMaxX(), 0); } else if (right) { return new Point(other.getMinX() - this.getMaxX(), 0); } else if (top) { return new Point(0, other.getMinY() - this.getMaxY()); } else if (bottom) { return new Point(0, this.getMinY() - other.getMaxY()); } else { return new Point(0, 0); } } /** Gets the bounds that is linearly-interpolated between the two given bounds. */ public static AxisAlignedBoxBounds lerp(AxisAlignedBoxBounds a, AxisAlignedBoxBounds b, float t) { if (a == null || b == null) return null; int minX = (int) Util.lerp(a.getMinX(), b.getMinX(), t); int minY = (int) Util.lerp(a.getMinY(), b.getMinY(), t); int width = (int) Util.lerp(a.getWidth(), b.getWidth(), t); int height = (int) Util.lerp(a.getHeight(), b.getHeight(), t); return new AxisAlignedBoxBounds(minX, minY, width, height); } @Override public AxisAlignedBoxBounds clone() { return new AxisAlignedBoxBounds(this); } @Override public String toString() { return "AxisAlignedBoxBounds with (top left, bottom right): " + getTopLeft() + "," + getBottomRight(); } }