/******************************************************************************* * Copyright (c) 2000, 2015 IBM Corporation and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Alexander Nyßen (itemis AG) - migration to double precision * Matthias Wienand (itemis AG) - contribution for Bugzilla #355997 * *******************************************************************************/ package org.eclipse.gef.geometry.planar; import org.eclipse.gef.geometry.euclidean.Angle; import org.eclipse.gef.geometry.internal.utils.PrecisionUtils; /** * Represents the geometric shape of a rectangle, where a rectangle is * characterized by means of its upper left corner (x,y) and its size (width, * height). * * Note that while all manipulations (e.g. within shrink, expand) within this * class are based on double precision, all comparisons (e.g. within contains, * intersects, equals, etc.) are based on a limited precision (with an accuracy * defined within {@link PrecisionUtils}) to compensate for rounding effects. * * @author ebordeau * @author rhudson * @author msorens * @author pshah * @author sshaw * @author ahunter * @author anyssen * @author mwienand * */ public final class Rectangle extends AbstractRectangleBasedGeometry<Rectangle, Polygon> implements IShape { private static final long serialVersionUID = 1L; /** * Constructs a {@link Rectangle} with location (0,0) and a size of (0,0). */ public Rectangle() { super(0, 0, 0, 0); } /** * Constructs a Rectangle from the given values for its location (upper-left * corner point) and its size. If a negative, width or height is passed in, * 0 will be used instead. * * @param x * the x location of the new {@link Rectangle} * @param y * the y location of the new {@link Rectangle} * @param width * the width of the new {@link Rectangle} * @param height * the height of the new {@link Rectangle} */ public Rectangle(double x, double y, double width, double height) { super(x, y, width < 0 ? 0 : width, height < 0 ? 0 : height); } /** * Constructs a new {@link Rectangle} with the given location and size. * * @param location * the location of the new {@link Rectangle} * @param size * the size of the new {@link Rectangle} * */ public Rectangle(Point location, Dimension size) { this(location.x, location.y, size.width, size.height); } /** * Constructs a new {@link Rectangle}, which is the smallest one containing * both given {@link Point}s. * * @param p1 * the first point used to construct the new {@link Rectangle} * @param p2 * the second point used to construct the new {@link Rectangle} * */ public Rectangle(Point p1, Point p2) { this(Math.min(p1.x, p2.x), Math.min(p1.y, p2.y), Math.abs(p2.x - p1.x), Math.abs(p2.y - p1.y)); } /** * Constructs a new {@link Rectangle} with x, y, width, and height values of * the given {@link Rectangle}. * * @param r * the {@link Rectangle}, whose x, y, width, and height values * should be used to initialize the new {@link Rectangle} * */ public Rectangle(Rectangle r) { this(r.x, r.y, r.width, r.height); } /** * Returns whether the point given by x and y is within the boundaries of * this Rectangle. * * @param x * the x-coordinate of the point to test * @param y * the y-coordinate of the point to test * @return true if the Point is (imprecisely) contained within this * Rectangle */ public boolean contains(double x, double y) { return PrecisionUtils.greaterEqual(y, this.y) && PrecisionUtils.smallerEqual(y, this.y + this.height) && PrecisionUtils.greaterEqual(x, this.x) && PrecisionUtils.smallerEqual(x, this.x + this.width); } /** * Returns true in case the rectangle specified by (x, y, width, height) is * contained within this {@link Rectangle}. * * @param x * The x coordinate of the rectangle to be tested for containment * @param y * The y coordinate of the rectangle to be tested for containment * @param width * The width of the rectangle to be tested for containment * @param height * The height of the rectangle to be tested for containment * @return <code>true</code> if the rectangle characterized by (x,y, width, * height) is (imprecisely) fully contained within this * {@link Rectangle}, <code>false</code> otherwise */ public boolean contains(double x, double y, double width, double height) { return PrecisionUtils.smallerEqual(this.x, x) && PrecisionUtils.smallerEqual(this.y, y) && PrecisionUtils.greaterEqual(this.x + this.width, x + width) && PrecisionUtils.greaterEqual(this.y + this.height, y + height); } @Override public boolean contains(IGeometry g) { if (g instanceof Rectangle) { return contains((Rectangle) g); } return ShapeUtils.contains(this, g); } /** * Returns whether the given point is within the boundaries of this * Rectangle. The boundaries are inclusive of the top and left edges, but * exclusive of the bottom and right edges. * * @param p * Point being tested for containment * @return true if the Point is within this Rectangle * */ @Override public boolean contains(Point p) { return contains(p.x(), p.y()); } /** * Tests whether this {@link Rectangle} fully contains the given other * {@link Rectangle}. * * @param r * the other {@link Rectangle} to test for being contained by * this {@link Rectangle} * @return <code>true</code> if this {@link Rectangle} contains the other * {@link Rectangle}, otherwise <code>false</code> * @see IShape#contains(IGeometry) */ public boolean contains(Rectangle r) { return contains(r.x, r.y, r.width, r.height); } /** * Returns <code>true</code> if this Rectangle's x, y, width, and height * values are identical to the provided ones. * * @param x * The x value to test * @param y * The y value to test * @param width * The width value to test * @param height * The height value to test * @return <code>true</code> if this Rectangle's x, y, width, and height * values are (imprecisely) equal to the provided ones, * <code>false</code> otherwise */ public boolean equals(double x, double y, double width, double height) { return PrecisionUtils.equal(this.x, x) && PrecisionUtils.equal(this.y, y) && PrecisionUtils.equal(this.width, width) && PrecisionUtils.equal(this.height, height); } /** * Returns whether the input object is equal to this Rectangle or not. * Rectangles are equivalent if their x, y, height, and width values are the * same. * * @param o * Object being tested for equality * @return Returns the result of the equality test * */ @Override public boolean equals(Object o) { if (this == o) { return true; } if (o instanceof Rectangle) { Rectangle r = (Rectangle) o; return equals(r.x, r.y, r.width, r.height); } return false; } /** * Returns the area of this {@link Rectangle}, i.e. the product of its width * and height. * * @return the area of this {@link Rectangle} */ public double getArea() { return width * height; } /** * Returns a new Point representing the middle point of the bottom side of * this Rectangle. * * @return Point at the bottom of the Rectangle */ public Point getBottom() { return new Point(x + width / 2, y + height); } /** * Returns a new Point representing the bottom left point of this Rectangle. * * @return Point at the bottom left of the rectangle */ public Point getBottomLeft() { return new Point(x, y + height); } /** * Returns a new Point representing the bottom right point of this * Rectangle. * * @return Point at the bottom right of the rectangle */ public Point getBottomRight() { return new Point(x + width, y + height); } /** * Returns a new Rectangle which has the exact same parameters as this * Rectangle. * * @return Copy of this Rectangle */ @Override public Rectangle getCopy() { return new Rectangle(x, y, width, height); } /** * Returns a new Rectangle which has the intersection of this Rectangle and * the rectangle provided as input. Returns an empty Rectangle if there is * no intersection. * * @param rect * Rectangle provided to test for intersection * @return A new Rectangle representing the intersection */ public Rectangle getIntersected(Rectangle rect) { return getCopy().intersect(rect); } /** * Returns a new Point representing the middle point of the left hand side * of this Rectangle. * * @return Point at the left of the Rectangle */ public Point getLeft() { return new Point(x, y + height / 2); } @Override public Polyline getOutline() { return new Polyline(x, y, x + width, y, x + width, y + height, x, y + height, x, y); } /** * Returns an array of {@link Line}s representing the top, right, bottom, * and left borders of this {@link Rectangle}. * * @return An array containing {@link Line} representations of this * {@link Rectangle}'s borders. */ @Override public Line[] getOutlineSegments() { Line[] segments = new Line[4]; segments[0] = new Line(x, y, x + width, y); segments[1] = new Line(x + width, y, x + width, y + height); segments[2] = new Line(x + width, y + height, x, y + height); segments[3] = new Line(x, y + height, x, y); return segments; } /** * Returns an array of {@link Point}s representing the top-left, top-right, * bottom-right, and bottom-left border points of this {@link Rectangle}. * * @return An array containing the border points of this {@link Rectangle} */ public Point[] getPoints() { return new Point[] { getTopLeft(), getTopRight(), getBottomRight(), getBottomLeft() }; } /** * Returns a new Point which represents the middle point of the right hand * side of this Rectangle. * * @return Point at the right of the Rectangle */ public Point getRight() { return new Point(x + width, y + height / 2); } /** * Rotates this {@link Rectangle} counter-clock-wise by the given * {@link Angle} around the center {@link Point} of this {@link Rectangle} * (see {@link AbstractRectangleBasedGeometry#getCenter()}). * * @param alpha * The rotation {@link Angle}. * @return the resulting {@link Polygon} * @see IRotatable#getRotatedCCW(Angle, Point) */ @Override public Polygon getRotatedCCW(Angle alpha) { Point centroid = getCenter(); return toPolygon().rotateCCW(alpha, centroid.x, centroid.y); } /** * Rotates this {@link Rectangle} counter-clock-wise by the given * {@link Angle} around the given {@link Point}. * * If the rotation {@link Angle} is not an integer multiple of 90 degrees, * the resulting figure cannot be expressed as a {@link Rectangle} object. * That's why this method returns a {@link Polygon} instead. * * @param alpha * the rotation angle * @param cx * x-component of the center point for the rotation * @param cy * y-component of the center point for the rotation * @return the resulting {@link Polygon} */ @Override public Polygon getRotatedCCW(Angle alpha, double cx, double cy) { return toPolygon().rotateCCW(alpha, cx, cy); } /** * Rotates this {@link Rectangle} counter-clock-wise by the given * {@link Angle} around the given {@link Point}. * * If the rotation {@link Angle} is not an integer multiple of 90 degrees, * the resulting figure cannot be expressed as a {@link Rectangle} object. * That's why this method returns a {@link Polygon} instead. * * @param alpha * the rotation angle * @param center * the center point for the rotation * @return the resulting {@link Polygon} */ @Override public Polygon getRotatedCCW(Angle alpha, Point center) { return toPolygon().rotateCCW(alpha, center.x, center.y); } /** * Rotates this {@link Rectangle} clock-wise by the given {@link Angle} * around the center ({@link AbstractRectangleBasedGeometry#getCenter()}) of * this {@link Rectangle}. * * @param alpha * the rotation {@link Angle} * @return the resulting {@link Polygon} * @see IRotatable#getRotatedCW(Angle, Point) */ @Override public Polygon getRotatedCW(Angle alpha) { Point centroid = getCenter(); return toPolygon().rotateCW(alpha, centroid.x, centroid.y); } /** * Rotates this {@link Rectangle} clock-wise by the given {@link Angle} * alpha around the given {@link Point} (cx, cy). * * If the rotation {@link Angle} is not an integer multiple of 90 degrees, * the resulting figure cannot be expressed as a {@link Rectangle} object. * That's why this method returns a {@link Polygon} instead. * * @param alpha * the rotation angle * @param cx * x-component of the center point for the rotation * @param cy * y-component of the center point for the rotation * @return the resulting {@link Polygon} */ @Override public Polygon getRotatedCW(Angle alpha, double cx, double cy) { return toPolygon().rotateCW(alpha, cx, cy); } /** * Rotates this {@link Rectangle} clock-wise by the given {@link Angle} * alpha around the given {@link Point}. * * If the rotation {@link Angle} is not an integer multiple of 90 degrees, * the resulting figure cannot be expressed as a {@link Rectangle} object. * That's why this method returns a {@link Polygon} instead. * * @param alpha * the rotation angle * @param center * the center point for the rotation * @return the resulting {@link Polygon} */ @Override public Polygon getRotatedCW(Angle alpha, Point center) { return toPolygon().rotateCW(alpha, center.x, center.y); } /** * Returns a new Point which represents the middle point of the top side of * this Rectangle. * * @return Point at the top of the Rectangle */ public Point getTop() { return new Point(x + width / 2, y); } /** * Returns a new Point which represents the top left hand corner of this * Rectangle. * * @return Point at the top left of the rectangle */ public Point getTopLeft() { return new Point(x, y); } /** * Returns a new Point which represents the top right hand corner of this * Rectangle. * * @return Point at the top right of the rectangle */ public Point getTopRight() { return new Point(x + width, y); } /** * Returns a {@link Polygon}, which represents the transformed * {@link Rectangle}. * * @see IGeometry#getTransformed(AffineTransform) */ @Override public Polygon getTransformed(AffineTransform t) { return new Polygon(t.getTransformed(getPoints())); } /** * Returns a new rectangle whose width and height have been interchanged, as * well as its x and y values. This can be useful in orientation changes. * * @return The transposed rectangle * */ public Rectangle getTransposed() { return getCopy().transpose(); } /** * Returns a new Rectangle which contains both this Rectangle and the Point * supplied as input. * * @param p * Point for calculating union * @return A new unioned Rectangle */ public Rectangle getUnioned(Point p) { return getCopy().union(p); } /** * Returns a new Rectangle which contains both this Rectangle and the * Rectangle supplied as input. * * @param rect * Rectangle for calculating union * @return A new unioned Rectangle */ public Rectangle getUnioned(Rectangle rect) { return getCopy().union(rect); } /** * Sets the bounds of this {@link Rectangle} to the intersection of this * {@link Rectangle} with the given one. * * @param r * The {@link Rectangle} to intersect this {@link Rectangle} * with. * @return <code>this</code> for convenience. */ public Rectangle intersect(Rectangle r) { double x1 = Math.max(x, r.x); double x2 = Math.min(x + width, r.x + r.width); double y1 = Math.max(y, r.y); double y2 = Math.min(y + height, r.y + r.height); if (PrecisionUtils.greaterEqual(x2 - x1, 0) && PrecisionUtils.greaterEqual(y2 - y1, 0)) { setBounds(x1, y1, x2 - x1 < 0 ? 0 : x2 - x1, y2 - y1 < 0 ? 0 : y2 - y1); return this; } setBounds(0, 0, 0, 0); // no intersection return this; } /** * Returns <code>true</code> if this Rectangle's width or height is less * than or equal to 0. * * @return <code>true</code> if this Rectangle is (imprecisely) considered * to be empty * */ public boolean isEmpty() { return PrecisionUtils.smallerEqual(width, 0) || PrecisionUtils.smallerEqual(height, 0); } /** * @see IGeometry#toPath() */ @Override public Path toPath() { return new Path().moveTo(x, y).lineTo(x + width, y) .lineTo(x + width, y + height).lineTo(x, y + height).close(); } /** * Converts this {@link Rectangle} into a {@link Polygon} representation. * The control points used to construct the polygon are the border points * returned by {@link #getPoints()}. * * @return A {@link Polygon} representation for this {@link Rectangle} */ public Polygon toPolygon() { return new Polygon(Point.getCopy(getPoints())); } @Override public String toString() { return "Rectangle: (" + x + ", " + y + ", " + //$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$ width + ", " + height + ")";//$NON-NLS-2$//$NON-NLS-1$ } @Override public boolean touches(IGeometry g) { if (g instanceof Line) { return touches((Line) g); } else if (g instanceof Rectangle) { return touches((Rectangle) g); } return super.touches(g); } /** * Tests whether this {@link Rectangle} and the given {@link Line} touch, * i.e. whether they have at least one point in common. * * @param l * The {@link Line} to test. * @return <code>true</code> if this {@link Rectangle} and the given * {@link Line} share at least one common point, <code>false</code> * otherwise. */ public boolean touches(Line l) { if (contains(l.getP1()) || contains(l.getP2())) { return true; } for (Line segment : getOutlineSegments()) { if (segment.intersects(l)) { return true; } } return false; } /** * Tests whether this {@link Rectangle} and the given other * {@link Rectangle} touch, i.e. whether they have at least one point in * common. * * @param r * The {@link Rectangle} to test * @return <code>true</code> if this {@link Rectangle} and the given * {@link Rectangle} share at least one common point, * <code>false</code> otherwise. * @see IGeometry#touches(IGeometry) */ public boolean touches(Rectangle r) { return PrecisionUtils.smallerEqual(r.x, x + width) && PrecisionUtils.smallerEqual(r.y, y + height) && PrecisionUtils.greaterEqual(r.x + r.width, x) && PrecisionUtils.greaterEqual(r.y + r.height, y); } /** * Switches the x and y values, as well as the width and height of this * Rectangle. Useful for orientation changes. * * @return <code>this</code> for convenience */ public Rectangle transpose() { double temp = x; x = y; y = temp; temp = width; width = height; height = temp; return this; } /** * Updates this {@link Rectangle}'s bounds so that the {@link Point} given * by (x,y) is contained. * * @param x * The x-coordinate of the {@link Point} to union with * @param y * The y-coordinate of the {@link Point} to union with * @return <code>this</code> for convenience * */ public Rectangle union(double x, double y) { if (x < this.x) { this.width += this.x - x; this.x = x; } else if (x > this.x + this.width) { this.width += x - this.x - this.width; } if (y < this.y) { this.height += this.y - y; this.y = y; } else if (y > this.y + this.height) { this.height += y - this.y - this.height; } return this; } /** * Updates this Rectangle's bounds to the union of this {@link Rectangle} * and the {@link Rectangle} with location (x, y) and size(w, h). * * @param x * The x-coordinate of the {@link Rectangle} to union with. * @param y * The y-coordinate of the {@link Rectangle} to union with * @param w * The width of the {@link Rectangle} to union with * @param h * The height of the {@link Rectangle} to union with * @return <code>this</code> for convenience * */ public Rectangle union(double x, double y, double w, double h) { double right = Math.max(this.x + width, x + w); double bottom = Math.max(this.y + height, y + h); this.x = Math.min(this.x, x); this.y = Math.min(this.y, y); this.width = right - this.x; this.height = bottom - this.y; return this; } /** * Updates this {@link Rectangle}'s bounds so that the given {@link Point} * is included within. * * @param p * The {@link Point} to union with * @return <code>this</code> for convenience */ public Rectangle union(Point p) { return union(p.x, p.y); } /** * Updates this Rectangle's bounds to the union of this {@link Rectangle} * and the {@link Rectangle}. * * @param r * The {@link Rectangle} to union with * @return <code>this</code> for convenience */ public Rectangle union(Rectangle r) { return union(r.x, r.y, r.width, r.height); } }