/* File Box2D.java
*
* Project : Java Geometry Library
*
* ===========================================
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or (at
* your option) any later version.
*
* This library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. if not, write to :
* The Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* Created on 05 mar. 2007
*/
// package
package math.geom2d;
// Imports
import static java.lang.Double.*;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Collection;
import math.geom2d.domain.Boundary2D;
import math.geom2d.domain.BoundaryPolyCurve2D;
import math.geom2d.domain.ContourArray2D;
import math.geom2d.line.AbstractLine2D;
import math.geom2d.line.LineArc2D;
import math.geom2d.line.LineSegment2D;
import math.geom2d.line.LinearShape2D;
import math.geom2d.line.StraightLine2D;
import math.geom2d.polygon.LinearRing2D;
import math.geom2d.polygon.Polygon2D;
import math.geom2d.polygon.Polygons2D;
import math.utils.EqualUtils;
import static java.lang.Math.*;
/**
* This class defines bounds of a shape. It stores limits in each direction:
* x and y. It also provides methods for clipping
* others shapes, depending on their type.
*/
public class Box2D implements GeometricObject2D, Cloneable {
// ===================================================================
// Static factory
/**
* @deprecated since 0.11.1
*/
@Deprecated
public static Box2D create(double xmin, double xmax, double ymin, double ymax) {
return new Box2D(xmin, xmax, ymin, ymax);
}
/**
* @deprecated since 0.11.1
*/
@Deprecated
public static Box2D create(Point2D p1, Point2D p2) {
return new Box2D(p1, p2);
}
/**
* The box corresponding to the unit square, with bounds [0 1] in each
* direction
* @since 0.9.1
*/
public final static Box2D UNIT_SQUARE_BOX = Box2D.create(0, 1, 0, 1);
/**
* The box corresponding to the the whole plane, with infinite bounds
* in each direction.
* @since 0.9.1
*/
public final static Box2D INFINITE_BOX = Box2D.create(
NEGATIVE_INFINITY, POSITIVE_INFINITY,
NEGATIVE_INFINITY, POSITIVE_INFINITY);
// ===================================================================
// class variables
private double xmin = 0;
private double xmax = 0;
private double ymin = 0;
private double ymax = 0;
// ===================================================================
// constructors
/** Empty constructor (size and position zero) */
public Box2D() {
this(0, 0, 0, 0);
}
/**
* Main constructor, given bounds for x coord, then bounds for y coord.
*/
public Box2D(double xmin, double xmax, double ymin, double ymax) {
this.xmin = xmin;
this.xmax = xmax;
this.ymin = ymin;
this.ymax = ymax;
}
/** Constructor from awt, to allow easy construction from existing apps. */
public Box2D(java.awt.geom.Rectangle2D rect) {
this(rect.getX(), rect.getX() + rect.getWidth(),
rect.getY(), rect.getY() + rect.getHeight());
}
/**
* Constructor from 2 points, giving extreme coordinates of the box.
*/
public Box2D(Point2D p1, Point2D p2) {
double x1 = p1.x();
double y1 = p1.y();
double x2 = p2.x();
double y2 = p2.y();
this.xmin = Math.min(x1, x2);
this.xmax = Math.max(x1, x2);
this.ymin = Math.min(y1, y2);
this.ymax = Math.max(y1, y2);
}
/** Constructor from a point, a width and an height */
public Box2D(Point2D point, double w, double h) {
this(point.x(), point.x() + w, point.y(), point.y() + h);
}
// ===================================================================
// accessors to Box2D fields
public double getMinX() {
return xmin;
}
public double getMinY() {
return ymin;
}
public double getMaxX() {
return xmax;
}
public double getMaxY() {
return ymax;
}
public double getWidth() {
return xmax - xmin;
}
public double getHeight() {
return ymax - ymin;
}
/** Returns true if all bounds are finite. */
public boolean isBounded() {
if (isInfinite(xmin))
return false;
if (isInfinite(ymin))
return false;
if (isInfinite(xmax))
return false;
if (isInfinite(ymax))
return false;
return true;
}
// ===================================================================
// tests of inclusion
/**
* Checks if this box contains the given point.
*/
public boolean contains(Point2D point) {
double x = point.x();
double y = point.y();
if (x < xmin)
return false;
if (y < ymin)
return false;
if (x > xmax)
return false;
if (y > ymax)
return false;
return true;
}
/**
* Checks if this box contains the point defined by the given coordinates.
*/
public boolean contains(double x, double y) {
if (x < xmin)
return false;
if (y < ymin)
return false;
if (x > xmax)
return false;
if (y > ymax)
return false;
return true;
}
/**
* Tests if the specified Shape is totally contained in this Box2D. Note that
* the test is performed on the bounding box of the shape, then for rotated
* rectangles, this method can return false with a shape totally contained
* in the rectangle. The problem does not exist for horizontal rectangle,
* since edges of rectangle and bounding box are parallel.
*/
public boolean containsBounds(Shape2D shape) {
if (!shape.isBounded())
return false;
for (Point2D point : shape.boundingBox().vertices())
if (!contains(point))
return false;
return true;
}
// ===================================================================
// information on the boundary
/**
* Returns a set of straight of lines defining half-planes, that all contain
* the box. If the box is bounded, the number of straight lines is 4,
* otherwise it can be less.
*
* @return a set of straight lines
*/
public Collection clippingLines() {
ArrayList lines = new ArrayList(4);
if (isFinite(ymin))
lines.add(new StraightLine2D(0, ymin, 1, 0));
if (isFinite(xmax))
lines.add(new StraightLine2D(xmax, 0, 0, 1));
if (isFinite(ymax))
lines.add(new StraightLine2D(0, ymax, -1, 0));
if (isFinite(xmin))
lines.add(new StraightLine2D(xmin, 0, 0, -1));
return lines;
}
/**
* Returns the set of linear shapes that constitutes the boundary of this
* box.
*/
public Collection edges() {
ArrayList edges = new ArrayList(4);
if (isBounded()) {
edges.add(new LineSegment2D(xmin, ymin, xmax, ymin));
edges.add(new LineSegment2D(xmax, ymin, xmax, ymax));
edges.add(new LineSegment2D(xmax, ymax, xmin, ymax));
edges.add(new LineSegment2D(xmin, ymax, xmin, ymin));
return edges;
}
if (!isInfinite(ymin)) {
if (isInfinite(xmin) && isInfinite(xmax))
edges.add(new StraightLine2D(0, ymin, 1, 0));
else if (!isInfinite(xmin) && !isInfinite(xmax))
edges.add(new LineSegment2D(xmin, ymin, xmax, ymin));
else
edges.add(new LineArc2D(0, ymin, 1, 0, xmin, xmax));
}
if (!isInfinite(xmax)) {
if (isInfinite(ymin) && isInfinite(ymax))
edges.add(new StraightLine2D(xmax, 0, 0, 1));
else if (!isInfinite(ymin) && !isInfinite(ymax))
edges.add(new LineSegment2D(xmax, ymin, xmax, ymax));
else
edges.add(new LineArc2D(xmax, 0, 0, 1, ymin, ymax));
}
if (!isInfinite(ymax)) {
if (isInfinite(xmin) && isInfinite(xmax))
edges.add(new StraightLine2D(0, ymax, 1, 0));
else if (!isInfinite(xmin) && !isInfinite(xmax))
edges.add(new LineSegment2D(xmax, ymax, xmin, ymax));
else
edges.add(new LineArc2D(0, ymin, 1, 0, xmin, xmax).reverse());
}
if (!isInfinite(xmin)) {
if (isInfinite(ymin) && isInfinite(ymax))
edges.add(new StraightLine2D(xmin, 0, 0, -1));
else if (!isInfinite(ymin) && !isInfinite(ymax))
edges.add(new LineSegment2D(xmin, ymax, xmin, ymin));
else
edges.add(new LineArc2D(xmin, 0, 0, 1, ymin, ymax).reverse());
}
return edges;
}
/**
* Returns the boundary of this box. The boundary can be bounded, in the
* case of a bounded box. It is unbounded if at least one bound of the box
* is infinite. If both x bounds or both y-bounds are infinite, the
* boundary is constituted from 2 straight lines.
* @return the box boundary
*/
public Boundary2D boundary() {
// First case of totally bounded box
if (isBounded()) {
Point2D pts[] = new Point2D[4];
pts[0] = new Point2D(xmin, ymin);
pts[1] = new Point2D(xmax, ymin);
pts[2] = new Point2D(xmax, ymax);
pts[3] = new Point2D(xmin, ymax);
return new LinearRing2D(pts);
}
// extract boolean info on "boundedness" in each direction
boolean bx0 = !isInfinite(xmin);
boolean bx1 = !isInfinite(xmax);
boolean by0 = !isInfinite(ymin);
boolean by1 = !isInfinite(ymax);
// case of boxes unbounded in both x directions
if (!bx0 && !bx1) {
if (!by0 && !by1)
return new ContourArray2D();
if (by0 && !by1)
return new StraightLine2D(0, ymin, 1, 0);
if (!by0 && by1)
return new StraightLine2D(0, ymax, -1, 0);
return new ContourArray2D(new StraightLine2D[] {
new StraightLine2D(0, ymin, 1, 0),
new StraightLine2D(0, ymax, -1, 0) });
}
// case of boxes unbounded in both y directions
if (!by0 && !by1) {
if (!bx0 && !bx1)
return new ContourArray2D();
if (bx0 && !bx1)
return new StraightLine2D(xmin, 0, 0, -1);
if (!bx0 && bx1)
return new StraightLine2D(xmax, 0, 0, 1);
return new ContourArray2D(new StraightLine2D[] {
new StraightLine2D(xmin, 0, 0, -1),
new StraightLine2D(xmax, 0, 0, 1) });
}
// "corner boxes"
if (bx0 && by0) // lower left corner
return new BoundaryPolyCurve2D(new LineArc2D[] {
new LineArc2D(xmin, ymin, 0, -1, NEGATIVE_INFINITY, 0),
new LineArc2D(xmin, ymin, 1, 0, 0, POSITIVE_INFINITY) });
if (bx1 && by0) // lower right corner
return new BoundaryPolyCurve2D(new LineArc2D[] {
new LineArc2D(xmax, ymin, 1, 0, NEGATIVE_INFINITY, 0),
new LineArc2D(xmax, ymin, 0, 1, 0, POSITIVE_INFINITY) });
if (bx1 && by1) // upper right corner
return new BoundaryPolyCurve2D(new LineArc2D[] {
new LineArc2D(xmax, ymax, 0, 1, NEGATIVE_INFINITY, 0),
new LineArc2D(xmax, ymax, -1, 0, 0, POSITIVE_INFINITY) });
if (bx0 && by1) // upper left corner
return new BoundaryPolyCurve2D(new LineArc2D[] {
new LineArc2D(xmin, ymax, -1, 0, NEGATIVE_INFINITY, 0),
new LineArc2D(xmin, ymax, 0, -1, 0, POSITIVE_INFINITY) });
// Remains only 4 cases: boxes unbounded in only one direction
if (bx0)
return new BoundaryPolyCurve2D(new AbstractLine2D[] {
new LineArc2D(xmin, ymax, -1, 0, NEGATIVE_INFINITY, 0),
new LineSegment2D(xmin, ymax, xmin, ymin),
new LineArc2D(xmin, ymin, 1, 0, 0, POSITIVE_INFINITY) });
if (bx1)
return new BoundaryPolyCurve2D(new AbstractLine2D[] {
new LineArc2D(xmax, ymin, 1, 0, NEGATIVE_INFINITY, 0),
new LineSegment2D(xmax, ymin, xmax, ymax),
new LineArc2D(xmax, ymax, -1, 0, 0, POSITIVE_INFINITY) });
if (by0)
return new BoundaryPolyCurve2D(new AbstractLine2D[] {
new LineArc2D(xmin, ymin, 0, -1, NEGATIVE_INFINITY, 0),
new LineSegment2D(xmin, ymin, xmax, ymin),
new LineArc2D(xmax, ymin, 0, 1, 0, POSITIVE_INFINITY) });
if (by1)
return new BoundaryPolyCurve2D(new AbstractLine2D[] {
new LineArc2D(xmax, ymax, 0, 1, NEGATIVE_INFINITY, 0),
new LineSegment2D(xmax, ymax, xmin, ymax),
new LineArc2D(xmin, ymax, 0, -1, 0, POSITIVE_INFINITY) });
return null;
}
public Collection vertices() {
ArrayList points = new ArrayList(4);
boolean bx0 = isFinite(xmin);
boolean bx1 = isFinite(xmax);
boolean by0 = isFinite(ymin);
boolean by1 = isFinite(ymax);
if (bx0 && by0)
points.add(new Point2D(xmin, ymin));
if (bx1 && by0)
points.add(new Point2D(xmax, ymin));
if (bx0 && by1)
points.add(new Point2D(xmin, ymax));
if (bx1 && by1)
points.add(new Point2D(xmax, ymax));
return points;
}
private final static boolean isFinite(double value) {
if (isInfinite(value))
return false;
if (isNaN(value))
return false;
return true;
}
/** Returns the number of vertices of the box. */
public int vertexNumber() {
return this.vertices().size();
}
// ===================================================================
// combination of box with other boxes
/**
* Returns the Box2D which contains both this box and the specified box.
*
* @param box the bounding box to include
* @return a new Box2D
*/
public Box2D union(Box2D box) {
double xmin = Math.min(this.xmin, box.xmin);
double xmax = Math.max(this.xmax, box.xmax);
double ymin = Math.min(this.ymin, box.ymin);
double ymax = Math.max(this.ymax, box.ymax);
return new Box2D(xmin, xmax, ymin, ymax);
}
/**
* Returns the Box2D which is contained both by this box and by the
* specified box.
*
* @param box the bounding box to include
* @return a new Box2D
*/
public Box2D intersection(Box2D box) {
double xmin = Math.max(this.xmin, box.xmin);
double xmax = Math.min(this.xmax, box.xmax);
double ymin = Math.max(this.ymin, box.ymin);
double ymax = Math.min(this.ymax, box.ymax);
return new Box2D(xmin, xmax, ymin, ymax);
}
/**
* Changes the bounds of this box to also include bounds of the argument.
*
* @param box the bounding box to include
* @return this
*/
public Box2D merge(Box2D box) {
this.xmin = Math.min(this.xmin, box.xmin);
this.xmax = Math.max(this.xmax, box.xmax);
this.ymin = Math.min(this.ymin, box.ymin);
this.ymax = Math.max(this.ymax, box.ymax);
return this;
}
/**
* Clip this bounding box such that after clipping, it is totally contained
* in the given box.
*
* @return the clipped box
*/
public Box2D clip(Box2D box) {
this.xmin = Math.max(this.xmin, box.xmin);
this.xmax = Math.min(this.xmax, box.xmax);
this.ymin = Math.max(this.ymin, box.ymin);
this.ymax = Math.min(this.ymax, box.ymax);
return this;
}
/**
* Returns the new box created by an affine transform of this box.
* If the box is unbounded, return an infinite box in all directions.
*/
public Box2D transform(AffineTransform2D trans) {
// special case of unbounded box
if (!this.isBounded())
return Box2D.INFINITE_BOX;
// initialize with extreme values
double xmin = POSITIVE_INFINITY;
double xmax = NEGATIVE_INFINITY;
double ymin = POSITIVE_INFINITY;
double ymax = NEGATIVE_INFINITY;
// update bounds with coordinates of transformed box vertices
for (Point2D point : this.vertices()) {
point = point.transform(trans);
xmin = Math.min(xmin, point.x());
ymin = Math.min(ymin, point.y());
xmax = Math.max(xmax, point.x());
ymax = Math.max(ymax, point.y());
}
// create the resulting box
return new Box2D(xmin, xmax, ymin, ymax);
}
// ===================================================================
// conversion methods
/**
* Converts to AWT rectangle.
*
* @return an instance of java.awt.geom.Rectangle2D
*/
public java.awt.Rectangle asAwtRectangle() {
int xr = (int) floor(this.xmin);
int yr = (int) floor(this.ymin);
int wr = (int) ceil(this.xmax-xr);
int hr = (int) ceil(this.ymax-yr);
return new java.awt.Rectangle(xr, yr, wr, hr);
}
/**
* Converts to AWT Rectangle2D. Result is an instance of
* java.awt.geom.Rectangle2D.Double.
*
* @return an instance of java.awt.geom.Rectangle2D
*/
public java.awt.geom.Rectangle2D asAwtRectangle2D() {
return new java.awt.geom.Rectangle2D.Double(xmin, ymin, xmax - xmin, ymax - ymin);
}
/**
* Converts to a rectangle.
*
* @return an instance of Polygon2D
*/
public Polygon2D asRectangle() {
return Polygons2D.createRectangle(xmin, ymin, xmax, ymax);
}
/**
* Draws the boundary of the box on the specified graphics.
* @param g2 the instance of graphics to draw in.
* @throws UnboundedBox2DException if the box is unbounded
*/
public void draw(Graphics2D g2) {
if (!isBounded())
throw new UnboundedBox2DException(this);
this.boundary().draw(g2);
}
/**
* Fills the content of the box on the specified graphics.
* @param g2 the instance of graphics to draw in.
* @throws UnboundedBox2DException if the box is unbounded
*/
public void fill(Graphics2D g2) {
if (!isBounded())
throw new UnboundedBox2DException(this);
this.boundary().fill(g2);
}
/**
* @deprecated useless (0.11.1)
*/
@Deprecated
public Box2D boundingBox() {
return new Box2D(xmin, xmax, ymin, ymax);
}
/**
* Tests if boxes are the same. Two boxes are the same if they have the
* same bounds, up to the specified threshold value.
*/
public boolean almostEquals(GeometricObject2D obj, double eps) {
if (this==obj)
return true;
// check class, and cast type
if (!(obj instanceof Box2D))
return false;
Box2D box = (Box2D) obj;
if (Math.abs(this.xmin - box.xmin) > eps)
return false;
if (Math.abs(this.xmax - box.xmax) > eps)
return false;
if (Math.abs(this.ymin - box.ymin) > eps)
return false;
if (Math.abs(this.ymax - box.ymax) > eps)
return false;
return true;
}
// ===================================================================
// methods from Object interface
@Override
public String toString() {
return new String("Box2D("+xmin+","+xmax+","+ymin+","+ymax+")");
}
/**
* Test if boxes are the same. two boxes are the same if the have exactly
* the same bounds.
*/
@Override
public boolean equals(Object obj) {
if (this==obj)
return true;
// check class, and cast type
if (!(obj instanceof Box2D))
return false;
Box2D that = (Box2D) obj;
// Compare each field
if (!EqualUtils.areEqual(this.xmin, that.xmin))
return false;
if (!EqualUtils.areEqual(this.xmax, that.xmax))
return false;
if (!EqualUtils.areEqual(this.ymin, that.ymin))
return false;
if (!EqualUtils.areEqual(this.ymax, that.ymax))
return false;
return true;
}
/**
* @deprecated not necessary to clone immutable objects (0.11.2)
*/
@Deprecated
@Override
public Box2D clone() {
return new Box2D(xmin, xmax, ymin, ymax);
}
}
Ads help maintain this website.