source: trunk/src/org/expeditee/core/bounds/PolygonBounds.java@ 1415

Last change on this file since 1415 was 1415, checked in by bln4, 5 years ago

Renamed Frame.getItems() to Frame.getSortedItems() to better represent its functionality.

-> org.apollo.ApolloGestureActions
-> org.apollo.ApolloSystem
-> org.expeditee.actions.Actions
-> org.expeditee.actions.Debug
-> org.expeditee.actions.ExploratorySearchActions
-> org.expeditee.actions.JfxBrowserActions
-> org.expeditee.actions.Misc
-> org.expeditee.actions.Navigation
-> org.expeditee.actions.ScriptBase
-> org.expeditee.actions.Simple
-> org.expeditee.agents.ComputeTree
-> org.expeditee.agents.CopyTree
-> org.expeditee.agents.DisplayComet
-> org.expeditee.agents.DisplayTree
-> org.expeditee.agents.DisplayTreeLeaves
-> org.expeditee.agents.GraphFramesetLinks
-> org.expeditee.agents.TreeProcessor
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gui.DisplayController
-> org.expeditee.gui.FrameCreator
-> org.expeditee.gui.FrameIO
-> org.expeditee.io.DefaultTreeWriter
-> org.expeditee.io.JavaWriter
-> org.expeditee.io.PDF2Writer
-> org.expeditee.io.TXTWriter
-> org.expeditee.io.WebParser
-> org.expeditee.io.flowlayout.XGroupItem
-> org.expeditee.items.Dot
-> org.expeditee.items.Item
-> org.expeditee.items.ItemUtils
-> org.expeditee.network.FrameShare
-> org.expeditee.stats.TreeStats


Created ItemsList class to wrap ArrayList<Item>. Frames now use this new class to store its body list (used for display) as well as its primaryBody and surrogateBody.

-> org.expeditee.agents.Format
-> org.expeditee.agents.HFormat
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gui.Frame
-> org.expeditee.gui.FrameUtils


Refactorted Frame.setResort(bool) to Frame.invalidateSorted() to better function how it is intended to with a more accurate name.

-> org.expeditee.agents.Sort


When writing out .exp files and getting attributes to respond to LEFT + RIGHT click, boolean items are by default true. This has always been the case. An ammendment to this is that defaults can now be established.
Also added 'EnterClick' functionality. If cursored over a item with this property and you press enter, it acts as if you have clicked on it instead.

-> org.expeditee.assets.resources-public.framesets.authentication.1.exp to 6.exp
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gio.input.KBMInputEvent
-> org.expeditee.gio.javafx.JavaFXConversions
-> org.expeditee.gio.swing.SwingConversions
-> org.expeditee.gui.AttributeUtils
-> org.expeditee.io.Conversion
-> org.expeditee.io.DefaultFrameWriter
-> org.expeditee.items.Item


Fixed a bug caused by calling Math.abs on Integer.MIN_VALUE returning unexpected result. Due to zero being a thing, you cannot represent Math.abs(Integer.MIN_VALUE) in a Integer object. The solution is to use Integer.MIN_VALUE + 1 instead of Integer.MIN_VALUE.

-> org.expeditee.core.bounds.CombinationBounds
-> org.expeditee.io.flowlayout.DimensionExtent


Recoded the contains function in EllipticalBounds so that intersection tests containing circles work correctly.

-> org.expeditee.core.bounds.EllipticalBounds


Added toString() to PolygonBounds to allow for useful printing during debugging.

-> org.expeditee.core.bounds.PolygonBounds

Implemented Surrogate Mode!

-> org.expeditee.encryption.io.EncryptedExpReader
-> org.expeditee.encryption.io.EncryptedExpWriter
-> org.expeditee.encryption.items.surrogates.EncryptionDetail
-> org.expeditee.encryption.items.surrogates.Label
-> org.expeditee.gui.FrameUtils
-> org.expeditee.gui.ItemsList
-> org.expeditee.items.Item
-> org.expeditee.items.Text


???? Use Integer.MAX_VALUE cast to a float instead of Float.MAX_VALUE. This fixed some bug which I cannot remember.

-> org.expeditee.gio.TextLayoutManager
-> org.expeditee.gio.swing.SwingTextLayoutManager


Improved solution for dealing with the F10 key taking focus away from Expeditee due to it being a assessibility key.

-> org.expeditee.gio.swing.SwingInputManager


Renamed variable visibleItems in FrameGraphics.paintFrame to itemsToPaintCanditates to better represent functional intent.

-> org.expeditee.gui.FrameGraphics


Improved checking for if personal resources exist before recreating them

-> org.expeditee.gui.FrameIO


Repeated messages to message bay now have a visual feedback instead of just a beep. This visual feedback is in the form of a count of the amount of times it has repeated.

-> org.expeditee.gui.MessageBay


Updated comment on the Vector class to explain what vectors are.

-> org.expeditee.gui.Vector


Added constants to represent all of the property keys in DefaultFrameReader and DefaultFrameWriter.

-> org.expeditee.io.DefaultFrameReader
-> org.expeditee.io.DefaultFrameWriter


Updated the KeyList setting to be more heirarcial with how users will store their Secrets.

-> org.expeditee.settings.identity.secrets.KeyList

File size: 13.8 KB
Line 
1package org.expeditee.core.bounds;
2
3import java.util.LinkedList;
4import java.util.List;
5
6import org.expeditee.core.Line;
7import org.expeditee.core.Point;
8
9/**
10 * A bounds that is defined as the area enclosed by a polygon formed
11 * from a set of points. When considering if something is enclosed by
12 * a polygon, the set of points is always considered closed (even if
13 * the first and last points arenn't equal).
14 *
15 * @author cts16
16 */
17public class PolygonBounds extends Bounds {
18
19 /** The points that form this polygon. */
20 private List<Point> _points;
21
22 /** The right-most extent of this bounds. */
23 Integer _maxX;
24 /** The left-most extent of this bounds. */
25 Integer _minX;
26 /** The down-most extent of this bounds. */
27 Integer _maxY;
28 /** The up-most extent of this bounds. */
29 Integer _minY;
30
31 /** Standard constructor (creates a degenerate polygon with no points). */
32 public PolygonBounds()
33 {
34 _points = new LinkedList<Point>();
35 _maxX = _minX = _maxY = _minY = null;
36 }
37
38 /** Copy constructor. */
39 public PolygonBounds(PolygonBounds other)
40 {
41 this();
42 for (Point p : other._points) {
43 addPoint(p);
44 }
45 }
46
47 /**
48 * Constructs a polygon from a set of lines. Lines should be connected
49 * end to end, otherwise the resulting polygon may not match the given input.
50 */
51 public PolygonBounds(Line[] lines)
52 {
53 this();
54 for (Line line : lines) {
55 addPoint(line.getFirstEnd());
56 }
57 addPoint(lines[lines.length - 1].getSecondEnd());
58 }
59
60 /** Adds a new point to the polygon. */
61 public void addPoint(Point p)
62 {
63 addPoint(p.getX(), p.getY());
64 }
65
66 /** Adds a new point to the polygon. */
67 public void addPoint(int x, int y)
68 {
69 _points.add(new Point(x, y));
70 if (_maxX == null || x > _maxX.intValue()) _maxX = new Integer(x);
71 if (_minX == null || x < _minX.intValue()) _minX = new Integer(x);
72 if (_maxY == null || y > _maxY.intValue()) _maxY = new Integer(y);
73 if (_minY == null || y < _minY.intValue()) _minY = new Integer(y);
74 }
75
76 /** Gets the point at the given index in this polygon. */
77 public Point getPoint(int index)
78 {
79 if (0 <= index && index < getPointCount()) return _points.get(index).clone();
80
81 return null;
82 }
83
84 /** Returns the number of points in this polygon. */
85 public int getPointCount()
86 {
87 return _points.size();
88 }
89
90 /** Returns a copy of the points of this polygon as an array. */
91 public Point[] toArray()
92 {
93 Point[] ret = new Point[_points.size()];
94
95 for (int i = 0; i < _points.size(); i++) {
96 ret[i] = _points.get(i).clone();
97 }
98
99 return ret;
100 }
101
102 @Override
103 public PolygonBounds translate(int x, int y)
104 {
105 for (Point p : _points) p.add(x, y);
106
107 if (_maxX != null) _maxX = new Integer(_maxX.intValue() + x);
108 if (_minX != null) _minX = new Integer(_minX.intValue() + x);
109 if (_maxY != null) _maxY = new Integer(_maxY.intValue() + y);
110 if (_minY != null) _minY = new Integer(_minY.intValue() + y);
111
112 return this;
113 }
114
115 /**
116 * Rotates all of the points in this polygon about the given x/y coordinate.
117 *
118 * @param angle Clockwise angle of rotation in radians.
119 */
120 public PolygonBounds rotate(double angle, int x, int y)
121 {
122 // Rotate each point
123 for (Point p : _points) {
124 p.rotate(angle, x, y);
125 }
126
127 // Non-trivial change to mins/maxs, so recalculate
128 recalculateExtents();
129
130 return this;
131 }
132
133 /**
134 * Rotates the polygon around a point.
135 *
136 * @param angle
137 * The clockwise angle to rotate the polygon through (in radians).
138 *
139 * @param cor
140 * The centre of rotation.
141 */
142 public void rotate(double angle, Point cor)
143 {
144 rotate(angle, cor.getX(), cor.getY());
145 }
146
147 /**
148 * Code adapted from java.awt.Polygon.contains(double x, double y).
149 *
150 * @author cts16
151 */
152 @Override
153 public boolean contains(int x, int y) {
154 int npoints = _points.size();
155 if (npoints <= 2) {
156 return false;
157 }
158
159 int hits = 0;
160
161 int lastx = _points.get(npoints - 1).getX();
162 int lasty = _points.get(npoints - 1).getY();
163 int curx, cury;
164
165 // Walk the edges of the polygon
166 for (int i = 0; i < npoints; lastx = curx, lasty = cury, i++) {
167 curx = _points.get(i).getX();
168 cury = _points.get(i).getY();
169
170 if (cury == lasty) {
171 continue;
172 }
173
174 int leftx;
175 if (curx < lastx) {
176 if (x >= lastx) {
177 continue;
178 }
179 leftx = curx;
180 } else {
181 if (x >= curx) {
182 continue;
183 }
184 leftx = lastx;
185 }
186
187 double test1, test2;
188 if (cury < lasty) {
189 if (y < cury || y >= lasty) {
190 continue;
191 }
192 if (x < leftx) {
193 hits++;
194 continue;
195 }
196 test1 = x - curx;
197 test2 = y - cury;
198 } else {
199 if (y < lasty || y >= cury) {
200 continue;
201 }
202 if (x < leftx) {
203 hits++;
204 continue;
205 }
206 test1 = x - lastx;
207 test2 = y - lasty;
208 }
209
210 if (test1 < (test2 / (lasty - cury) * (lastx - curx))) {
211 hits++;
212 }
213 }
214
215 return ((hits & 1) != 0);
216 }
217
218 @Override
219 public int getMaxX() {
220 return _maxX != null ? _maxX : 0;
221 }
222
223 @Override
224 public int getMinX() {
225 return _minX != null ? _minX : 0;
226 }
227
228 @Override
229 public int getMaxY() {
230 return _maxY != null ? _maxY : 0;
231 }
232
233 @Override
234 public int getMinY() {
235 return _minY != null ? _minY : 0;
236 }
237
238 @Override
239 public boolean equals(Bounds other) {
240 if (other instanceof PolygonBounds) {
241 PolygonBounds p = (PolygonBounds) other;
242 if (p._points.size() == this._points.size()) {
243 for (int i = 0; i < p._points.size(); i++) {
244 if (!(p._points.get(i).equals(this._points.get(i)))) return false;
245 }
246 return true;
247 }
248 }
249
250 return false;
251 }
252
253 /** Convenience method to create a polygon in the shape of a diamond. */
254 public static PolygonBounds getDiamond(int width, int height)
255 {
256 PolygonBounds diamond = new PolygonBounds();
257
258 diamond.addPoint(0, -height / 2);
259 diamond.addPoint(width / 2, 0);
260 diamond.addPoint(0, height / 2);
261 diamond.addPoint(-width / 2, 0);
262
263 return diamond;
264 }
265
266 /** Convenience method to create a polygon in the shape of a triangle. */
267 public static PolygonBounds getTriangle(int base, int height)
268 {
269 PolygonBounds triangle = new PolygonBounds();
270
271 triangle.addPoint(0, -height / 2);
272 triangle.addPoint(base / 2, height / 2);
273 triangle.addPoint(-base / 2, height / 2);
274
275 return triangle;
276 }
277
278 @Override
279 public boolean intersects(AxisAlignedBoxBounds other) {
280 // Check if any polygon points are inside the box
281 for (Point p : _points) {
282 if (other.contains(p)) return true;
283 }
284
285 // Check if any of the border lines intersect
286 Line[] boxLines = other.getBorderLines();
287 Line[] polyLines = getBorderLines();
288 if (Line.anyIntersections(boxLines, polyLines, Line.PIXEL_ERROR)) return true;
289
290 // Check if any of the box points are inside the polygon
291 // (only test p1 as p2 is the p1 of another line)
292 for (Line boxLine : boxLines) {
293 if (contains(boxLine.getFirstEnd())) return true;
294 }
295
296 // If all tests fail, no intersection
297 return false;
298 }
299
300 @Override
301 public boolean intersects(CombinationBounds other) {
302 // Defer to the CombinationBounds routine
303 return other.intersects(this);
304 }
305
306 @Override
307 public boolean intersects(EllipticalBounds other) {
308 // Defer to the EllipticalBounds routine
309 return other.intersects(this);
310 }
311
312 @Override
313 public boolean intersects(PolygonBounds other) {
314 // Check if any of the border lines intersect
315 Line[] otherLines = other.getBorderLines();
316 Line[] thisLines = getBorderLines();
317 if (Line.anyIntersections(otherLines, thisLines, Line.PIXEL_ERROR)) return true;
318
319 // If no line intersections, one polygon must be inside the other to intersect
320 return other.contains(thisLines[0].getFirstEnd()) || this.contains(otherLines[0].getFirstEnd());
321 }
322
323 /** Gets an array of the vertices that make up this polygon. */
324 public Point[] getVertices()
325 {
326 Point[] ret = new Point[_points.size()];
327
328 for (int i = 0; i < _points.size(); i++) {
329 ret[i] = _points.get(i).clone();
330 }
331
332 return ret;
333 }
334
335 /** Gets an array of lines that represent the (closed) border of this polygon. */
336 public Line[] getBorderLines()
337 {
338 return getBorderLines(true);
339 }
340
341 /**
342 * Gets an array of lines that represent the border of this polygon.
343 *
344 * @param autoClose
345 * Whether to automatically create a line from the last point to the
346 * first to close this polygon (ignored if first and last point are equal).
347 *
348 * @return
349 * The array of border lines.
350 */
351 public Line[] getBorderLines(boolean autoClose)
352 {
353 // Don't need to autoClose if first and last points are equal
354 autoClose = autoClose && _points.get(0).equals(_points.get(_points.size() - 1));
355
356 // Calculate how many lines we will need to generate
357 int nLines = _points.size();
358 if (!autoClose) nLines--;
359
360 // Generate the lines
361 Line[] lines = new Line[nLines];
362 for (int i = 0; i < nLines; i++) {
363 Point p1 = new Point(_points.get(i));
364 Point p2 = new Point(_points.get((i + 1) % _points.size()));
365 lines[i] = new Line(p1, p2);
366 }
367
368 return lines;
369 }
370
371 @Override
372 public double getArea()
373 {
374 if (_points.size() < 3) return 0.0;
375
376 double area = 0.0;
377
378 for (int i = 0; i < _points.size(); i++) {
379 Point p1 = _points.get(i);
380 Point p2 = _points.get((i + 1) % _points.size());
381 area += p1.getX() * p2.getY() - p2.getX() * p1.getY();
382 }
383
384 return Math.abs(area / 2);
385 }
386
387 @Override
388 public boolean perimeterContains(Point p, double error) {
389 Line[] lines = getBorderLines();
390
391 for (Line line : lines) {
392 if (line.contains(p, error)) return true;
393 }
394
395 return false;
396 }
397
398 @Override
399 public boolean completelyContains(AxisAlignedBoxBounds other)
400 {
401 // Treat the box as a polygon
402 return completelyContains(other.getPolygon());
403 }
404
405 @Override
406 public boolean completelyContains(CombinationBounds other)
407 {
408 // Can't contain nothing
409 if (other == null) return false;
410
411 // Must contain all sub-bounds of the combination bounds
412 for (Bounds bounds : other._bounds) {
413 if (!completelyContains(bounds)) return false;
414 }
415
416 return true;
417 }
418
419 @Override
420 public boolean completelyContains(EllipticalBounds other)
421 {
422 // Can't contain nothing
423 if (other == null) return false;
424
425 // No polygon vertices can be inside the ellipse
426 for (Point vertex : _points) {
427 if (other.contains(vertex)) return false;
428 }
429
430 // No boundary lines can intersect the ellipse
431 for (Line line : getBorderLines()) {
432 if (other.isIntersectedByLine(line)) return false;
433 }
434
435 return true;
436 }
437
438 @Override
439 public boolean completelyContains(PolygonBounds other)
440 {
441 // Can't contain nothing
442 if (other == null) return false;
443
444 // Must contain all points of other
445 for (Point p : other._points) {
446 if (!contains(p)) return false;
447 }
448
449 // Can't be any line intersections
450 Line[] thisLines = getBorderLines();
451 Line[] otherLines = other.getBorderLines();
452 if (Line.anyIntersections(thisLines, otherLines, Line.PIXEL_ERROR)) return false;
453
454 return true;
455 }
456
457 /** Whether the given point is a vertex of this polygon. */
458 public boolean isVertex(Point p)
459 {
460 return _points.contains(p);
461 }
462
463 /** Whether or not this polygon is closed (first and last points are equal). */
464 public boolean isClosed()
465 {
466 return _points.get(0).equals(_points.get(getPointCount() - 1));
467 }
468
469 /** Closes this polygon (adds the first point as the last). */
470 public PolygonBounds close()
471 {
472 if (!isClosed()) addPoint(_points.get(0).clone());
473
474 return this;
475 }
476
477 /** Creates a polygon that represents the same area as a box bounds. */
478 public static PolygonBounds fromBox(AxisAlignedBoxBounds box)
479 {
480 if (box == null) return null;
481
482 // Get the corners of the box
483 Point[] corners = box.getCorners();
484
485 // Add the corners to a new polygon
486 PolygonBounds ret = new PolygonBounds();
487 for (Point corner : corners) ret.addPoint(corner);
488
489 return ret.close();
490 }
491
492 /**
493 * Creates a polygon that approximates the given ellipse.
494 *
495 * @param nSegments
496 * The number of segments the resulting polygon should have. Must be at least 3.
497 */
498 public static PolygonBounds fromEllipse(EllipticalBounds ellipse, int nSegments)
499 {
500 if (ellipse == null || nSegments < 3) return null;
501
502 int centreX = ellipse.getCentre().getX();
503 int centreY = ellipse.getCentre().getY();
504 double xRadius = ellipse.getDiameters().width / 2.0;
505 double yRadius = ellipse.getDiameters().height / 2.0;
506 double radians = 0.0;
507
508 PolygonBounds ret = new PolygonBounds();
509 for (int i = 0; i < nSegments; i++) {
510 int x = (int) (xRadius * Math.cos(radians)) + centreX;
511 int y = (int) (yRadius * Math.sin(radians)) + centreY;
512 ret.addPoint(x, y);
513 radians += (2.0 * Math.PI) / nSegments;
514 }
515
516 return ret.close();
517 }
518
519 @Override
520 public PolygonBounds clone()
521 {
522 return new PolygonBounds(this);
523 }
524
525 /**
526 * Recalculates the min/max x/y coordinates. Normally this should be done incrementally
527 * as points are added, but sometimes you just need to completely redo them.
528 */
529 private void recalculateExtents()
530 {
531 _maxX = _minX = _maxY = _minY = null;
532
533 for (Point p : _points) {
534 if (_maxX == null || p.getX() > _maxX.intValue()) _maxX = new Integer(p.getX());
535 if (_minX == null || p.getX() < _minX.intValue()) _minX = new Integer(p.getX());
536 if (_maxY == null || p.getY() > _maxY.intValue()) _maxY = new Integer(p.getY());
537 if (_minY == null || p.getY() < _minY.intValue()) _minY = new Integer(p.getY());
538 }
539 }
540
541 public String toString() {
542 StringBuilder sb = new StringBuilder("PolygonBounds with: ");
543 sb.append("[MinX: " + getMinX() + "]");
544 sb.append("[MinY: " + getMinY() + "]");
545 sb.append("[MaxX: " + getMaxX() + "]");
546 sb.append("[MaxY: " + getMaxY() + "]");
547 return sb.toString();
548 }
549
550}
Note: See TracBrowser for help on using the repository browser.