package org.erikaredmark.monkeyshines.bounds;
import org.erikaredmark.monkeyshines.ImmutableRectangle;
/**
*
* Note: Had this been Scala, this would be a trait.
* <p/>
* This 'trait' represents that an object contains bounds. Subclasses must set the protected members for the location and
* size to some implementation of {@code IPoint2D}. From there, the bounds checking methods in this trait will be able to
* allow clients to do collision detection
* <p/>
* Implementors MUST initialise all instance variables of this trait.
* <p/>
* It is <strong> suggested </strong> that implementors override {@code getLocation() } and {@code getSize() } if the
* implementation of the IPoint2D for either member is mutable. Default implementation returns a reference to the object,
* which may not be desirable if it is mutable.
*
* @author Erika Redmark
*
*/
public abstract class Boundable {
/** The location of the boundable. May be any implementation of a point. */
protected IPoint2D location;
/** The size of the boundable. May be any implementation of a point. */
protected IPoint2D size;
/**
*
* Returns the location (upper-left corner) of this boundable. Default implementation returns a direct reference to
* the object.
*
* @return
*
*/
public IPoint2D getLocation() { return location; }
/**
*
* Returns the size of this boundable. x() is width and y() is height Default implementation returns a direct reference to
* the object.
*
* @return
*
*/
public IPoint2D getSize() { return size; }
/**
*
* Tests if a given points lies within this boundable. Lying within is inclusive of the bounds, any point within
* {@code [x, x + width], [y, y + width] } is considered within the region.
*
* @param point
*
* @return {@code true} if the given point lies within the rectangle given be this object, {@code false} if otherwise
*
*/
public boolean inBounds(IPoint2D point) {
return (inBoundsX(point) && inBoundsY(point) );
}
/**
* Does bounds checking only for X co-ordinate
*
* @param point
* @return {@code true} if the point's x value lies within the x bounds of this region, {@code false} if otherwise
*/
public boolean inBoundsX(IPoint2D point) {
// Point right of left bound, and left of right bound?
return (point.x() >= this.location.x() &&
point.x() <= ( this.location.x() + this.size.x() ) );
}
/**
* Does bounds checking only for Y co-ordinate
*
* @param point
* @return {@code true} if the point's y value lies within the x bounds of this region, {@code false} if otherwise
*/
public boolean inBoundsY(IPoint2D point) {
// Point down from upper bound, and above lower bound?
return (point.y() >= this.location.y() &&
point.y() <= ( this.location.y() + this.size.y() ) );
}
/**
*
* Determines if the given regions intersect. Intersections are symmetric: If region A intersects with region B, then
* region B intersects with region A
* <p/>
* If two regions are intersecting, a boundable representing the intersection region is returned. Otherwise, {@code null}
* is returned to signify no intersection
*
* @param that
* the other boundable to check for intersection
*
* @return
* a boundable object representing the intersection region, or {@code null} if the two boundables
* do not intersect.
*
*/
public Boundable intersect(Boundable that) {
// Prove that the don't intersect
// If we orders the X values of both the left and right points of this and that, if this appears two times
// in succession, then that, or vice-versa, they are NOT intersecting.
int thisX1 = location.x();
int thisX2 = location.x() + size.x();
int thatX1 = that.location.x();
int thatX2 = that.location.x() + that.size.x();
// Check for the only two cases of non-intersecting X bounds:
// thisX1 < thisX2 < thatX1 < thatX2 turns into thisX2 < thatX1
// thatX1 < thatX2 < thisX1 < thisX2 turns into thatX2 < thisX1
if (thisX2 < thatX1 || thatX2 < thisX1) return null;
// Same thing for y
int thisY1 = location.y();
int thisY2 = location.y() + size.y();
int thatY1 = that.location.y();
int thatY2 = that.location.y() + that.size.y();
if (thisY2 < thatY1 || thatY2 < thisY1) return null;
// We have an intersection. Calculate offsets.
// If my starting position is less than theirs, theirs must be the intersection
// otherwise it is mine. This is like drawing a line on the axis where both points
// are and finding the intersection point
int intersectX = thisX1 < thatX1
? thatX1
: thisX1;
int intersectY = thisY1 < thatY1
? thatY1
: thisY1;
// Now, find sizes. Easiest way is to simply use the same process as above, but with
// the other corners of the rectangle. however, logic is reversed. Whoever is smaller wins.
int intersectBottomX = thisX2 < thatX2
? thisX2
: thatX2;
int intersectBottomY = thisY2 < thatY2
? thisY2
: thatY2;
// Make rectangle to return. Constructor expects width and heights, not positions.
return ImmutableRectangle.of(intersectX, intersectY, intersectBottomX - intersectX, intersectBottomY - intersectY);
}
/**
*
* Boundables are equal to each other if they have the same location and size, regardless
* of underlying type.
*
*/
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Boundable) ) return false;
Boundable other = (Boundable) o;
return this.location.x() == other.location.x()
&& this.location.y() == other.location.y()
&& this.size.x() == other.size.x()
&& this.size.y() == other.size.y();
}
/**
*
* Boundables with equal location and size have equal hashcode
*
*/
@Override public int hashCode() {
int result = 17;
result += result * 31 + location.x();
result += result * 31 + location.y();
result += result * 31 + size.x();
result += result * 31 + size.y();
return result;
}
}