diff --git a/src/main/java/pythagoras/f/AbstractDimension.java b/src/main/java/pythagoras/f/AbstractDimension.java new file mode 100644 index 0000000..06e81cd --- /dev/null +++ b/src/main/java/pythagoras/f/AbstractDimension.java @@ -0,0 +1,38 @@ +// +// $Id$ + +package pythagoras.f; + +/** + * Provides most of the implementation of {@link IDimension}, obtaining only width and height from + * the derived class. + */ +public abstract class AbstractDimension implements IDimension +{ + @Override // from interface IDimension + public Dimension clone () { + return new Dimension(this); + } + + @Override + public int hashCode () { + return Float.floatToIntBits(getWidth()) ^ Float.floatToIntBits(getHeight()); + } + + @Override + public boolean equals (Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof AbstractDimension) { + AbstractDimension d = (AbstractDimension)obj; + return (d.getWidth() == getWidth() && d.getHeight() == getHeight()); + } + return false; + } + + @Override + public String toString () { + return Geometry.dimenToString(getWidth(), getHeight()); + } +} diff --git a/src/main/java/pythagoras/f/AbstractLine.java b/src/main/java/pythagoras/f/AbstractLine.java new file mode 100644 index 0000000..89128e5 --- /dev/null +++ b/src/main/java/pythagoras/f/AbstractLine.java @@ -0,0 +1,130 @@ +// +// $Id$ + +package pythagoras.f; + +/** + * Provides most of the implementation of {@link ILine}, obtaining only the start and end points + * from the derived class. + */ +public abstract class AbstractLine implements ILine +{ + @Override // from interface ILine + public IPoint p1 () { + return new AbstractPoint() { + @Override public float getX () { + return getX1(); + } + @Override public float getY () { + return getY1(); + } + }; + } + + @Override // from interface ILine + public Point getP1 () { + return new Point(getX1(), getY1()); + } + + @Override // from interface ILine + public IPoint p2 () { + return new AbstractPoint() { + @Override public float getX () { + return getX2(); + } + @Override public float getY () { + return getY2(); + } + }; + } + + @Override // from interface ILine + public Point getP2 () { + return new Point(getX2(), getY2()); + } + + @Override // from interface ILine + public Line clone () { + return new Line(getX1(), getY1(), getX2(), getY2()); + } + + @Override // from interface IShape + public boolean isEmpty () { + return false; + } + + @Override // from interface IShape + public boolean contains (float x, float y) { + return false; + } + + @Override // from interface IShape + public boolean contains (IPoint point) { + return false; + } + + @Override // from interface IShape + public boolean contains (float x, float y, float w, float h) { + return false; + } + + @Override // from interface IShape + public boolean contains (IRectangle r) { + return false; + } + + @Override // from interface IShape + public boolean intersects (float rx, float ry, float rw, float rh) { + return Geometry.lineIntersectsRect(getX1(), getY1(), getX2(), getY2(), rx, ry, rw, rh); + } + + @Override // from interface IShape + public boolean intersects (IRectangle r) { + return r.intersectsLine(this); + } + + @Override // from interface IShape + public IRectangle bounds () { + return new AbstractRectangle() { + @Override public float getX () { + return Math.min(getX1(), getX2()); + } + @Override public float getY () { + return Math.min(getY1(), getY2()); + } + @Override public float getWidth () { + float x1 = getX1(), x2 = getX2(); + return (x1 < x2) ? (x2 - x1) : (x1 - x2); + } + @Override public float getHeight () { + float y1 = getY1(), y2 = getY2(); + return (y1 < y2) ? (y2 - y1) : (y1 - y2); + } + // this isn't visible in the type, so won't be called by non-combatants + @Override public void setFrame (float x, float y, float w, float h) { + throw new UnsupportedOperationException(); + } + }; + } + + @Override // from interface IShape + public Rectangle getBounds () { + float x1 = getX1(), x2 = getX2(), y1 = getY1(), y2 = getY2(); + float rx, ry, rw, rh; + if (x1 < x2) { + rx = x1; + rw = x2 - x1; + } else { + rx = x2; + rw = x1 - x2; + } + if (y1 < y2) { + ry = y1; + rh = y2 - y1; + } else { + ry = y2; + rh = y1 - y2; + } + return new Rectangle(rx, ry, rw, rh); + } +} diff --git a/src/main/java/pythagoras/f/AbstractPoint.java b/src/main/java/pythagoras/f/AbstractPoint.java new file mode 100644 index 0000000..347ff58 --- /dev/null +++ b/src/main/java/pythagoras/f/AbstractPoint.java @@ -0,0 +1,58 @@ +// +// $Id$ + +package pythagoras.f; + +/** + * Provides most of the implementation of {@link IPoint}, obtaining only the location from the + * derived class. + */ +public abstract class AbstractPoint implements IPoint +{ + @Override // from interface IPoint + public float distanceSq (float px, float py) { + return Geometry.distanceSq(getX(), getY(), px, py); + } + + @Override // from interface IPoint + public float distanceSq (IPoint p) { + return Geometry.distanceSq(getX(), getY(), p.getX(), p.getY()); + } + + @Override // from interface IPoint + public float distance (float px, float py) { + return Geometry.distance(getX(), getY(), px, py); + } + + @Override // from interface IPoint + public float distance (IPoint p) { + return Geometry.distance(getX(), getY(), p.getX(), p.getY()); + } + + @Override // from interface IPoint + public Point clone () { + return new Point(this); + } + + @Override + public boolean equals (Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof AbstractPoint) { + AbstractPoint p = (AbstractPoint)obj; + return getX() == p.getX() && getY() == p.getY(); + } + return false; + } + + @Override + public int hashCode () { + return Float.floatToIntBits(getX()) ^ Float.floatToIntBits(getY()); + } + + @Override + public String toString () { + return Geometry.pointToString(getX(), getY()); + } +} diff --git a/src/main/java/pythagoras/f/AbstractRectangle.java b/src/main/java/pythagoras/f/AbstractRectangle.java new file mode 100644 index 0000000..0cc5e29 --- /dev/null +++ b/src/main/java/pythagoras/f/AbstractRectangle.java @@ -0,0 +1,172 @@ +// +// $Id$ + +package pythagoras.f; + +/** + * Provides most of the implementation of {@link IRectangle}, obtaining only the location and + * dimensions from the derived class. + */ +public abstract class AbstractRectangle extends RectangularShape implements IRectangle +{ + @Override // from interface IRectangle + public IPoint location () { + return new AbstractPoint() { + @Override public float getX () { + return AbstractRectangle.this.getX(); + } + @Override public float getY () { + return AbstractRectangle.this.getY(); + } + }; + } + + @Override // from interface IRectangle + public Point getLocation () { + return new Point(getX(), getY()); + } + + @Override // from interface IRectangle + public IDimension size () { + return new AbstractDimension() { + @Override public float getWidth () { + return AbstractRectangle.this.getWidth(); + } + @Override public float getHeight () { + return AbstractRectangle.this.getHeight(); + } + }; + } + + @Override // from interface IRectangle + public Dimension getSize () { + return new Dimension(getWidth(), getHeight()); + } + + @Override // from interface IRectangle + public Rectangle intersection (float rx, float ry, float rw, float rh) { + float x1 = Math.max(getX(), rx); + float y1 = Math.max(getY(), ry); + float x2 = Math.min(getMaxX(), rx + rw); + float y2 = Math.min(getMaxY(), ry + rh); + return new Rectangle(x1, y1, x2 - x1, y2 - y1); + } + + @Override // from interface IRectangle + public Rectangle intersection (IRectangle r) { + return intersection(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + @Override // from interface IRectangle + public Rectangle union (IRectangle r) { + Rectangle rect = new Rectangle(this); + rect.add(r); + return rect; + } + + @Override // from interface IRectangle + public boolean intersectsLine (float x1, float y1, float x2, float y2) { + return Geometry.lineIntersectsRect(x1, y1, x2, y2, getX(), getY(), getWidth(), getHeight()); + } + + @Override // from interface IRectangle + public boolean intersectsLine (ILine l) { + return intersectsLine(l.getX1(), l.getY1(), l.getX2(), l.getY2()); + } + + @Override // from interface IRectangle + public int outcode (float px, float py) { + int code = 0; + + if (getWidth() <= 0) { + code |= OUT_LEFT | OUT_RIGHT; + } else if (px < getX()) { + code |= OUT_LEFT; + } else if (px > getMaxX()) { + code |= OUT_RIGHT; + } + + if (getHeight() <= 0) { + code |= OUT_TOP | OUT_BOTTOM; + } else if (py < getY()) { + code |= OUT_TOP; + } else if (py > getMaxY()) { + code |= OUT_BOTTOM; + } + + return code; + } + + @Override // from interface IRectangle + public int outcode (IPoint p) { + return outcode(p.getX(), p.getY()); + } + + @Override // from interface IRectangle + public Rectangle clone () { + return new Rectangle(this); + } + + @Override // from interface IShape + public boolean contains (float px, float py) { + if (isEmpty()) return false; + + float x = getX(), y = getY(); + if (px < x || py < y) return false; + + px -= x; + py -= y; + return px < getWidth() && py < getHeight(); + } + + @Override // from interface IShape + public boolean contains (float rx, float ry, float rw, float rh) { + if (isEmpty()) return false; + + double x1 = getX(), y1 = getY(), x2 = x1 + getWidth(), y2 = y1 + getHeight(); + return (x1 <= rx) && (rx + rw <= x2) && (y1 <= ry) && (ry + rh <= y2); + } + + @Override // from interface IShape + public boolean intersects (float rx, float ry, float rw, float rh) { + if (isEmpty()) return false; + + double x1 = getX(), y1 = getY(), x2 = x1 + getWidth(), y2 = y1 + getHeight(); + return (rx + rw > x1) && (rx < x2) && (ry + rh > y1) && (ry < y2); + } + + @Override // from RectangularShape + public IRectangle frame () { + return this; + } + + @Override // from RectangularShape + public IRectangle bounds () { + return this; + } + + @Override // from Object + public boolean equals (Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof AbstractRectangle) { + AbstractRectangle r = (AbstractRectangle)obj; + return r.getX() == getX() && r.getY() == getY() && + r.getWidth() == getWidth() && r.getHeight() == getHeight(); + } + return false; + } + + @Override // from Object + public int hashCode () { + return Float.floatToIntBits(getX()) ^ Float.floatToIntBits(getY()) ^ + Float.floatToIntBits(getWidth()) ^ Float.floatToIntBits(getHeight()); + } + + @Override // from Object + public String toString () { + return Geometry.dimenToString(getWidth(), getHeight()) + + Geometry.pointToString(getX(), getY()); + } +} diff --git a/src/main/java/pythagoras/f/Dimension.java b/src/main/java/pythagoras/f/Dimension.java index fd8c618..8df3803 100644 --- a/src/main/java/pythagoras/f/Dimension.java +++ b/src/main/java/pythagoras/f/Dimension.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** * Represents a magnitude in two dimensions. */ -public class Dimension implements IDimension, Serializable +public class Dimension extends AbstractDimension implements Serializable { /** The magnitude in the x-dimension. */ public float width; @@ -62,35 +62,4 @@ public class Dimension implements IDimension, Serializable public float getHeight () { return height; } - - @Override // from interface IDimension - public Dimension clone () { - try { - return (Dimension)super.clone(); - } catch (CloneNotSupportedException cnse) { - throw new AssertionError(cnse); - } - } - - @Override - public int hashCode () { - return Float.floatToIntBits(width) ^ Float.floatToIntBits(height); - } - - @Override - public boolean equals (Object obj) { - if (obj == this) { - return true; - } - if (obj instanceof Dimension) { - Dimension d = (Dimension)obj; - return (d.width == width && d.height == height); - } - return false; - } - - @Override - public String toString () { - return Geometry.dimenToString(width, height); - } } diff --git a/src/main/java/pythagoras/f/Geometry.java b/src/main/java/pythagoras/f/Geometry.java index d4fc77c..8e054cf 100644 --- a/src/main/java/pythagoras/f/Geometry.java +++ b/src/main/java/pythagoras/f/Geometry.java @@ -25,6 +25,61 @@ public class Geometry return (float)Math.sqrt(distanceSq(x1, y1, x2, y2)); } + /** + * Returns true if the specified two line segments intersect. + */ + public static boolean linesIntersect (float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4) { + // A = (x2-x1, y2-y1) + // B = (x3-x1, y3-y1) + // C = (x4-x1, y4-y1) + // D = (x4-x3, y4-y3) = C-B + // E = (x1-x3, y1-y3) = -B + // F = (x2-x3, y2-y3) = A-B + // + // Result is ((AxB) * (AxC) <= 0) and ((DxE) * (DxF) <= 0) + // + // DxE = (C-B)x(-B) = BxB-CxB = BxC + // DxF = (C-B)x(A-B) = CxA-CxB-BxA+BxB = AxB+BxC-AxC + x2 -= x1; // A + y2 -= y1; + x3 -= x1; // B + y3 -= y1; + x4 -= x1; // C + y4 -= y1; + + float AvB = x2 * y3 - x3 * y2; + float AvC = x2 * y4 - x4 * y2; + + // online + if (AvB == 0 && AvC == 0) { + if (x2 != 0) { + return (x4 * x3 <= 0) || + ((x3 * x2 >= 0) && (x2 > 0 ? x3 <= x2 || x4 <= x2 : x3 >= x2 || x4 >= x2)); + } + if (y2 != 0) { + return (y4 * y3 <= 0) || + ((y3 * y2 >= 0) && (y2 > 0 ? y3 <= y2 || y4 <= y2 : y3 >= y2 || y4 >= y2)); + } + return false; + } + + float BvC = x3 * y4 - x4 * y3; + return (AvB * AvC <= 0) && (BvC * (AvB + BvC - AvC) <= 0); + } + + /** + * Returns true if the specified line segment intersects the specified rectangle. + */ + public static boolean lineIntersectsRect (float x1, float y1, float x2, float y2, + float rx, float ry, float rw, float rh) { + float rr = rx + rw, rb = ry + rh; + return (rx <= x1 && x1 <= rr && ry <= y1 && y1 <= rb) + || (rx <= x2 && x2 <= rr && ry <= y2 && y2 <= rb) + || linesIntersect(rx, ry, rr, rb, x1, y1, x2, y2) + || linesIntersect(rr, ry, rx, rb, x1, y1, x2, y2); + } + /** * Returns a string describing the supplied point, of the form +x+y, * +x-y, -x-y, etc. diff --git a/src/main/java/pythagoras/f/IDimension.java b/src/main/java/pythagoras/f/IDimension.java index d546383..40b7411 100644 --- a/src/main/java/pythagoras/f/IDimension.java +++ b/src/main/java/pythagoras/f/IDimension.java @@ -20,7 +20,7 @@ public interface IDimension extends Cloneable float getHeight (); /** - * Clones this dimension. + * Returns a mutable copy of this dimension. */ Dimension clone (); } diff --git a/src/main/java/pythagoras/f/ILine.java b/src/main/java/pythagoras/f/ILine.java new file mode 100644 index 0000000..f5a772c --- /dev/null +++ b/src/main/java/pythagoras/f/ILine.java @@ -0,0 +1,59 @@ +// +// $Id$ + +package pythagoras.f; + +/** + * Provides read-only access to a {@link Line}. + */ +public interface ILine extends IShape, Cloneable +{ + /** + * Returns the x-coordinate of the start of this line. + */ + float getX1 (); + + /** + * Returns the y-coordinate of the start of this line. + */ + float getY1 (); + + /** + * Returns the x-coordinate of the end of this line. + */ + float getX2 (); + + /** + * Returns the y-coordinate of the end of this line. + */ + float getY2 (); + + /** + * Returns a view of the starting point of this line. Subsequent changes to the starting point + * will be visible in the returned point. + */ + IPoint p1 (); + + /** + * Returns a copy of the starting point of this line. Subsequent changes to the starting point + * will not be visible in the returned point. + */ + Point getP1 (); + + /** + * Returns a view of the ending point of this line. Subsequent changes to the ending point will + * be visible in the returned point. + */ + IPoint p2 (); + + /** + * Returns a copy of the ending point of this line. Subsequent changes to the ending point will + * not be visible in the returned point. + */ + Point getP2 (); + + /** + * Returns a mutable copy of this line. + */ + Line clone (); +} diff --git a/src/main/java/pythagoras/f/IPoint.java b/src/main/java/pythagoras/f/IPoint.java index 7a4359f..365b12a 100644 --- a/src/main/java/pythagoras/f/IPoint.java +++ b/src/main/java/pythagoras/f/IPoint.java @@ -40,7 +40,7 @@ public interface IPoint extends Cloneable float distance (IPoint p); /** - * Clones this point. + * Returns a mutable copy of this point. */ Point clone (); } diff --git a/src/main/java/pythagoras/f/IRectangle.java b/src/main/java/pythagoras/f/IRectangle.java index 5e8eaf2..990a4ac 100644 --- a/src/main/java/pythagoras/f/IRectangle.java +++ b/src/main/java/pythagoras/f/IRectangle.java @@ -7,7 +7,7 @@ package pythagoras.f; /** * Provides read-only access to a {@link Rectangle}. */ -public interface IRectangle extends Shape, Cloneable +public interface IRectangle extends IRectangularShape, Cloneable { /** The bitmask that indicates that a point lies to the left of this rectangle. * See {@link #outcode}. */ @@ -24,41 +24,26 @@ public interface IRectangle extends Shape, Cloneable int OUT_BOTTOM = 8; /** - * Returns the x-coordinate of the rectangle's upper left corner. + * Returns a view of this rectangle's upper-left corner. Subsequent changes to this rectangle's + * location will be reflected in the returned point. */ - float getX (); + IPoint location (); /** - * Returns the y-coordinate of the rectangle's upper left corner. - */ - float getY (); - - /** - * Returns the width of the rectangle. - */ - float getWidth (); - - /** - * Returns the height of the rectangle. - */ - float getHeight (); - - /** - * Returns true if this rectangle does not have > 0 width and height. - */ - boolean isEmpty (); - - /** - * Returns this rectangle's upper-left corner. Note that this constructs a new instance, - * because a {@link Rectangle} is not an {@link IPoint} and cannot efficiently masquerade as - * one. + * Returns a copy of this rectangle's upper-left corner. Subsequent changes to this rectangle's + * location will not be reflected in the returned point. */ Point getLocation (); /** - * Returns the size of this rectangle as a newly constructed dimension. Note that this - * constructs a new instance, because a {@link Rectangle} is not an {@link IDimension} and - * cannot efficiently masquerade as one. + * Returns a view of this rectangle's size. Subsequent changes to this rectangle's size will be + * reflected in the returned dimension. + */ + IDimension size (); + + /** + * Returns a copy of this rectangle's size. Subsequent changes to this rectangle's size will + * not be reflected in the returned dimension. */ Dimension getSize (); @@ -80,11 +65,21 @@ public interface IRectangle extends Shape, Cloneable */ Rectangle union (IRectangle r); + /** + * Returns true if the specified line segment intersects this rectangle. + */ + boolean intersectsLine (float x1, float y1, float x2, float y2); + + /** + * Returns true if the supplied line segment intersects this rectangle. + */ + boolean intersectsLine (ILine l); + /** * Returns a set of flags indicating where the specified point lies in relation to the bounds * of this rectangle. See {@link #OUT_LEFT}, etc. */ - int outcode (double px, double py); + int outcode (float px, float py); /** * Returns a set of flags indicating where the supplied point lies in relation to the bounds of @@ -93,7 +88,7 @@ public interface IRectangle extends Shape, Cloneable int outcode (IPoint point); /** - * Clones this rectangle. + * Returns a mutable copy of this rectangle. */ Rectangle clone (); } diff --git a/src/main/java/pythagoras/f/IRectangularShape.java b/src/main/java/pythagoras/f/IRectangularShape.java new file mode 100644 index 0000000..7844878 --- /dev/null +++ b/src/main/java/pythagoras/f/IRectangularShape.java @@ -0,0 +1,75 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.f; + +/** + * An interface implemented by {@link Shape} classes whose geometry is defined by a rectangular + * frame. The framing rectangle defines the geometry, but may in some cases differ from + * the bounding rectangle of the shape. + */ +public interface IRectangularShape extends IShape +{ + /** + * Returns the x-coordinate of the upper-left corner of the framing rectangle. + */ + float getX (); + + /** + * Returns the y-coordinate of the upper-left corner of the framing rectangle. + */ + float getY (); + + /** + * Returns the width of the framing rectangle. + */ + float getWidth (); + + /** + * Returns the height of the framing rectangle. + */ + float getHeight (); + + /** + * Returns the minimum x-coordinate of the framing rectangle. + */ + float getMinX (); + + /** + * Returns the minimum y-coordinate of the framing rectangle. + */ + float getMinY (); + + /** + * Returns the maximum x-coordinate of the framing rectangle. + */ + float getMaxX (); + + /** + * Returns the maximum y-coordinate of the framing rectangle. + */ + float getMaxY (); + + /** + * Returns the x-coordinate of the center of the framing rectangle. + */ + float getCenterX (); + + /** + * Returns the y-coordinate of the center of the framing rectangle. + */ + float getCenterY (); + + /** + * Returns a view of this shape's framing rectangle. Subsequent changes to the shape will be + * reflected in the returned rectangle. + */ + IRectangle frame (); + + /** + * Returns a copy of this shape's framing rectangle. Subsequent changes to the shape will be + * reflected in the returned rectangle. + */ + Rectangle getFrame (); +} diff --git a/src/main/java/pythagoras/f/Shape.java b/src/main/java/pythagoras/f/IShape.java similarity index 77% rename from src/main/java/pythagoras/f/Shape.java rename to src/main/java/pythagoras/f/IShape.java index 426d5dc..0f9dd9f 100644 --- a/src/main/java/pythagoras/f/Shape.java +++ b/src/main/java/pythagoras/f/IShape.java @@ -7,8 +7,13 @@ package pythagoras.f; /** * An interface provided by all shapes. */ -public interface Shape +public interface IShape { + /** + * Returns true if this shape encloses no area. + */ + boolean isEmpty (); + /** * Returns true if this shape contains the specified point. */ @@ -40,9 +45,16 @@ public interface Shape boolean intersects (IRectangle r); /** - * Returns the bounding rectangle for this shape. + * Returns a view of the bounding rectangle for this shape. Subsequent changes to the bounding + * rectangle will be reflected in the returned rectangle. */ - IRectangle getBounds (); + IRectangle bounds(); + + /** + * Returns a copy of the bounding rectangle for this shape. Subsequent changes to the bounding + * rectangle will not be reflected in the returned rectangle. + */ + Rectangle getBounds (); // /** // * Returns an iterator over the path described by this shape. diff --git a/src/main/java/pythagoras/f/Line.java b/src/main/java/pythagoras/f/Line.java new file mode 100644 index 0000000..fd6e553 --- /dev/null +++ b/src/main/java/pythagoras/f/Line.java @@ -0,0 +1,74 @@ +// +// $Id$ + +package pythagoras.f; + +import java.io.Serializable; + +/** + * Represents a line segment. + */ +public class Line extends AbstractLine implements Serializable +{ + public float x1; + public float y1; + public float x2; + public float y2; + + /** + * Creates a line from (0,0) to (0,0). + */ + public Line () { + } + + /** + * Creates a line from (x1,y1), to (x2,y2). + */ + public Line (float x1, float y1, float x2, float y2) { + setLine(x1, y1, x2, y2); + } + + /** + * Creates a line from p1 to p2. + */ + public Line (IPoint p1, IPoint p2) { + setLine(p1, p2); + } + + /** + * Sets the start and end point of this line to the specified values. + */ + public void setLine (float x1, float y1, float x2, float y2) { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + /** + * Sets the start and end of this line to the specified points. + */ + public void setLine (IPoint p1, IPoint p2) { + setLine(p1.getX(), p1.getY(), p2.getY(), p2.getY()); + } + + @Override // from interface ILine + public float getX1 () { + return x1; + } + + @Override // from interface ILine + public float getY1 () { + return y1; + } + + @Override // from interface ILine + public float getX2 () { + return x2; + } + + @Override // from interface ILine + public float getY2 () { + return y2; + } +} diff --git a/src/main/java/pythagoras/f/Point.java b/src/main/java/pythagoras/f/Point.java index 59e9c65..bfe0ddd 100644 --- a/src/main/java/pythagoras/f/Point.java +++ b/src/main/java/pythagoras/f/Point.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** * Represents a point on a plane. */ -public class Point implements IPoint, Serializable +public class Point extends AbstractPoint implements Serializable { /** The x-coordinate of the point. */ public float x; @@ -77,55 +77,4 @@ public class Point implements IPoint, Serializable public float getY () { return y; } - - @Override // from interface IPoint - public float distanceSq (float px, float py) { - return Geometry.distanceSq(x, y, px, py); - } - - @Override // from interface IPoint - public float distanceSq (IPoint p) { - return Geometry.distanceSq(x, y, p.getX(), p.getY()); - } - - @Override // from interface IPoint - public float distance (float px, float py) { - return Geometry.distance(x, y, px, py); - } - - @Override // from interface IPoint - public float distance (IPoint p) { - return Geometry.distance(x, y, p.getX(), p.getY()); - } - - @Override // from interface IPoint - public Point clone () { - try { - return (Point)super.clone(); - } catch (CloneNotSupportedException cnse) { - throw new AssertionError(cnse); - } - } - - @Override - public boolean equals (Object obj) { - if (obj == this) { - return true; - } - if (obj instanceof Point) { - Point p = (Point)obj; - return x == p.x && y == p.y; - } - return false; - } - - @Override - public int hashCode () { - return Float.floatToIntBits(x) ^ Float.floatToIntBits(y); - } - - @Override - public String toString () { - return Geometry.pointToString(x, y); - } } diff --git a/src/main/java/pythagoras/f/Rectangle.java b/src/main/java/pythagoras/f/Rectangle.java index ef4d965..a55c15f 100644 --- a/src/main/java/pythagoras/f/Rectangle.java +++ b/src/main/java/pythagoras/f/Rectangle.java @@ -9,7 +9,7 @@ import java.io.Serializable; /** * Represents an area in two dimensions. */ -public class Rectangle implements IRectangle, Serializable +public class Rectangle extends AbstractRectangle implements Serializable { /** The x-coordinate of the rectangle's upper left corner. */ public float x; @@ -33,23 +33,23 @@ public class Rectangle implements IRectangle, Serializable /** * Constructs a rectangle with the supplied upper-left corner and dimensions (0,0). */ - public Rectangle (Point p) { - setBounds(p.x, p.y, 0, 0); + public Rectangle (IPoint p) { + setBounds(p.getX(), p.getY(), 0, 0); } /** * Constructs a rectangle with upper-left corner at (0,) and the supplied dimensions. */ - public Rectangle (Dimension d) { - setBounds(0, 0, d.width, d.height); + public Rectangle (IDimension d) { + setBounds(0, 0, d.getWidth(), d.getHeight()); } /** * Constructs a rectangle with upper-left corner at the supplied point and with the supplied * dimensions. */ - public Rectangle (Point p, Dimension d) { - setBounds(p.x, p.y, d.width, d.height); + public Rectangle (IPoint p, IDimension d) { + setBounds(p.getX(), p.getY(), d.getWidth(), d.getHeight()); } /** @@ -62,23 +62,8 @@ public class Rectangle implements IRectangle, Serializable /** * Constructs a rectangle with bounds equal to the supplied rectangle. */ - public Rectangle (Rectangle r) { - setBounds(r.x, r.y, r.width, r.height); - } - - /** - * Sets the size of this rectangle to the specified dimensions. - */ - public void setSize (float width, float height) { - this.width = width; - this.height = height; - } - - /** - * Sets the size of this rectangle to the supplied dimensions. - */ - public void setSize (Dimension d) { - setSize(d.width, d.height); + public Rectangle (IRectangle r) { + setBounds(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } /** @@ -96,6 +81,21 @@ public class Rectangle implements IRectangle, Serializable setLocation(p.getX(), p.getY()); } + /** + * Sets the size of this rectangle to the specified dimensions. + */ + public void setSize (float width, float height) { + this.width = width; + this.height = height; + } + + /** + * Sets the size of this rectangle to the supplied dimensions. + */ + public void setSize (Dimension d) { + setSize(d.width, d.height); + } + /** * Sets the bounds of this rectangle to the specified bounds. */ @@ -162,166 +162,28 @@ public class Rectangle implements IRectangle, Serializable setBounds(x1, y1, x2 - x1, y2 - y1); } - @Override // from interface IRectangle - public float getX () - { + @Override // from interface IRectangularShape + public float getX () { return x; } - @Override // from interface IRectangle - public float getY () - { + @Override // from interface IRectangularShape + public float getY () { return y; } - @Override // from interface IRectangle - public float getWidth () - { + @Override // from interface IRectangularShape + public float getWidth () { return width; } - @Override // from interface IRectangle - public float getHeight () - { + @Override // from interface IRectangularShape + public float getHeight () { return height; } - @Override // from interface IRectangle - public boolean isEmpty () { - return width <= 0 || height <= 0; - } - - @Override // from interface IRectangle - public Point getLocation () { - return new Point(x, y); - } - - @Override // from interface IRectangle - public Dimension getSize () { - return new Dimension(width, height); - } - - @Override // from interface IRectangle - public Rectangle intersection (float rx, float ry, float rw, float rh) { - float x1 = Math.max(x, rx); - float y1 = Math.max(y, ry); - float x2 = Math.min(x + width, rx + rw); - float y2 = Math.min(y + height, ry + rh); - return new Rectangle(x1, y1, x2 - x1, y2 - y1); - } - - @Override // from interface IRectangle - public Rectangle intersection (IRectangle r) { - return intersection(r.getX(), r.getY(), r.getWidth(), r.getHeight()); - } - - @Override // from interface IRectangle - public Rectangle union (IRectangle r) { - Rectangle rect = new Rectangle(this); - rect.add(r); - return rect; - } - - @Override // from interface IRectangle - public int outcode (double px, double py) { - int code = 0; - - if (width <= 0) { - code |= OUT_LEFT | OUT_RIGHT; - } else if (px < x) { - code |= OUT_LEFT; - } else if (px > x + width) { - code |= OUT_RIGHT; - } - - if (height <= 0) { - code |= OUT_TOP | OUT_BOTTOM; - } else if (py < y) { - code |= OUT_TOP; - } else if (py > y + height) { - code |= OUT_BOTTOM; - } - - return code; - } - - @Override // from interface IRectangle - public int outcode (IPoint p) { - return outcode(p.getX(), p.getY()); - } - - @Override // from interface IRectangle - public Rectangle clone () { - try { - return (Rectangle)super.clone(); - } catch (CloneNotSupportedException cnse) { - throw new AssertionError(cnse); - } - } - - @Override // from interface Shape - public boolean contains (float px, float py) { - if (isEmpty()) { - return false; - } - if (px < x || py < y) { - return false; - } - px -= x; - py -= y; - return px < width && py < height; - } - - @Override // from interface Shape - public boolean contains (IPoint p) { - return contains(p.getX(), p.getY()); - } - - @Override // from interface Shape - public boolean contains (float rx, float ry, float rw, float rh) { - return contains(rx, ry) && contains(rx + rw - 1, ry + rh - 1); - } - - @Override // from interface Shape - public boolean contains (IRectangle r) { - return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); - } - - @Override // from interface Shape - public boolean intersects (float rx, float ry, float rw, float rh) { - return !intersection(rx, ry, rw, rh).isEmpty(); // TODO: don't create garbage - } - - @Override // from interface Shape - public boolean intersects (IRectangle r) { - return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); - } - - @Override // from interface Shape - public IRectangle getBounds () { - return this; - } - - @Override - public boolean equals (Object obj) { - if (obj == this) { - return true; - } - if (obj instanceof Rectangle) { - Rectangle r = (Rectangle)obj; - return r.x == x && r.y == y && r.width == width && r.height == height; - } - return false; - } - - @Override - public int hashCode () { - return Float.floatToIntBits(x) ^ Float.floatToIntBits(y) ^ - Float.floatToIntBits(width) ^ Float.floatToIntBits(height); - } - - @Override - public String toString () { - return Geometry.dimenToString(width, height) + Geometry.pointToString(x, y); + @Override // from RectangularShape + public void setFrame (float x, float y, float w, float h) { + setBounds(x, y, w, h); } } diff --git a/src/main/java/pythagoras/f/RectangularShape.java b/src/main/java/pythagoras/f/RectangularShape.java new file mode 100644 index 0000000..0083829 --- /dev/null +++ b/src/main/java/pythagoras/f/RectangularShape.java @@ -0,0 +1,173 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.f; + +/** + * The base class for various {@link Shape} objects whose geometry is defined by a rectangular + * frame. + */ +public abstract class RectangularShape implements IRectangularShape +{ + /** + * Sets the location and size of the framing rectangle of this shape to the specified values. + */ + public abstract void setFrame (float x, float y, float w, float h); + + /** + * Sets the location and size of the framing rectangle of this shape to the supplied values. + */ + public void setFrame (IPoint loc, IDimension size) { + setFrame(loc.getX(), loc.getY(), size.getWidth(), size.getHeight()); + } + + /** + * Sets the location and size of the framing rectangle of this shape to be equal to the + * supplied rectangle. + */ + public void setFrame (IRectangle r) { + setFrame(r.getX(), r.getY(), r.getWidth(), r.getHeight()); + } + + /** + * Sets the location and size of the framing rectangle of this shape based on the specified + * diagonal line. + */ + public void setFrameFromDiagonal (float x1, float y1, float x2, float y2) { + float rx, ry, rw, rh; + if (x1 < x2) { + rx = x1; + rw = x2 - x1; + } else { + rx = x2; + rw = x1 - x2; + } + if (y1 < y2) { + ry = y1; + rh = y2 - y1; + } else { + ry = y2; + rh = y1 - y2; + } + setFrame(rx, ry, rw, rh); + } + + /** + * Sets the location and size of the framing rectangle of this shape based on the supplied + * diagonal line. + */ + public void setFrameFromDiagonal (IPoint p1, IPoint p2) { + setFrameFromDiagonal(p1.getX(), p1.getY(), p2.getX(), p2.getY()); + } + + /** + * Sets the location and size of the framing rectangle of this shape based on the specified + * center and corner points. + */ + public void setFrameFromCenter (float centerX, float centerY, + float cornerX, float cornerY) { + float width = Math.abs(cornerX - centerX); + float height = Math.abs(cornerY - centerY); + setFrame(centerX - width, centerY - height, width * 2, height * 2); + } + + /** + * Sets the location and size of the framing rectangle of this shape based on the supplied + * center and corner points. + */ + public void setFrameFromCenter (IPoint center, IPoint corner) { + setFrameFromCenter(center.getX(), center.getY(), corner.getX(), corner.getY()); + } + + @Override // from IRectangularShape + public float getMinX () { + return getX(); + } + + @Override // from IRectangularShape + public float getMinY () { + return getY(); + } + + @Override // from IRectangularShape + public float getMaxX () { + return getX() + getWidth(); + } + + @Override // from IRectangularShape + public float getMaxY () { + return getY() + getHeight(); + } + + @Override // from IRectangularShape + public float getCenterX () { + return getX() + getWidth() / 2; + } + + @Override // from IRectangularShape + public float getCenterY () { + return getY() + getHeight() / 2; + } + + @Override // from IRectangularShape + public IRectangle frame () { + return bounds(); + } + + @Override // from IRectangularShape + public Rectangle getFrame () { + return getBounds(); + } + + @Override // from interface IShape + public boolean isEmpty () { + return getWidth() <= 0 || getHeight() <= 0; + } + + @Override // from interface IShape + public boolean contains (IPoint point) { + return contains(point.getX(), point.getY()); + } + + @Override // from interface IShape + public boolean contains (IRectangle rect) { + return contains(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()); + } + + @Override // from interface IShape + public boolean intersects (IRectangle rect) { + return intersects(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()); + } + + @Override // from interface IShape + public IRectangle bounds () { + return new AbstractRectangle() { + @Override public float getX () { + return RectangularShape.this.getX(); + } + @Override public float getY () { + return RectangularShape.this.getY(); + } + @Override public float getWidth () { + return RectangularShape.this.getWidth(); + } + @Override public float getHeight () { + return RectangularShape.this.getHeight(); + } + // this isn't visible in the type, so won't be called by non-combatants + @Override public void setFrame (float x, float y, float w, float h) { + throw new UnsupportedOperationException(); + } + }; + } + + @Override // from interface IShape + public Rectangle getBounds () { + return new Rectangle(getX(), getY(), getWidth(), getHeight()); + } + + // public PathIterator getPathIterator (AffineTransform t, float flatness) { + // return new FlatteningPathIterator(getPathIterator(t), flatness); + // } +}