/*
* This file is part of the Illarion project.
*
* Copyright © 2015 - Illarion e.V.
*
* Illarion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Illarion 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.
*/
package illarion.common.types;
import org.jetbrains.annotations.Contract;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import java.io.Serializable;
/**
* The rectangle class is a helper class that allows to define rectangles and check them for intersection.
*
* @author Martin Karing <nitram@illarion.org>
*/
@NotThreadSafe
public final class Rectangle implements Serializable {
/**
* Serialization UID of this rectangle class.
*/
private static final long serialVersionUID = 1L;
/**
* The first x coordinate of this rectangle. This is the left border of the rectangle.
*/
private int x0;
/**
* The second x coordinate of this rectangle. This is the right border of the rectangle.
*/
private int x1;
/**
* The first y coordinate of this rectangle. This is the bottom border of the rectangle.
*/
private int y0;
/**
* The second y coordinate of this rectangle. This is the top border of the rectangle.
*/
private int y1;
/**
* The private constructor to ensure that new instances are only created by the get method.
*/
public Rectangle() {
reset();
}
public Rectangle(int x, int y, int width, int height) {
set(x, y, width, height);
}
public Rectangle(@Nonnull Rectangle other) {
x0 = other.x0;
x1 = other.x1;
y0 = other.y0;
y1 = other.y1;
}
/**
* Add another rectangle to this one. This basically creates a union of both rectangles.
*
* @param other the rectangle that shall be added to the current instance
*/
public void add(@Nonnull Rectangle other) {
if (isEmpty()) {
set(other);
return;
}
if (other.isEmpty()) {
return;
}
x0 = Math.min(x0, other.x0);
y0 = Math.min(y0, other.y0);
x1 = Math.max(x1, other.x1);
y1 = Math.max(y1, other.y1);
}
/**
* Test this rectangle and another object for being equal.
*
* @param obj the object this Rectangle shall be compared with
* @return {@code true} in case this rectangle and the other one describe the same rectangle
*/
@Override
@Contract(value = "null -> false", pure = true)
public boolean equals(@Nullable Object obj) {
if (super.equals(obj)) {
return true;
}
if (obj instanceof Rectangle) {
Rectangle oRect = (Rectangle) obj;
return (oRect.x0 == x0) && (oRect.x1 == x1) && (oRect.y0 == y0) && (oRect.y1 == y1);
}
return false;
}
/**
* Get the y coordinate (bottom border) of the rectangle.
*
* @return the coordinate of the bottom border
*/
@Contract(pure = true)
public int getBottom() {
return y0;
}
/**
* Get the center x coordinate of the rectangle.
*
* @return the center x coordinate of the rectangle
*/
@Contract(pure = true)
public int getCenterX() {
return (x0 + x1) / 2;
}
/**
* Get the center y coordinate of the rectangle.
*
* @return the center y coordinate of the rectangle
*/
@Contract(pure = true)
public int getCenterY() {
return (y0 + y1) / 2;
}
/**
* Get the height of the rectangle.
*
* @return the height of the rectangle
*/
@Contract(pure = true)
public int getHeight() {
return y1 - y0;
}
/**
* Get the x coordinate (left border) of the rectangle.
*
* @return the coordinate of the left border
*/
@Contract(pure = true)
public int getLeft() {
return x0;
}
/**
* Get the x coordinate (right border) of the rectangle.
*
* @return the coordinate of the right border
*/
@Contract(pure = true)
public int getRight() {
return x1;
}
/**
* Get the y coordinate (top border) of the rectangle.
*
* @return the coordinate of the top border
*/
@Contract(pure = true)
public int getTop() {
return y1;
}
/**
* Get the width of the rectangle.
*
* @return the width of the rectangle
*/
@Contract(pure = true)
public int getWidth() {
return x1 - x0;
}
/**
* Get the x coordinate (left border) of the rectangle.
*
* @return the coordinate of the left border
*/
@Contract(pure = true)
public int getX() {
return x0;
}
/**
* Get the y coordinate (bottom border) of the rectangle.
*
* @return the coordinate of the top border
*/
@Contract(pure = true)
public int getY() {
return y0;
}
/**
* Generate a hash code to identify the object.
*
* @return the hashcode of this object
*/
@Override
@Contract(pure = true)
public int hashCode() {
int retVal = x0 & 0xFF;
retVal <<= Byte.SIZE;
retVal += x1 & 0xFF;
retVal <<= Byte.SIZE;
retVal += y0 & 0xFF;
retVal <<= Byte.SIZE;
retVal += y1 & 0xFF;
return retVal;
}
/**
* Move the current location of the rectangle without changing its height and width.
*
* @param x the change value for the x coordinate
* @param y the change value for the y coordinate
*/
public void move(int x, int y) {
x0 += x;
x1 += x;
y0 += y;
y1 += y;
}
public void expand(int left, int top, int right, int bottom) {
x0 -= left;
x1 += right;
y0 -= bottom;
y1 += top;
}
/**
* Check the two rectangles for intersection.
*
* @param other the second rectangle
* @return {@code true} in case there is an intersection
*/
public boolean intersects(@Nonnull Rectangle other) {
if ((x0 > other.x1) || (x1 < other.x0)) {
return false;
}
return !((y0 > other.y1) || (y1 < other.y0));
}
/**
* Check if the rectangle covers no area.
*
* @return {@code true} if the rectangle covers no area
*/
@Contract(pure = true)
public boolean isEmpty() {
return (x0 == x1) || (y0 == y1);
}
/**
* Check if a coordinate is inside of the rectangle.
*
* @param x the x coordinate to check
* @param y the y coordinate of check
* @return {@code true} in case the coordinates are inside the the rectangle
*/
@Contract(pure = true)
public boolean isInside(int x, int y) {
return (x >= x0) && (y >= y0) && (x < x1) && (y < y1);
}
/**
* Set the values of this instance back to its original values.
*/
public void reset() {
x0 = 0;
x1 = 0;
y0 = 0;
y1 = 0;
}
/**
* Set the properties of this rectangle.
*
* @param x the new x coordinate of this rectangle
* @param y the new y coordinate of this rectangle
* @param width the new width of this rectangle
* @param height the new height of this rectangle
*/
public void set(int x, int y, int width, int height) {
x0 = x;
y0 = y;
x1 = x + Math.max(0, width);
y1 = y + Math.max(0, height);
}
/**
* Set the properties of this rectangle to the values of another rectangle.
*
* @param org the rectangle that shall be copied
*/
public void set(@Nonnull Rectangle org) {
x0 = org.x0;
x1 = org.x1;
y0 = org.y0;
y1 = org.y1;
}
/**
* Get the size of the area covered by this rectangle.
*
* @return the size of the area
*/
@Contract(pure = true)
public int getArea() {
return getWidth() * getHeight();
}
@Override
@Nonnull
@Contract(pure = true)
public String toString() {
return String.format("Rectangle(x:%1$d y:%2$d w:%3$d h:%4$d)", getX(), getY(), getWidth(), getHeight());
}
}