[1097] | 1 | package org.expeditee.core.bounds;
|
---|
| 2 |
|
---|
| 3 | import org.expeditee.Util;
|
---|
| 4 | import org.expeditee.core.Dimension;
|
---|
| 5 | import org.expeditee.core.Line;
|
---|
| 6 | import org.expeditee.core.Point;
|
---|
| 7 |
|
---|
| 8 | /**
|
---|
| 9 | * A bounds in the shape of an ellipse.
|
---|
| 10 | *
|
---|
| 11 | * @author cts16
|
---|
| 12 | */
|
---|
| 13 | public class EllipticalBounds extends Bounds {
|
---|
| 14 |
|
---|
| 15 | /** The location of the centre of the ellipse. */
|
---|
| 16 | private Point _centre;
|
---|
| 17 | /** The diameters of the ellipse in the x- and y-dimension. */
|
---|
| 18 | private Dimension _diameters;
|
---|
| 19 |
|
---|
| 20 | /** Standard constructor. */
|
---|
| 21 | public EllipticalBounds(Point centre, Dimension diameters)
|
---|
| 22 | {
|
---|
| 23 | setCentre(centre);
|
---|
| 24 | setDiameters(diameters);
|
---|
| 25 | }
|
---|
| 26 |
|
---|
| 27 | /** Constructor for a circular bounds. */
|
---|
| 28 | public EllipticalBounds(Point centre, int diameter)
|
---|
| 29 | {
|
---|
| 30 | this(centre, new Dimension(diameter, diameter));
|
---|
| 31 | }
|
---|
| 32 |
|
---|
| 33 | /** Copy constructor. */
|
---|
| 34 | public EllipticalBounds(EllipticalBounds other)
|
---|
| 35 | {
|
---|
| 36 | setCentre(other._centre);
|
---|
| 37 | setDiameters(other._diameters);
|
---|
| 38 | }
|
---|
| 39 |
|
---|
| 40 | /** Sets the location of the centre of this elliptical bounds. */
|
---|
| 41 | public void setCentre(Point centre)
|
---|
| 42 | {
|
---|
| 43 | // Control referential access to private variables
|
---|
| 44 | _centre = new Point(centre);
|
---|
| 45 | }
|
---|
| 46 |
|
---|
| 47 | /** Sets the size of the diameters of this elliptical bounds. */
|
---|
| 48 | public void setDiameters(Dimension diameters)
|
---|
| 49 | {
|
---|
| 50 | // Control referential access to private variables
|
---|
| 51 | _diameters = new Dimension(diameters);
|
---|
| 52 |
|
---|
| 53 | // Diameters must be positive
|
---|
| 54 | _diameters.absolve();
|
---|
| 55 | }
|
---|
| 56 |
|
---|
| 57 | /** Gets the centre of the elliptical bounds. */
|
---|
| 58 | public Point getCentre()
|
---|
| 59 | {
|
---|
| 60 | // Control referential access to private variables
|
---|
| 61 | return _centre.clone();
|
---|
| 62 | }
|
---|
| 63 |
|
---|
| 64 | /** Gets the size of the diameters of the elliptical bounds. */
|
---|
| 65 | public Dimension getDiameters()
|
---|
| 66 | {
|
---|
| 67 | // Control referential access to private variables
|
---|
| 68 | return _diameters.clone();
|
---|
| 69 | }
|
---|
| 70 |
|
---|
| 71 | @Override
|
---|
| 72 | public boolean contains(int x, int y)
|
---|
| 73 | {
|
---|
| 74 | // If the ellipse is degenerate then it can't contain anything
|
---|
| 75 | if (_diameters.getArea() == 0) return false;
|
---|
| 76 |
|
---|
| 77 | // Get the position of the given coordinates with respect to the ellipse centre
|
---|
[1142] | 78 | Point pRel = new Point(x - _centre.getX(), y - _centre.getY());
|
---|
[1097] | 79 |
|
---|
| 80 | // x^2 / r_x^2 + y^2 / r_y^2 must be <= 1
|
---|
| 81 | // (using diameters instead of radii hence the 4.0f value)
|
---|
[1142] | 82 | float xNorm = (((float) pRel.getX()) * pRel.getX()) / (((float) _diameters.width) * _diameters.width);
|
---|
| 83 | float yNorm = (((float) pRel.getY()) * pRel.getY()) / (((float) _diameters.height) * _diameters.height);
|
---|
[1097] | 84 | return (xNorm + yNorm <= 4.0f);
|
---|
| 85 | }
|
---|
| 86 |
|
---|
| 87 | @Override
|
---|
| 88 | public int getMaxX()
|
---|
| 89 | {
|
---|
[1142] | 90 | return _centre.getX() + (_diameters.width / 2 + _diameters.width % 2);
|
---|
[1097] | 91 | }
|
---|
| 92 |
|
---|
| 93 | @Override
|
---|
| 94 | public int getMinX()
|
---|
| 95 | {
|
---|
[1142] | 96 | return _centre.getX() -(_diameters.width / 2 + _diameters.width % 2);
|
---|
[1097] | 97 | }
|
---|
| 98 |
|
---|
| 99 | @Override
|
---|
| 100 | public int getMaxY()
|
---|
| 101 | {
|
---|
[1142] | 102 | return _centre.getY() + (_diameters.height / 2 + _diameters.height % 2);
|
---|
[1097] | 103 | }
|
---|
| 104 |
|
---|
| 105 | @Override
|
---|
| 106 | public int getMinY()
|
---|
| 107 | {
|
---|
[1142] | 108 | return _centre.getY() - (_diameters.height / 2 + _diameters.height % 2);
|
---|
[1097] | 109 | }
|
---|
| 110 |
|
---|
| 111 | @Override
|
---|
| 112 | public EllipticalBounds translate(int x, int y)
|
---|
| 113 | {
|
---|
| 114 | _centre.add(x, y);
|
---|
| 115 |
|
---|
| 116 | return this;
|
---|
| 117 | }
|
---|
| 118 |
|
---|
| 119 | @Override
|
---|
| 120 | public boolean equals(Bounds other)
|
---|
| 121 | {
|
---|
| 122 | if (other instanceof EllipticalBounds) {
|
---|
| 123 | EllipticalBounds e = (EllipticalBounds) other;
|
---|
| 124 | return e._centre.equals(this._centre) && e._diameters.equals(this._diameters);
|
---|
| 125 | }
|
---|
| 126 | return false;
|
---|
| 127 | }
|
---|
| 128 |
|
---|
| 129 | @Override
|
---|
| 130 | public boolean intersects(AxisAlignedBoxBounds other)
|
---|
| 131 | {
|
---|
| 132 | // Treat the box as a polygon
|
---|
| 133 | return intersects(other.getPolygon());
|
---|
| 134 | }
|
---|
| 135 |
|
---|
| 136 | @Override
|
---|
| 137 | public boolean intersects(CombinationBounds other)
|
---|
| 138 | {
|
---|
| 139 | // Defer to the CombinationBounds routine
|
---|
| 140 | return other.intersects(this);
|
---|
| 141 | }
|
---|
| 142 |
|
---|
| 143 | @Override
|
---|
| 144 | public boolean intersects(EllipticalBounds other)
|
---|
| 145 | {
|
---|
| 146 | // TODO: Implement. cts16
|
---|
| 147 | System.err.println("Unimplemented method: EllipticalBounds.intersects(EllipticalBounds)");
|
---|
| 148 | return false;
|
---|
| 149 | }
|
---|
| 150 |
|
---|
| 151 | @Override
|
---|
| 152 | public boolean intersects(PolygonBounds other)
|
---|
| 153 | {
|
---|
| 154 | // If any point in the polygon is inside the ellipse then they intersect
|
---|
| 155 | for (Point p : other.getVertices()) {
|
---|
| 156 | if (contains(p)) return true;
|
---|
| 157 | }
|
---|
| 158 |
|
---|
| 159 | // If no polygon point is contained, may still intersect polygon boundary lines
|
---|
| 160 | for (Line line : other.getBorderLines()) {
|
---|
| 161 | if (isIntersectedByLine(line)) return true;
|
---|
| 162 | }
|
---|
| 163 |
|
---|
| 164 | return false;
|
---|
| 165 | }
|
---|
| 166 |
|
---|
| 167 | @Override
|
---|
| 168 | public double getArea()
|
---|
| 169 | {
|
---|
| 170 | // Area = pi * r_x * r_y
|
---|
| 171 | // (dividing by 4.0 because using diameters instead of radii)
|
---|
| 172 | return Math.PI * _diameters.getArea() / 4.0;
|
---|
| 173 | }
|
---|
| 174 |
|
---|
| 175 | @Override
|
---|
| 176 | public boolean perimeterContains(Point p, double error)
|
---|
| 177 | {
|
---|
| 178 | // If the given point is null, we can't test
|
---|
| 179 | if (p == null) return false;
|
---|
| 180 |
|
---|
| 181 | // Find the distance from the centre of the ellipse to the given point
|
---|
| 182 | double pRadialDistance = Point.distanceBetween(p, _centre);
|
---|
| 183 |
|
---|
| 184 | // Find the distance to the ellipse border in the same direction
|
---|
| 185 | double borderRadialDistance = radialDistanceAtAngle(getAngleToPoint(p));
|
---|
| 186 |
|
---|
| 187 | // If the difference between these two distances is within error then we are on the perimeter
|
---|
| 188 | return Math.abs(borderRadialDistance - pRadialDistance) <= error;
|
---|
| 189 |
|
---|
| 190 | }
|
---|
| 191 |
|
---|
| 192 | @Override
|
---|
| 193 | public boolean completelyContains(AxisAlignedBoxBounds other)
|
---|
| 194 | {
|
---|
| 195 | // Can't contain nothing
|
---|
| 196 | if (other == null) return false;
|
---|
| 197 |
|
---|
| 198 | // It is sufficient to test if all four corners are contained
|
---|
| 199 | Point[] corners = other.getCorners();
|
---|
| 200 | for (Point corner : corners) {
|
---|
| 201 | if (!contains(corner)) return false;
|
---|
| 202 | }
|
---|
| 203 |
|
---|
| 204 | return true;
|
---|
| 205 | }
|
---|
| 206 |
|
---|
| 207 | @Override
|
---|
| 208 | public boolean completelyContains(CombinationBounds other)
|
---|
| 209 | {
|
---|
| 210 | // Can't contain nothing
|
---|
| 211 | if (other == null) return false;
|
---|
| 212 |
|
---|
| 213 | // Must contain all sub-bounds
|
---|
| 214 | for (Bounds bound : other._bounds) {
|
---|
| 215 | if (!completelyContains(bound)) return false;
|
---|
| 216 | }
|
---|
| 217 |
|
---|
| 218 | return true;
|
---|
| 219 | }
|
---|
| 220 |
|
---|
| 221 | @Override
|
---|
| 222 | public boolean completelyContains(EllipticalBounds other)
|
---|
| 223 | {
|
---|
| 224 | // TODO: Implement. cts16
|
---|
| 225 | System.err.println("Unimplemented method: EllipticalBounds.completelyContains(EllipticalBounds)");
|
---|
| 226 | return false;
|
---|
| 227 | }
|
---|
| 228 |
|
---|
| 229 | @Override
|
---|
| 230 | public boolean completelyContains(PolygonBounds other)
|
---|
| 231 | {
|
---|
| 232 | // Can't contain nothing
|
---|
| 233 | if (other == null) return false;
|
---|
| 234 |
|
---|
| 235 | // It is sufficient to test if all vertices are contained
|
---|
| 236 | Point[] vertices = other.getVertices();
|
---|
| 237 | for (Point vertex : vertices) {
|
---|
| 238 | if (!contains(vertex)) return false;
|
---|
| 239 | }
|
---|
| 240 |
|
---|
| 241 | return true;
|
---|
| 242 | }
|
---|
| 243 |
|
---|
| 244 | /** Gets the distance to the border of the ellipse at a given angle. */
|
---|
| 245 | public double radialDistanceAtAngle(double angle)
|
---|
| 246 | {
|
---|
| 247 | double xComponent = (_diameters.width / 2.0) * Math.cos(angle);
|
---|
| 248 | double yComponent = (_diameters.height / 2.0) * Math.sin(angle);
|
---|
| 249 | return Math.sqrt(xComponent * xComponent + yComponent * yComponent);
|
---|
| 250 | }
|
---|
| 251 |
|
---|
| 252 | /** Gets the bearing angle from the centre of the ellipse to a given point. */
|
---|
| 253 | public double getAngleToPoint(Point p)
|
---|
| 254 | {
|
---|
| 255 | if (p == null) return Double.NaN;
|
---|
| 256 |
|
---|
| 257 | Point pRel = Point.difference(p, _centre);
|
---|
[1142] | 258 | return Math.atan2(pRel.getY(), pRel.getX());
|
---|
[1097] | 259 | }
|
---|
| 260 |
|
---|
| 261 | /** Whether the given line intersects the perimeter of this ellipse. */
|
---|
| 262 | public boolean isIntersectedByLine(Line line)
|
---|
| 263 | {
|
---|
| 264 | // Can't intersect with nothing
|
---|
| 265 | if (line == null) return false;
|
---|
| 266 |
|
---|
| 267 | // Get the t-values at the points of intersections (if there are any)
|
---|
| 268 | double r_x = _diameters.width / 2.0;
|
---|
| 269 | double r_y = _diameters.height / 2.0;
|
---|
| 270 | Point lineStartRel = Point.difference(line.getFirstEnd(), _centre);
|
---|
[1142] | 271 | double x_0 = lineStartRel.getX();
|
---|
| 272 | double y_0 = lineStartRel.getY();
|
---|
[1097] | 273 | double x_d = line.deltaX();
|
---|
| 274 | double y_d = line.deltaY();
|
---|
| 275 | double r_x_squared = r_x * r_x;
|
---|
| 276 | double r_y_squared = r_y * r_y;
|
---|
| 277 | double x_0_squared = x_0 * x_0;
|
---|
| 278 | double y_0_squared = y_0 * y_0;
|
---|
| 279 | double x_d_squared = x_d * x_d;
|
---|
| 280 | double y_d_squared = y_d * y_d;
|
---|
| 281 | double[] roots = Util.quadraticEquation(
|
---|
| 282 | x_d_squared * r_y_squared + y_d_squared * r_x_squared,
|
---|
| 283 | 2 * (r_y_squared * x_0 * x_d + r_x_squared * y_0 * y_d),
|
---|
| 284 | r_y_squared * x_0_squared + r_x_squared * y_0_squared - r_x_squared * r_y_squared
|
---|
| 285 | );
|
---|
| 286 |
|
---|
| 287 | // No roots, no intersection
|
---|
| 288 | if (roots == null) return false;
|
---|
| 289 |
|
---|
| 290 | // See if any of the roots is inside the line segment
|
---|
| 291 | for (double root : roots) {
|
---|
| 292 | if (0.0 <= root && root <= 1.0) return true;
|
---|
| 293 | }
|
---|
| 294 |
|
---|
| 295 | return false;
|
---|
| 296 | }
|
---|
| 297 |
|
---|
| 298 | /** Creates a polygonal approximation of this ellipse. */
|
---|
| 299 | public PolygonBounds getPolygon(int nSegments)
|
---|
| 300 | {
|
---|
| 301 | return PolygonBounds.fromEllipse(this, nSegments);
|
---|
| 302 | }
|
---|
| 303 |
|
---|
| 304 | @Override
|
---|
| 305 | public EllipticalBounds clone()
|
---|
| 306 | {
|
---|
| 307 | return new EllipticalBounds(this);
|
---|
| 308 | }
|
---|
| 309 |
|
---|
| 310 | }
|
---|