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
|
---|
78 | Point pRel = new Point(x - _centre.x, y - _centre.y);
|
---|
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)
|
---|
82 | float xNorm = (((float) pRel.x) * pRel.x) / (((float) _diameters.width) * _diameters.width);
|
---|
83 | float yNorm = (((float) pRel.y) * pRel.y) / (((float) _diameters.height) * _diameters.height);
|
---|
84 | return (xNorm + yNorm <= 4.0f);
|
---|
85 | }
|
---|
86 |
|
---|
87 | @Override
|
---|
88 | public int getMaxX()
|
---|
89 | {
|
---|
90 | return _centre.x + (_diameters.width / 2 + _diameters.width % 2);
|
---|
91 | }
|
---|
92 |
|
---|
93 | @Override
|
---|
94 | public int getMinX()
|
---|
95 | {
|
---|
96 | return _centre.x -(_diameters.width / 2 + _diameters.width % 2);
|
---|
97 | }
|
---|
98 |
|
---|
99 | @Override
|
---|
100 | public int getMaxY()
|
---|
101 | {
|
---|
102 | return _centre.y + (_diameters.height / 2 + _diameters.height % 2);
|
---|
103 | }
|
---|
104 |
|
---|
105 | @Override
|
---|
106 | public int getMinY()
|
---|
107 | {
|
---|
108 | return _centre.y - (_diameters.height / 2 + _diameters.height % 2);
|
---|
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);
|
---|
258 | return Math.atan2(pRel.y, pRel.x);
|
---|
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);
|
---|
271 | double x_0 = lineStartRel.x;
|
---|
272 | double y_0 = lineStartRel.y;
|
---|
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 | }
|
---|