source: trunk/src/org/expeditee/core/bounds/EllipticalBounds.java@ 1097

Last change on this file since 1097 was 1097, checked in by davidb, 6 years ago

Newly structured files from Corey's work on logic/graphics separation

File size: 8.0 KB
Line 
1package org.expeditee.core.bounds;
2
3import org.expeditee.Util;
4import org.expeditee.core.Dimension;
5import org.expeditee.core.Line;
6import org.expeditee.core.Point;
7
8/**
9 * A bounds in the shape of an ellipse.
10 *
11 * @author cts16
12 */
13public 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}
Note: See TracBrowser for help on using the repository browser.