/*
* Copyright 2010-2011 Øyvind Berg (elacin@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.elacin.pdfextract.geom;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Created by IntelliJ IDEA. User: elacin Date: May 19, 2010 Time: 9:43:07 PM <p/> A non-mutable
* rectangle, with union and intercepts bits stolen from javas Rectangle2D. The problem with just
* using that class was that is isnt available in an integer version.
*/
public final class Rectangle extends HasPositionAbstract {
// ------------------------------ FIELDS ------------------------------
public static final Rectangle EMPTY_RECTANGLE = new Rectangle(0.1f, 0.1f, 0.1f, 0.1f);
private transient int hash = -1;
/* caching, we do a lot of comparing */
private transient boolean hasCalculatedHash;
public final float x, y, width, height, endX, endY;
// --------------------------- CONSTRUCTORS ---------------------------
public Rectangle(final float x, final float y, final float width, final float height) {
this.height = height;
this.width = width;
this.x = x;
this.y = y;
endX = x + width;
endY = y + height;
if (height <= 0.0f) {
throw new IllegalArgumentException("height must be positive: " + this);
}
if (width <= 0.0f) {
throw new IllegalArgumentException("width must be positive " + this);
}
setPos(this);
}
// ------------------------ INTERFACE METHODS ------------------------
// --------------------- Interface HasPosition ---------------------
public void calculatePos() {
assert false;
}
// ------------------------ CANONICAL METHODS ------------------------
@SuppressWarnings({ "ALL" })
/* generated */
@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
return true;
}
if ((o == null) || (getClass() != o.getClass())) {
return false;
}
final Rectangle rectangle = (Rectangle) o;
if (Float.compare(rectangle.height, height) != 0) {
return false;
}
if (Float.compare(rectangle.width, width) != 0) {
return false;
}
if (Float.compare(rectangle.x, x) != 0) {
return false;
}
if (Float.compare(rectangle.y, y) != 0) {
return false;
}
return true;
}
@SuppressWarnings({ "ALL" })
/* generated */
@Override
public int hashCode() {
if (!hasCalculatedHash) {
int result = ((x != +0.0f)
? Float.floatToIntBits(x)
: 0);
result = 31 * result + ((y != +0.0f)
? Float.floatToIntBits(y)
: 0);
result = 31 * result + ((width != +0.0f)
? Float.floatToIntBits(width)
: 0);
result = 31 * result + ((height != +0.0f)
? Float.floatToIntBits(height)
: 0);
hash = result;
hasCalculatedHash = true;
}
return hash;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("pos{");
sb.append(" x=").append(x);
sb.append(", y=").append(y);
sb.append(", w=").append(width);
sb.append(", h=").append(height);
sb.append(", endX=").append(x + width);
sb.append(", endY=").append(y + height);
sb.append('}');
return sb.toString();
}
// -------------------------- PUBLIC METHODS --------------------------
/**
* Compute the area of this rectangle.
*
* @return The area of this rectangle
*/
public float area() {
return width * height;
}
/**
* Determines the centre point of the rectangle
*/
@NotNull
public FloatPoint centre() {
return new FloatPoint((x + (width / 2.0F)), (y + (height / 2.0F)));
}
/**
* Determine whether this rectangle is contained by the passed rectangle
*
* @param r The rectangle that might contain this rectangle
* @return true if the passed rectangle contains this rectangle, false if it does not
*/
public boolean containedBy(@NotNull Rectangle r) {
return (r.endX >= endX) && (r.x <= x) && (r.endY >= endY) && (r.y <= y);
}
/**
* Determine whether this rectangle contains the passed rectangle
*
* @param r The rectangle that might be contained by this rectangle
* @return true if this rectangle contains the passed rectangle, false if it does not
*/
public boolean contains(@NotNull Rectangle r) {
return (endX >= r.endX) && (x <= r.x) && (endY >= r.endY) && (y <= r.y);
}
/**
* Return the distance between this rectangle and the passed point. If the rectangle contains
* the point, the distance is zero.
*
* @param p Point to find the distance to
* @return distance beween this rectangle and the passed point.
*/
public float distance(@NotNull final FloatPoint p) {
float temp = x - p.x;
if (temp < 0.0F) {
temp = p.x - endX;
}
float distanceSquared = Math.max(0.0F, temp * temp);
float temp2 = (y - p.y);
if (temp2 < 0.0F) {
temp2 = p.y - endY;
}
if (temp2 > 0.0F) {
distanceSquared += (temp2 * temp2);
}
return MathUtils.sqrt(distanceSquared);
}
/**
* Distance to another rectangle
*
* @param that another rectangle
* @return the distance
*/
public float distance(@NotNull Rectangle that) {
if (intersectsWith(that)) {
return 0.0f;
}
float distance = 0.0f;
if (x > that.endX) {
distance += (x - that.endX) * (x - that.endX);
} else if (that.x > endX) {
distance += (that.x - endX) * (that.x - endX);
}
if (y > that.endY) {
distance += (y - that.endY) * (y - that.endY);
} else if (that.y > endY) {
distance += (that.y - endY) * (that.y - endY);
}
return MathUtils.sqrt(distance);
}
@NotNull
public Rectangle getAdjustedBy(float adjust) {
return new Rectangle(Math.max(0.1f, x - adjust), Math.max(0.1f, y - adjust),
Math.max(0.1f, width + 2 * adjust), Math.max(0.1f, height + 2 * adjust));
}
public float getMiddleX() {
return x + width / 2.0f;
}
public float getMiddleY() {
return y + height / 2.0f;
}
public float getVerticalDistanceTo(@NotNull Rectangle that) {
if (that.endY < y) {
return y - that.endY;
}
if (that.y > endY) {
return endY - that.y;
}
return 0.0f;
}
@NotNull
public Rectangle intersection(@NotNull Rectangle that) {
float maxX = Math.max(endX, that.endX);
float maxY = Math.max(endY, that.endY);
float minX = Math.min(x, that.x);
float minY = Math.min(y, that.y);
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
public boolean intersectsAdmittingOverlap(@NotNull Rectangle that, final float overlap) {
if (isEmpty()) {
return false;
}
if (that.endX < x + overlap) {
return false;
}
if (that.x > endX - overlap) {
return false;
}
if (that.y > endY - overlap) {
return false;
}
return that.endY > y + overlap;
}
public boolean intersectsWith(@NotNull Rectangle that) {
if (isEmpty()) {
return false;
}
if (that.endX < x) {
return false;
}
if (that.x > endX) {
return false;
}
if (that.y > endY) {
return false;
}
return that.endY > y;
}
/**
* Determines if this rectangle has an area of 0
*/
public final boolean isEmpty() {
return (width <= 0.0F) || (height <= 0.0F);
}
/**
* I stole this code from java.awt.geom.Rectange2D, im sure the details make sense :)
*/
@NotNull
public Rectangle union(@NotNull Rectangle that) {
float x1 = Math.min(x, that.x);
float y1 = Math.min(y, that.y);
float x2 = Math.max(endX, that.endX);
float y2 = Math.max(endY, that.endY);
if (x2 < x1) {
float t = x1;
x1 = x2;
x2 = t;
}
if (y2 < y1) {
float t = y1;
y1 = y2;
y2 = t;
}
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
}