[1097] | 1 | package org.expeditee.core.bounds;
|
---|
| 2 |
|
---|
| 3 | import org.expeditee.Util;
|
---|
| 4 | import org.expeditee.core.Dimension;
|
---|
| 5 | import org.expeditee.core.Line;
|
---|
| 6 | import org.expeditee.core.Point;
|
---|
| 7 |
|
---|
| 8 | /**
|
---|
| 9 | * A bounds that represents a rectangular area where the left and right sides are parallel
|
---|
| 10 | * to the y-axis and the top and bottom sides are parallel to the x-axis.
|
---|
| 11 | *
|
---|
| 12 | * @author cts16
|
---|
| 13 | */
|
---|
| 14 | public class AxisAlignedBoxBounds extends Bounds {
|
---|
| 15 |
|
---|
| 16 | /** The top-left corner of the bounds. */
|
---|
| 17 | private Point _topLeft;
|
---|
| 18 | /** The width and height of the rectangular bounds. */
|
---|
| 19 | private Dimension _size;
|
---|
| 20 |
|
---|
| 21 | /** Creates an AxisAlignedBoxBounds at the given location with the given size. */
|
---|
| 22 | public AxisAlignedBoxBounds(Point topLeft, Dimension size)
|
---|
| 23 | {
|
---|
| 24 | setTopLeft(topLeft);
|
---|
| 25 | setSize(size);
|
---|
| 26 | }
|
---|
| 27 |
|
---|
| 28 | /** Creates an AxisAlignedBoxBounds at the given location with the given size. */
|
---|
| 29 | public AxisAlignedBoxBounds(int x, int y, int width, int height)
|
---|
| 30 | {
|
---|
| 31 | this(new Point(x, y), new Dimension(width, height));
|
---|
| 32 | }
|
---|
| 33 |
|
---|
| 34 | /** Creates an AxisAlignedBoxBounds with the same location and size as <code>other</code>. */
|
---|
| 35 | public AxisAlignedBoxBounds(AxisAlignedBoxBounds other)
|
---|
| 36 | {
|
---|
| 37 | if (other != null) {
|
---|
| 38 | setTopLeft(other._topLeft);
|
---|
| 39 | setSize(other._size);
|
---|
| 40 | } else {
|
---|
| 41 | setTopLeft(null);
|
---|
| 42 | setSize(null);
|
---|
| 43 | }
|
---|
| 44 | }
|
---|
| 45 |
|
---|
| 46 | /** Creates an AxisAlignedBoxBounds that completely encloses the given bounds. */
|
---|
| 47 | public static AxisAlignedBoxBounds getEnclosing(Bounds b)
|
---|
| 48 | {
|
---|
| 49 | if (b == null) return null;
|
---|
| 50 |
|
---|
| 51 | int maxX = b.getMaxX();
|
---|
| 52 | int minX = b.getMinX();
|
---|
| 53 | int maxY = b.getMaxY();
|
---|
| 54 | int minY = b.getMinY();
|
---|
| 55 |
|
---|
| 56 | Point topLeft = new Point(minX, minY);
|
---|
| 57 | Dimension size = new Dimension(maxX - minX + 1, maxY - minY + 1);
|
---|
| 58 |
|
---|
| 59 | return new AxisAlignedBoxBounds(topLeft, size);
|
---|
| 60 | }
|
---|
| 61 |
|
---|
| 62 | /** Gets the top-left corner position of this AxisAlignedBoxBounds. */
|
---|
| 63 | public Point getTopLeft()
|
---|
| 64 | {
|
---|
| 65 | return _topLeft;
|
---|
| 66 | }
|
---|
| 67 |
|
---|
| 68 | /** Gets the top-left corner position of this AxisAlignedBoxBounds. */
|
---|
| 69 | public Point getBottomRight()
|
---|
| 70 | {
|
---|
| 71 | return _topLeft.clone().add(_size.width - 1, _size.height - 1);
|
---|
| 72 | }
|
---|
| 73 |
|
---|
| 74 | /** Sets the top-left corner of this AxisAlignedBoxBounds to the given point. */
|
---|
| 75 | public void setTopLeft(Point topLeft)
|
---|
| 76 | {
|
---|
| 77 | if (topLeft == null) topLeft = new Point(0, 0);
|
---|
| 78 | this._topLeft = topLeft;
|
---|
| 79 | }
|
---|
| 80 |
|
---|
| 81 | /** Gets the size of this Bounds. */
|
---|
| 82 | public Dimension getSize()
|
---|
| 83 | {
|
---|
| 84 | return _size;
|
---|
| 85 | }
|
---|
| 86 |
|
---|
| 87 | /** Sets the size of this Bounds (null sets to 1x1). */
|
---|
| 88 | public void setSize(Dimension size)
|
---|
| 89 | {
|
---|
| 90 | if (size == null) size = new Dimension(1, 1);
|
---|
| 91 | this._size = size;
|
---|
| 92 | }
|
---|
| 93 |
|
---|
| 94 | /** Gets the width of this AxisAlignedBoxBounds. */
|
---|
| 95 | public int getWidth()
|
---|
| 96 | {
|
---|
| 97 | return _size.width;
|
---|
| 98 | }
|
---|
| 99 |
|
---|
| 100 | /** Gets the height of this AxisAlignedBoxBounds. */
|
---|
| 101 | public int getHeight()
|
---|
| 102 | {
|
---|
| 103 | return _size.height;
|
---|
| 104 | }
|
---|
| 105 |
|
---|
| 106 | /** Gets the x-coordinate of the centre of this AxisAlignedBoxBounds. */
|
---|
| 107 | public int getCentreX()
|
---|
| 108 | {
|
---|
[1142] | 109 | return _topLeft.getX() + _size.width / 2;
|
---|
[1097] | 110 | }
|
---|
| 111 |
|
---|
| 112 | /** Gets the y-coordinate of the centre of this AxisAlignedBoxBounds. */
|
---|
| 113 | public int getCentreY()
|
---|
| 114 | {
|
---|
[1142] | 115 | return _topLeft.getY() + _size.height / 2;
|
---|
[1097] | 116 | }
|
---|
| 117 |
|
---|
| 118 | /** Gets the coordinates of the centre of this AxisAlignedBoxBounds. */
|
---|
| 119 | public Point getCentre()
|
---|
| 120 | {
|
---|
| 121 | return new Point(getCentreX(), getCentreY());
|
---|
| 122 | }
|
---|
| 123 |
|
---|
| 124 | /**
|
---|
| 125 | * Updates this Bounds so it covers both its original area and
|
---|
| 126 | * the area of the given bounds (plus some possible extra area).
|
---|
| 127 | *
|
---|
| 128 | * E.g. /////////**********
|
---|
| 129 | * / / *
|
---|
| 130 | * / other / \\\\\\
|
---|
| 131 | * ///////// \ \
|
---|
| 132 | * * \this\
|
---|
| 133 | * * result \ \
|
---|
| 134 | * *************\\\\\\
|
---|
| 135 | */
|
---|
| 136 | public AxisAlignedBoxBounds combineWith(AxisAlignedBoxBounds other)
|
---|
| 137 | {
|
---|
| 138 | // Can't combine with nothing
|
---|
| 139 | if (other == null) return this;
|
---|
| 140 |
|
---|
| 141 | int top = Math.min(this.getMinY(), other.getMinY());
|
---|
| 142 | int bottom = Math.max(this.getMaxY(), other.getMaxY());
|
---|
| 143 | int left = Math.min(this.getMinX(), other.getMinX());
|
---|
| 144 | int right = Math.max(this.getMaxX(), other.getMaxX());
|
---|
| 145 |
|
---|
| 146 | _topLeft.set(left, top);
|
---|
| 147 | _size.width = right - left + 1;
|
---|
| 148 | _size.height = bottom - top + 1;
|
---|
| 149 |
|
---|
| 150 | return this;
|
---|
| 151 | }
|
---|
| 152 |
|
---|
| 153 | /**
|
---|
| 154 | * Expands the bounds to encompass the given point.
|
---|
| 155 | *
|
---|
| 156 | * @param p
|
---|
| 157 | * The point to cover.
|
---|
| 158 | */
|
---|
| 159 | public AxisAlignedBoxBounds combineWith(Point p)
|
---|
| 160 | {
|
---|
| 161 | if (p == null) return this;
|
---|
| 162 |
|
---|
| 163 | return combineWith(new AxisAlignedBoxBounds(p, new Dimension(1, 1)));
|
---|
| 164 | }
|
---|
| 165 |
|
---|
| 166 | @Override
|
---|
| 167 | public boolean contains(int x, int y)
|
---|
| 168 | {
|
---|
[1142] | 169 | return (x >= _topLeft.getX() &&
|
---|
| 170 | x < (_topLeft.getX() + _size.width) &&
|
---|
| 171 | y >= _topLeft.getY() &&
|
---|
| 172 | y < (_topLeft.getY() + _size.height));
|
---|
[1097] | 173 | }
|
---|
| 174 |
|
---|
| 175 | @Override
|
---|
| 176 | public int getMaxX()
|
---|
| 177 | {
|
---|
[1142] | 178 | return _topLeft.getX() + _size.width - 1;
|
---|
[1097] | 179 | }
|
---|
| 180 |
|
---|
| 181 | @Override
|
---|
| 182 | public int getMinX()
|
---|
| 183 | {
|
---|
[1142] | 184 | return _topLeft.getX();
|
---|
[1097] | 185 | }
|
---|
| 186 |
|
---|
| 187 | @Override
|
---|
| 188 | public int getMaxY()
|
---|
| 189 | {
|
---|
[1142] | 190 | return _topLeft.getY() + _size.height - 1;
|
---|
[1097] | 191 | }
|
---|
| 192 |
|
---|
| 193 | @Override
|
---|
| 194 | public int getMinY()
|
---|
| 195 | {
|
---|
[1142] | 196 | return _topLeft.getY();
|
---|
[1097] | 197 | }
|
---|
| 198 |
|
---|
| 199 | @Override
|
---|
| 200 | public AxisAlignedBoxBounds translate(int x, int y)
|
---|
| 201 | {
|
---|
| 202 | _topLeft.add(x, y);
|
---|
| 203 |
|
---|
| 204 | return this;
|
---|
| 205 | }
|
---|
| 206 |
|
---|
| 207 | @Override
|
---|
| 208 | public boolean equals(Bounds other)
|
---|
| 209 | {
|
---|
| 210 | if (other instanceof AxisAlignedBoxBounds) {
|
---|
| 211 | return this.getTopLeft().equals(((AxisAlignedBoxBounds) other).getTopLeft()) && this.getSize().equals(((AxisAlignedBoxBounds) other).getSize());
|
---|
| 212 | }
|
---|
| 213 |
|
---|
| 214 | return false;
|
---|
| 215 | }
|
---|
| 216 |
|
---|
| 217 | @Override
|
---|
| 218 | public boolean intersects(AxisAlignedBoxBounds other)
|
---|
| 219 | {
|
---|
| 220 | if (other == null) return false;
|
---|
| 221 |
|
---|
| 222 | boolean completelyAbove = other.getMaxY() < this.getMinY();
|
---|
| 223 | boolean completelyBelow = other.getMinY() > this.getMaxY();
|
---|
| 224 | boolean completelyLeft = other.getMaxX() < this.getMinX();
|
---|
| 225 | boolean completelyRight = other.getMinX() > this.getMaxX();
|
---|
| 226 |
|
---|
| 227 | return !(completelyAbove || completelyBelow || completelyLeft || completelyRight);
|
---|
| 228 | }
|
---|
| 229 |
|
---|
| 230 | @Override
|
---|
| 231 | public boolean intersects(CombinationBounds other)
|
---|
| 232 | {
|
---|
| 233 | // Defer to the CombinationBounds routine
|
---|
| 234 | return other.intersects(this);
|
---|
| 235 | }
|
---|
| 236 |
|
---|
| 237 | @Override
|
---|
| 238 | public boolean intersects(EllipticalBounds other)
|
---|
| 239 | {
|
---|
| 240 | // Defer to the EllipticalBounds routine
|
---|
| 241 | return other.intersects(this);
|
---|
| 242 | }
|
---|
| 243 |
|
---|
| 244 | @Override
|
---|
| 245 | public boolean intersects(PolygonBounds other)
|
---|
| 246 | {
|
---|
| 247 | // Defer to the PolygonBounds routine
|
---|
| 248 | return other.intersects(this);
|
---|
| 249 | }
|
---|
| 250 |
|
---|
| 251 | /** Gets an array of four points representing the corners of the box. */
|
---|
| 252 | public Point[] getCorners()
|
---|
| 253 | {
|
---|
| 254 | Point[] corners = new Point[4];
|
---|
| 255 |
|
---|
| 256 | corners[0] = new Point(getMinX(), getMinY());
|
---|
| 257 | corners[1] = new Point(getMaxX(), getMinY());
|
---|
| 258 | corners[2] = new Point(getMaxX(), getMaxY());
|
---|
| 259 | corners[3] = new Point(getMinX(), getMaxY());
|
---|
| 260 |
|
---|
| 261 | return corners;
|
---|
| 262 | }
|
---|
| 263 |
|
---|
| 264 | /** Gets an array of four lines representing the border of this Bounds. */
|
---|
| 265 | public Line[] getBorderLines()
|
---|
| 266 | {
|
---|
| 267 | Line[] lines = new Line[4];
|
---|
| 268 | Point[] corners = getCorners();
|
---|
| 269 |
|
---|
| 270 | lines[0] = new Line(corners[0], corners[1]);
|
---|
| 271 | lines[1] = new Line(corners[1], corners[2]);
|
---|
| 272 | lines[2] = new Line(corners[2], corners[3]);
|
---|
| 273 | lines[3] = new Line(corners[3], corners[0]);
|
---|
| 274 |
|
---|
| 275 | return lines;
|
---|
| 276 | }
|
---|
| 277 |
|
---|
| 278 | @Override
|
---|
| 279 | public double getArea()
|
---|
| 280 | {
|
---|
| 281 | return _size.width * _size.height;
|
---|
| 282 | }
|
---|
| 283 |
|
---|
| 284 | @Override
|
---|
| 285 | public boolean perimeterContains(Point p, double error)
|
---|
| 286 | {
|
---|
| 287 | Line[] borderLines = getBorderLines();
|
---|
| 288 |
|
---|
| 289 | for (Line borderLine : borderLines) {
|
---|
| 290 | if (borderLine.contains(p, error)) return true;
|
---|
| 291 | }
|
---|
| 292 |
|
---|
| 293 | return false;
|
---|
| 294 | }
|
---|
| 295 |
|
---|
| 296 | @Override
|
---|
| 297 | public boolean completelyContains(AxisAlignedBoxBounds other)
|
---|
| 298 | {
|
---|
| 299 | if (other == null) return false;
|
---|
| 300 |
|
---|
| 301 | return contains(other.getTopLeft()) && contains(other.getBottomRight());
|
---|
| 302 | }
|
---|
| 303 |
|
---|
| 304 | @Override
|
---|
| 305 | public boolean completelyContains(CombinationBounds other)
|
---|
| 306 | {
|
---|
| 307 | if (other == null) return false;
|
---|
| 308 |
|
---|
| 309 | return completelyContains(getEnclosing(other));
|
---|
| 310 | }
|
---|
| 311 |
|
---|
| 312 | @Override
|
---|
| 313 | public boolean completelyContains(EllipticalBounds other)
|
---|
| 314 | {
|
---|
| 315 | if (other == null) return false;
|
---|
| 316 |
|
---|
| 317 | return completelyContains(getEnclosing(other));
|
---|
| 318 | }
|
---|
| 319 |
|
---|
| 320 | @Override
|
---|
| 321 | public boolean completelyContains(PolygonBounds other)
|
---|
| 322 | {
|
---|
| 323 | if (other == null) return false;
|
---|
| 324 |
|
---|
| 325 | return completelyContains(getEnclosing(other));
|
---|
| 326 | }
|
---|
| 327 |
|
---|
| 328 | /** Gets a polygon that represents the same area as this box. */
|
---|
| 329 | public PolygonBounds getPolygon()
|
---|
| 330 | {
|
---|
| 331 | return PolygonBounds.fromBox(this);
|
---|
| 332 | }
|
---|
| 333 |
|
---|
| 334 | /** Gets the area of intersection between this box and the other. */
|
---|
| 335 | public AxisAlignedBoxBounds intersectionWith(AxisAlignedBoxBounds other)
|
---|
| 336 | {
|
---|
| 337 | if (intersects(other)) {
|
---|
| 338 | int minX = getMinX() > other.getMinX() ? getMinX() : other.getMinX();
|
---|
| 339 | int minY = getMinY() > other.getMinY() ? getMinY() : other.getMinY();
|
---|
| 340 | int maxX = getMaxX() < other.getMaxX() ? getMaxX() : other.getMaxX();
|
---|
| 341 | int maxY = getMaxY() < other.getMaxY() ? getMaxY() : other.getMaxY();
|
---|
| 342 |
|
---|
| 343 | return new AxisAlignedBoxBounds(minX, minY, maxX - minX + 1, maxY - minY + 1);
|
---|
| 344 | } else {
|
---|
| 345 | return null;
|
---|
| 346 | }
|
---|
| 347 | }
|
---|
[1523] | 348 |
|
---|
| 349 | public Point distanceFrom(AxisAlignedBoxBounds other) {
|
---|
| 350 | boolean left = other.getMaxX() < this.getMinX();
|
---|
| 351 | boolean right = this.getMaxX() < other.getMinX();
|
---|
| 352 | boolean top = this.getMaxY() < other.getMinY();
|
---|
| 353 | boolean bottom = other.getMaxY() < this.getMinY();
|
---|
| 354 |
|
---|
| 355 | if (top && left) {
|
---|
| 356 | Point thisP = new Point(this.getMinX(), this.getMaxY());
|
---|
| 357 | Point otherP = new Point(other.getMaxX(), other.getMinY());
|
---|
| 358 | return Point.difference(thisP, otherP);
|
---|
| 359 | } else if (left && bottom) {
|
---|
| 360 | Point thisP = new Point(this.getMinX(), this.getMinY());
|
---|
| 361 | Point otherP = new Point(other.getMaxX(), other.getMaxY());
|
---|
| 362 | return Point.difference(thisP, otherP);
|
---|
| 363 | } else if (bottom && right) {
|
---|
| 364 | Point thisP = new Point(this.getMaxX(), this.getMinY());
|
---|
| 365 | Point otherP = new Point(other.getMinX(), other.getMaxY());
|
---|
| 366 | return Point.difference(thisP, otherP);
|
---|
| 367 | } else if (right && top) {
|
---|
| 368 | Point thisP = new Point(this.getMaxX(), this.getMaxY());
|
---|
| 369 | Point otherP = new Point(other.getMinX(), other.getMinY());
|
---|
| 370 | return Point.difference(thisP, otherP);
|
---|
| 371 | } else if (left) {
|
---|
| 372 | return new Point(this.getMinX() - other.getMaxX(), 0);
|
---|
| 373 | } else if (right) {
|
---|
| 374 | return new Point(other.getMinX() - this.getMaxX(), 0);
|
---|
| 375 | } else if (top) {
|
---|
| 376 | return new Point(0, other.getMinY() - this.getMaxY());
|
---|
| 377 | } else if (bottom) {
|
---|
| 378 | return new Point(0, this.getMinY() - other.getMaxY());
|
---|
| 379 | } else {
|
---|
| 380 | return new Point(0, 0);
|
---|
| 381 | }
|
---|
| 382 | }
|
---|
| 383 |
|
---|
[1097] | 384 | /** Gets the bounds that is linearly-interpolated between the two given bounds. */
|
---|
| 385 | public static AxisAlignedBoxBounds lerp(AxisAlignedBoxBounds a, AxisAlignedBoxBounds b, float t)
|
---|
| 386 | {
|
---|
| 387 | if (a == null || b == null) return null;
|
---|
| 388 |
|
---|
| 389 | int minX = (int) Util.lerp(a.getMinX(), b.getMinX(), t);
|
---|
| 390 | int minY = (int) Util.lerp(a.getMinY(), b.getMinY(), t);
|
---|
| 391 | int width = (int) Util.lerp(a.getWidth(), b.getWidth(), t);
|
---|
| 392 | int height = (int) Util.lerp(a.getHeight(), b.getHeight(), t);
|
---|
| 393 |
|
---|
| 394 | return new AxisAlignedBoxBounds(minX, minY, width, height);
|
---|
| 395 | }
|
---|
| 396 |
|
---|
| 397 | @Override
|
---|
| 398 | public AxisAlignedBoxBounds clone()
|
---|
| 399 | {
|
---|
| 400 | return new AxisAlignedBoxBounds(this);
|
---|
| 401 | }
|
---|
[1149] | 402 |
|
---|
| 403 | @Override
|
---|
| 404 | public String toString() {
|
---|
| 405 | return "AxisAlignedBoxBounds with (top left, bottom right): " + getTopLeft() + "," + getBottomRight();
|
---|
| 406 | }
|
---|
[1097] | 407 |
|
---|
| 408 | }
|
---|