package org.expeditee.core.bounds; import org.expeditee.Util; import org.expeditee.core.Dimension; import org.expeditee.core.Line; import org.expeditee.core.Point; /** * A bounds in the shape of an ellipse. * * @author cts16 */ public class EllipticalBounds extends Bounds { /** The location of the centre of the ellipse. */ private Point _centre; /** The diameters of the ellipse in the x- and y-dimension. */ private Dimension _diameters; /** Standard constructor. */ public EllipticalBounds(Point centre, Dimension diameters) { setCentre(centre); setDiameters(diameters); } /** Constructor for a circular bounds. */ public EllipticalBounds(Point centre, int diameter) { this(centre, new Dimension(diameter, diameter)); } /** Copy constructor. */ public EllipticalBounds(EllipticalBounds other) { setCentre(other._centre); setDiameters(other._diameters); } /** Sets the location of the centre of this elliptical bounds. */ public void setCentre(Point centre) { // Control referential access to private variables _centre = new Point(centre); } /** Sets the size of the diameters of this elliptical bounds. */ public void setDiameters(Dimension diameters) { // Control referential access to private variables _diameters = new Dimension(diameters); // Diameters must be positive _diameters.absolve(); } /** Gets the centre of the elliptical bounds. */ public Point getCentre() { // Control referential access to private variables return _centre.clone(); } /** Gets the size of the diameters of the elliptical bounds. */ public Dimension getDiameters() { // Control referential access to private variables return _diameters.clone(); } @Override public boolean contains(int x, int y) { // If the ellipse is degenerate then it can't contain anything if (_diameters.getArea() == 0) return false; // Get the position of the given coordinates with respect to the ellipse centre Point pRel = new Point(x - _centre.x, y - _centre.y); // x^2 / r_x^2 + y^2 / r_y^2 must be <= 1 // (using diameters instead of radii hence the 4.0f value) float xNorm = (((float) pRel.x) * pRel.x) / (((float) _diameters.width) * _diameters.width); float yNorm = (((float) pRel.y) * pRel.y) / (((float) _diameters.height) * _diameters.height); return (xNorm + yNorm <= 4.0f); } @Override public int getMaxX() { return _centre.x + (_diameters.width / 2 + _diameters.width % 2); } @Override public int getMinX() { return _centre.x -(_diameters.width / 2 + _diameters.width % 2); } @Override public int getMaxY() { return _centre.y + (_diameters.height / 2 + _diameters.height % 2); } @Override public int getMinY() { return _centre.y - (_diameters.height / 2 + _diameters.height % 2); } @Override public EllipticalBounds translate(int x, int y) { _centre.add(x, y); return this; } @Override public boolean equals(Bounds other) { if (other instanceof EllipticalBounds) { EllipticalBounds e = (EllipticalBounds) other; return e._centre.equals(this._centre) && e._diameters.equals(this._diameters); } return false; } @Override public boolean intersects(AxisAlignedBoxBounds other) { // Treat the box as a polygon return intersects(other.getPolygon()); } @Override public boolean intersects(CombinationBounds other) { // Defer to the CombinationBounds routine return other.intersects(this); } @Override public boolean intersects(EllipticalBounds other) { // TODO: Implement. cts16 System.err.println("Unimplemented method: EllipticalBounds.intersects(EllipticalBounds)"); return false; } @Override public boolean intersects(PolygonBounds other) { // If any point in the polygon is inside the ellipse then they intersect for (Point p : other.getVertices()) { if (contains(p)) return true; } // If no polygon point is contained, may still intersect polygon boundary lines for (Line line : other.getBorderLines()) { if (isIntersectedByLine(line)) return true; } return false; } @Override public double getArea() { // Area = pi * r_x * r_y // (dividing by 4.0 because using diameters instead of radii) return Math.PI * _diameters.getArea() / 4.0; } @Override public boolean perimeterContains(Point p, double error) { // If the given point is null, we can't test if (p == null) return false; // Find the distance from the centre of the ellipse to the given point double pRadialDistance = Point.distanceBetween(p, _centre); // Find the distance to the ellipse border in the same direction double borderRadialDistance = radialDistanceAtAngle(getAngleToPoint(p)); // If the difference between these two distances is within error then we are on the perimeter return Math.abs(borderRadialDistance - pRadialDistance) <= error; } @Override public boolean completelyContains(AxisAlignedBoxBounds other) { // Can't contain nothing if (other == null) return false; // It is sufficient to test if all four corners are contained Point[] corners = other.getCorners(); for (Point corner : corners) { if (!contains(corner)) return false; } return true; } @Override public boolean completelyContains(CombinationBounds other) { // Can't contain nothing if (other == null) return false; // Must contain all sub-bounds for (Bounds bound : other._bounds) { if (!completelyContains(bound)) return false; } return true; } @Override public boolean completelyContains(EllipticalBounds other) { // TODO: Implement. cts16 System.err.println("Unimplemented method: EllipticalBounds.completelyContains(EllipticalBounds)"); return false; } @Override public boolean completelyContains(PolygonBounds other) { // Can't contain nothing if (other == null) return false; // It is sufficient to test if all vertices are contained Point[] vertices = other.getVertices(); for (Point vertex : vertices) { if (!contains(vertex)) return false; } return true; } /** Gets the distance to the border of the ellipse at a given angle. */ public double radialDistanceAtAngle(double angle) { double xComponent = (_diameters.width / 2.0) * Math.cos(angle); double yComponent = (_diameters.height / 2.0) * Math.sin(angle); return Math.sqrt(xComponent * xComponent + yComponent * yComponent); } /** Gets the bearing angle from the centre of the ellipse to a given point. */ public double getAngleToPoint(Point p) { if (p == null) return Double.NaN; Point pRel = Point.difference(p, _centre); return Math.atan2(pRel.y, pRel.x); } /** Whether the given line intersects the perimeter of this ellipse. */ public boolean isIntersectedByLine(Line line) { // Can't intersect with nothing if (line == null) return false; // Get the t-values at the points of intersections (if there are any) double r_x = _diameters.width / 2.0; double r_y = _diameters.height / 2.0; Point lineStartRel = Point.difference(line.getFirstEnd(), _centre); double x_0 = lineStartRel.x; double y_0 = lineStartRel.y; double x_d = line.deltaX(); double y_d = line.deltaY(); double r_x_squared = r_x * r_x; double r_y_squared = r_y * r_y; double x_0_squared = x_0 * x_0; double y_0_squared = y_0 * y_0; double x_d_squared = x_d * x_d; double y_d_squared = y_d * y_d; double[] roots = Util.quadraticEquation( x_d_squared * r_y_squared + y_d_squared * r_x_squared, 2 * (r_y_squared * x_0 * x_d + r_x_squared * y_0 * y_d), r_y_squared * x_0_squared + r_x_squared * y_0_squared - r_x_squared * r_y_squared ); // No roots, no intersection if (roots == null) return false; // See if any of the roots is inside the line segment for (double root : roots) { if (0.0 <= root && root <= 1.0) return true; } return false; } /** Creates a polygonal approximation of this ellipse. */ public PolygonBounds getPolygon(int nSegments) { return PolygonBounds.fromEllipse(this, nSegments); } @Override public EllipticalBounds clone() { return new EllipticalBounds(this); } }