1 |
/* Polygon.java -- Class representing a polygon |
/* Polygon.java -- class representing a polygon |
2 |
Copyright (C) 1999 Free Software Foundation, Inc. |
Copyright (C) 1999, 2002 Free Software Foundation, Inc. |
3 |
|
|
4 |
This file is part of GNU Classpath. |
This file is part of GNU Classpath. |
5 |
|
|
38 |
|
|
39 |
package java.awt; |
package java.awt; |
40 |
|
|
41 |
/** |
import java.awt.geom.AffineTransform; |
42 |
* This class represents a polygon |
import java.awt.geom.PathIterator; |
43 |
* |
import java.awt.geom.Point2D; |
44 |
* @author Aaron M. Renn (arenn@urbanophile.com) |
import java.awt.geom.Rectangle2D; |
45 |
*/ |
import java.io.Serializable; |
46 |
public class Polygon implements Shape, java.io.Serializable |
|
47 |
{ |
/** |
48 |
|
* This class represents a polygon, a closed, two-dimensional region in a |
49 |
/* |
* coordinate space. The region is bounded by an arbitrary number of line |
50 |
* Instance Variables |
* segments, between (x,y) coordinate vertices. The polygon has even-odd |
51 |
*/ |
* winding, meaning that a point is inside the shape if it crosses the |
52 |
|
* boundary an odd number of times on the way to infinity. |
53 |
/** |
* |
54 |
* This total number of endpoints |
* <p>There are some public fields; if you mess with them in an inconsistent |
55 |
*/ |
* manner, it is your own fault when you get NullPointerException, |
56 |
public int npoints; |
* ArrayIndexOutOfBoundsException, or invalid results. Also, this class is |
57 |
|
* not threadsafe. |
58 |
/** |
* |
59 |
* The array of X coordinates of endpoints. |
* @author Aaron M. Renn <arenn@urbanophile.com> |
60 |
*/ |
* @author Eric Blake <ebb9@email.byu.edu> |
61 |
public int xpoints[]; |
* @since 1.0 |
62 |
|
* @status updated to 1.4 |
|
/** |
|
|
* The array of Y coordinates of endpoints. |
|
|
*/ |
|
|
public int ypoints[]; |
|
|
|
|
|
/** |
|
|
* The bounding box of this polygon |
|
|
*/ |
|
|
protected Rectangle bounds; |
|
|
|
|
|
/*************************************************************************/ |
|
|
|
|
|
/* |
|
|
* Constructors |
|
63 |
*/ |
*/ |
64 |
|
public class Polygon implements Shape, Serializable |
|
/** |
|
|
* Initializes a new instance of <code>Polygon</code> that is empty. |
|
|
*/ |
|
|
public |
|
|
Polygon() |
|
|
{ |
|
|
xpoints = new int[0]; |
|
|
ypoints = new int[0]; |
|
|
|
|
|
bounds = new Rectangle(0,0,0,0); |
|
|
} |
|
|
|
|
|
/*************************************************************************/ |
|
|
|
|
|
/** |
|
|
* Initializes a new instance of <code>Polygon</code> that has the |
|
|
* specified endpoints. |
|
|
* |
|
|
* @param xpoints The array of X coordinates for this polygon. |
|
|
* @param ypoints The array of Y coordinates for this polygon. |
|
|
* @param npoints The total number of endpoints in this polygon. |
|
|
* |
|
|
* @exception NegativeArraySizeException If <code>npoints</code> is negative. |
|
|
*/ |
|
|
public |
|
|
Polygon(int[] xpoints, int[] ypoints, int npoints) |
|
|
{ |
|
|
if (npoints < 0) |
|
|
throw new NegativeArraySizeException(); |
|
|
|
|
|
this.xpoints = xpoints; |
|
|
this.ypoints = ypoints; |
|
|
this.npoints = npoints; |
|
|
|
|
|
calculateBounds(); |
|
|
} |
|
|
|
|
|
/*************************************************************************/ |
|
|
|
|
|
/* |
|
|
* Instance Methods |
|
|
*/ |
|
|
|
|
|
/** |
|
|
* Calculates the bounding rectangle of this polygon. |
|
|
*/ |
|
|
public void |
|
|
calculateBounds() |
|
|
{ |
|
|
int minx = xpoints[0], maxx = xpoints[0]; |
|
|
int miny = ypoints[0], maxy = ypoints[0]; |
|
|
|
|
|
for (int i = 0; i < npoints; i++) |
|
|
{ |
|
|
if (xpoints[i] < minx) |
|
|
minx = xpoints[i]; |
|
|
|
|
|
if (xpoints[i] > maxx) |
|
|
maxx = xpoints[i]; |
|
|
|
|
|
if (ypoints[i] < miny) |
|
|
miny = ypoints[i]; |
|
|
|
|
|
if (ypoints[i] > maxy) |
|
|
maxy = ypoints[i]; |
|
|
} |
|
|
|
|
|
bounds = new Rectangle(minx, maxy, maxx-minx, maxy-miny); |
|
|
} |
|
|
|
|
|
/*************************************************************************/ |
|
|
|
|
|
/** |
|
|
* Translates the polygon by adding the specified values to all X and Y |
|
|
* coordinates. |
|
|
* |
|
|
* @param dx The amount to add to all X coordinates. |
|
|
* @param dy The amount to add to all Y coordinates. |
|
|
*/ |
|
|
public void |
|
|
translate(int dx, int dy) |
|
65 |
{ |
{ |
66 |
for (int i = 0; i < npoints; i++) |
/** |
67 |
|
* Compatible with JDK 1.0+. |
68 |
|
*/ |
69 |
|
private static final long serialVersionUID = -6460061437900069969L; |
70 |
|
|
71 |
|
/** |
72 |
|
* This total number of endpoints. |
73 |
|
* |
74 |
|
* @serial the number of endpoints, possibly less than the array sizes |
75 |
|
*/ |
76 |
|
public int npoints; |
77 |
|
|
78 |
|
/** |
79 |
|
* The array of X coordinates of endpoints. This should not be null. |
80 |
|
* |
81 |
|
* @see #addPoint(int, int) |
82 |
|
* @serial the x coordinates |
83 |
|
*/ |
84 |
|
public int[] xpoints; |
85 |
|
|
86 |
|
/** |
87 |
|
* The array of Y coordinates of endpoints. This should not be null. |
88 |
|
* |
89 |
|
* @see #addPoint(int, int) |
90 |
|
* @serial the y coordinates |
91 |
|
*/ |
92 |
|
public int[] ypoints; |
93 |
|
|
94 |
|
/** |
95 |
|
* The bounding box of this polygon. This is lazily created and cached, so |
96 |
|
* it must be invalidated after changing points. |
97 |
|
* |
98 |
|
* @see #getBounds() |
99 |
|
* @serial the bounding box, or null |
100 |
|
*/ |
101 |
|
protected Rectangle bounds; |
102 |
|
|
103 |
|
/** |
104 |
|
* Cached flattened version - condense points and parallel lines, so the |
105 |
|
* result has area if there are >= 3 condensed vertices. flat[0] is the |
106 |
|
* number of condensed points, and (flat[odd], flat[odd+1]) form the |
107 |
|
* condensed points. |
108 |
|
* |
109 |
|
* @see #condense() |
110 |
|
* @see #contains(double, double) |
111 |
|
* @see #contains(double, double, double, double) |
112 |
|
*/ |
113 |
|
private transient int[] condensed; |
114 |
|
|
115 |
|
/** |
116 |
|
* Initializes an empty polygon. |
117 |
|
*/ |
118 |
|
public Polygon() |
119 |
|
{ |
120 |
|
// Leave room for growth. |
121 |
|
xpoints = new int[4]; |
122 |
|
ypoints = new int[4]; |
123 |
|
} |
124 |
|
|
125 |
|
/** |
126 |
|
* Create a new polygon with the specified endpoints. |
127 |
|
* |
128 |
|
* @param xpoints the array of X coordinates for this polygon |
129 |
|
* @param ypoints the array of Y coordinates for this polygon |
130 |
|
* @param npoints the total number of endpoints in this polygon |
131 |
|
* @throws NegativeArraySizeException if npoints is negative |
132 |
|
* @throws IndexOutOfBoundsException if npoints exceeds either array |
133 |
|
* @throws NullPointerException if xpoints or ypoints is null |
134 |
|
*/ |
135 |
|
public Polygon(int[] xpoints, int[] ypoints, int npoints) |
136 |
|
{ |
137 |
|
if (npoints < 0) |
138 |
|
throw new NegativeArraySizeException(); |
139 |
|
if (npoints > xpoints.length || npoints > ypoints.length) |
140 |
|
throw new IndexOutOfBoundsException(); |
141 |
|
this.xpoints = xpoints; |
142 |
|
this.ypoints = ypoints; |
143 |
|
this.npoints = npoints; |
144 |
|
} |
145 |
|
|
146 |
|
/** |
147 |
|
* Reset the polygon to be empty. The arrays are left alone, to avoid object |
148 |
|
* allocation, but the number of points is set to 0, and all cached data |
149 |
|
* is discarded. If you are discarding a huge number of points, it may be |
150 |
|
* more efficient to just create a new Polygon. |
151 |
|
* |
152 |
|
* @see #invalidate() |
153 |
|
* @since 1.4 |
154 |
|
*/ |
155 |
|
public void reset() |
156 |
|
{ |
157 |
|
npoints = 0; |
158 |
|
invalidate(); |
159 |
|
} |
160 |
|
|
161 |
|
/** |
162 |
|
* Invalidate or flush all cached data. After direct manipulation of the |
163 |
|
* public member fields, this is necessary to avoid inconsistent results |
164 |
|
* in methods like <code>contains</code>. |
165 |
|
* |
166 |
|
* @see #getBounds() |
167 |
|
* @since 1.4 |
168 |
|
*/ |
169 |
|
public void invalidate() |
170 |
|
{ |
171 |
|
bounds = null; |
172 |
|
condensed = null; |
173 |
|
} |
174 |
|
|
175 |
|
/** |
176 |
|
* Translates the polygon by adding the specified values to all X and Y |
177 |
|
* coordinates. This updates the bounding box, if it has been calculated. |
178 |
|
* |
179 |
|
* @param dx the amount to add to all X coordinates |
180 |
|
* @param dy the amount to add to all Y coordinates |
181 |
|
* @since 1.1 |
182 |
|
*/ |
183 |
|
public void translate(int dx, int dy) |
184 |
|
{ |
185 |
|
int i = npoints; |
186 |
|
while (--i >= 0) |
187 |
|
{ |
188 |
|
xpoints[i] += dx; |
189 |
|
xpoints[i] += dy; |
190 |
|
} |
191 |
|
if (bounds != null) |
192 |
|
{ |
193 |
|
bounds.x += dx; |
194 |
|
bounds.y += dy; |
195 |
|
} |
196 |
|
condensed = null; |
197 |
|
} |
198 |
|
|
199 |
|
/** |
200 |
|
* Adds the specified endpoint to the polygon. This updates the bounding |
201 |
|
* box, if it has been created. |
202 |
|
* |
203 |
|
* @param x the X coordinate of the point to add |
204 |
|
* @param y the Y coordiante of the point to add |
205 |
|
*/ |
206 |
|
public void addPoint(int x, int y) |
207 |
|
{ |
208 |
|
if (npoints + 1 > xpoints.length) |
209 |
|
{ |
210 |
|
int[] newx = new int[npoints + 1]; |
211 |
|
System.arraycopy(xpoints, 0, newx, 0, npoints); |
212 |
|
xpoints = newx; |
213 |
|
} |
214 |
|
if (npoints + 1 > ypoints.length) |
215 |
|
{ |
216 |
|
int[] newy = new int[npoints + 1]; |
217 |
|
System.arraycopy(ypoints, 0, newy, 0, npoints); |
218 |
|
ypoints = newy; |
219 |
|
} |
220 |
|
xpoints[npoints] = x; |
221 |
|
ypoints[npoints] = y; |
222 |
|
npoints++; |
223 |
|
if (bounds != null) |
224 |
|
{ |
225 |
|
if (npoints == 1) |
226 |
|
{ |
227 |
|
bounds.x = x; |
228 |
|
bounds.y = y; |
229 |
|
} |
230 |
|
else |
231 |
|
{ |
232 |
|
if (x < bounds.x) |
233 |
|
{ |
234 |
|
bounds.width += bounds.x - x; |
235 |
|
bounds.x = x; |
236 |
|
} |
237 |
|
else if (x > bounds.x + bounds.width) |
238 |
|
bounds.width = x - bounds.x; |
239 |
|
if (y < bounds.y) |
240 |
|
{ |
241 |
|
bounds.height += bounds.y - y; |
242 |
|
bounds.y = y; |
243 |
|
} |
244 |
|
else if (y > bounds.y + bounds.height) |
245 |
|
bounds.height = y - bounds.y; |
246 |
|
} |
247 |
|
} |
248 |
|
condensed = null; |
249 |
|
} |
250 |
|
|
251 |
|
/** |
252 |
|
* Returns the bounding box of this polygon. This is the smallest |
253 |
|
* rectangle with sides parallel to the X axis that will contain this |
254 |
|
* polygon. |
255 |
|
* |
256 |
|
* @return the bounding box for this polygon |
257 |
|
* @see #getBounds2D() |
258 |
|
* @since 1.1 |
259 |
|
*/ |
260 |
|
public Rectangle getBounds() |
261 |
|
{ |
262 |
|
if (bounds == null) |
263 |
|
{ |
264 |
|
if (npoints == 0) |
265 |
|
return bounds = new Rectangle(); |
266 |
|
int i = npoints - 1; |
267 |
|
int minx = xpoints[i]; |
268 |
|
int maxx = minx; |
269 |
|
int miny = ypoints[i]; |
270 |
|
int maxy = miny; |
271 |
|
while (--i >= 0) |
272 |
|
{ |
273 |
|
int x = xpoints[i]; |
274 |
|
int y = ypoints[i]; |
275 |
|
if (x < minx) |
276 |
|
minx = x; |
277 |
|
else if (x > maxx) |
278 |
|
maxx = x; |
279 |
|
if (y < miny) |
280 |
|
miny = y; |
281 |
|
else if (y > maxy) |
282 |
|
maxy = y; |
283 |
|
} |
284 |
|
bounds = new Rectangle(minx, maxy, maxx - minx, maxy - miny); |
285 |
|
} |
286 |
|
return bounds; |
287 |
|
} |
288 |
|
|
289 |
|
/** |
290 |
|
* Returns the bounding box of this polygon. This is the smallest |
291 |
|
* rectangle with sides parallel to the X axis that will contain this |
292 |
|
* polygon. |
293 |
|
* |
294 |
|
* @return the bounding box for this polygon |
295 |
|
* @see #getBounds2D() |
296 |
|
* @deprecated use {@link #getBounds()} instead |
297 |
|
*/ |
298 |
|
public Rectangle getBoundingBox() |
299 |
|
{ |
300 |
|
return getBounds(); |
301 |
|
} |
302 |
|
|
303 |
|
/** |
304 |
|
* Tests whether or not the specified point is inside this polygon. |
305 |
|
* |
306 |
|
* @param p the point to test |
307 |
|
* @return true if the point is inside this polygon |
308 |
|
* @throws NullPointerException if p is null |
309 |
|
* @see #contains(double, double) |
310 |
|
*/ |
311 |
|
public boolean contains(Point p) |
312 |
|
{ |
313 |
|
return contains(p.getX(), p.getY()); |
314 |
|
} |
315 |
|
|
316 |
|
/** |
317 |
|
* Tests whether or not the specified point is inside this polygon. |
318 |
|
* |
319 |
|
* @param x the X coordinate of the point to test |
320 |
|
* @param y the Y coordinate of the point to test |
321 |
|
* @return true if the point is inside this polygon |
322 |
|
* @see #contains(double, double) |
323 |
|
* @since 1.1 |
324 |
|
*/ |
325 |
|
public boolean contains(int x, int y) |
326 |
|
{ |
327 |
|
return contains((double) x, (double) y); |
328 |
|
} |
329 |
|
|
330 |
|
/** |
331 |
|
* Tests whether or not the specified point is inside this polygon. |
332 |
|
* |
333 |
|
* @param x the X coordinate of the point to test |
334 |
|
* @param y the Y coordinate of the point to test |
335 |
|
* @return true if the point is inside this polygon |
336 |
|
* @see #contains(double, double) |
337 |
|
* @deprecated use {@link #contains(int, int)} instead |
338 |
|
*/ |
339 |
|
public boolean inside(int x, int y) |
340 |
|
{ |
341 |
|
return contains((double) x, (double) y); |
342 |
|
} |
343 |
|
|
344 |
|
/** |
345 |
|
* Returns a high-precision bounding box of this polygon. This is the |
346 |
|
* smallest rectangle with sides parallel to the X axis that will contain |
347 |
|
* this polygon. |
348 |
|
* |
349 |
|
* @return the bounding box for this polygon |
350 |
|
* @see #getBounds() |
351 |
|
* @since 1.2 |
352 |
|
*/ |
353 |
|
public Rectangle2D getBounds2D() |
354 |
|
{ |
355 |
|
// For polygons, the integer version is exact! |
356 |
|
return getBounds(); |
357 |
|
} |
358 |
|
|
359 |
|
/** |
360 |
|
* Tests whether or not the specified point is inside this polygon. |
361 |
|
* |
362 |
|
* @param x the X coordinate of the point to test |
363 |
|
* @param y the Y coordinate of the point to test |
364 |
|
* @return true if the point is inside this polygon |
365 |
|
* @since 1.2 |
366 |
|
*/ |
367 |
|
public boolean contains(double x, double y) |
368 |
|
{ |
369 |
|
// First, the obvious bounds checks. |
370 |
|
if (! condense() || ! getBounds().contains(x, y)) |
371 |
|
return false; |
372 |
|
// A point is contained if a ray to (-inf, y) crosses an odd number |
373 |
|
// of segments. This must obey the semantics of Shape when the point is |
374 |
|
// exactly on a segment or vertex. Note that we are guaranteed that the |
375 |
|
// condensed polygon has area, and no two segments with identical slope. |
376 |
|
int intersections = 0; |
377 |
|
int limit = condensed[0]; |
378 |
|
int curx = condensed[(limit << 1) - 1]; |
379 |
|
int cury = condensed[limit << 1]; |
380 |
|
for (int i = 1; i <= limit; i++) |
381 |
|
{ |
382 |
|
int priorx = curx; |
383 |
|
int priory = cury; |
384 |
|
curx = condensed[(i << 1) - 1]; |
385 |
|
cury = condensed[i << 1]; |
386 |
|
if ((priorx > x && curx > x) // Left of segment, or NaN. |
387 |
|
|| (priory > y && cury > y) // Below segment, or NaN. |
388 |
|
|| (priory < y && cury < y)) // Above segment. |
389 |
|
continue; |
390 |
|
if (priory == cury) // Horizontal segment, y == cury == priory |
391 |
|
{ |
392 |
|
if (priorx < x && curx < x) // Right of segment. |
393 |
|
{ |
394 |
|
intersections++; |
395 |
|
continue; |
396 |
|
} |
397 |
|
// Did we approach this segment from above or below? |
398 |
|
// This mess is necessary to obey rules of Shape. |
399 |
|
priory = condensed[((limit + i - 2) % limit) << 1]; |
400 |
|
boolean above = priory > cury; |
401 |
|
if ((curx == x && (curx > priorx || above)) |
402 |
|
|| (priorx == x && (curx < priorx || ! above)) |
403 |
|
|| (curx > priorx && ! above) || above) |
404 |
|
intersections++; |
405 |
|
continue; |
406 |
|
} |
407 |
|
if (priorx == x && priory == y) // On prior vertex. |
408 |
|
continue; |
409 |
|
if (priorx == curx // Vertical segment. |
410 |
|
|| (priorx < x && curx < x)) // Right of segment. |
411 |
|
{ |
412 |
|
intersections++; |
413 |
|
continue; |
414 |
|
} |
415 |
|
// The point is inside the segment's bounding box, compare slopes. |
416 |
|
double slopeseg = (double) (cury - priory) / (curx - priorx); |
417 |
|
double slopepoint = (double) (y - priory) / (x - priorx); |
418 |
|
if ((slopeseg > 0 && slopeseg > slopepoint) |
419 |
|
|| slopeseg < slopepoint) |
420 |
|
intersections++; |
421 |
|
} |
422 |
|
return (intersections & 1) != 0; |
423 |
|
} |
424 |
|
|
425 |
|
/** |
426 |
|
* Tests whether or not the specified point is inside this polygon. |
427 |
|
* |
428 |
|
* @param p the point to test |
429 |
|
* @return true if the point is inside this polygon |
430 |
|
* @throws NullPointerException if p is null |
431 |
|
* @see #contains(double, double) |
432 |
|
* @since 1.2 |
433 |
|
*/ |
434 |
|
public boolean contains(Point2D p) |
435 |
|
{ |
436 |
|
return contains(p.getX(), p.getY()); |
437 |
|
} |
438 |
|
|
439 |
|
/** |
440 |
|
* Test if a high-precision rectangle intersects the shape. This is true |
441 |
|
* if any point in the rectangle is in the shape. This implementation is |
442 |
|
* precise. |
443 |
|
* |
444 |
|
* @param x the x coordinate of the rectangle |
445 |
|
* @param y the y coordinate of the rectangle |
446 |
|
* @param w the width of the rectangle, treated as point if negative |
447 |
|
* @param h the height of the rectangle, treated as point if negative |
448 |
|
* @return true if the rectangle intersects this shape |
449 |
|
* @since 1.2 |
450 |
|
*/ |
451 |
|
public boolean intersects(double x, double y, double w, double h) |
452 |
|
{ |
453 |
|
// First, the obvious bounds checks. |
454 |
|
if (w <= 0 || h <= 0 || npoints == 0 || |
455 |
|
! getBounds().intersects(x, y, w, h)) |
456 |
|
return false; // Disjoint bounds. |
457 |
|
if ((x <= bounds.x && x + w >= bounds.x + bounds.width |
458 |
|
&& y <= bounds.y && y + h >= bounds.y + bounds.height) |
459 |
|
|| contains(x, y)) |
460 |
|
return true; // Rectangle contains the polygon, or one point matches. |
461 |
|
// If any vertex is in the rectangle, the two might intersect. |
462 |
|
int curx = 0; |
463 |
|
int cury = 0; |
464 |
|
for (int i = 0; i < npoints; i++) |
465 |
|
{ |
466 |
|
curx = xpoints[i]; |
467 |
|
cury = ypoints[i]; |
468 |
|
if (curx >= x && curx < x + w && cury >= y && cury < y + h |
469 |
|
&& contains(curx, cury)) // Boundary check necessary. |
470 |
|
return true; |
471 |
|
} |
472 |
|
// Finally, if at least one of the four bounding lines intersect any |
473 |
|
// segment of the polygon, return true. Be careful of the semantics of |
474 |
|
// Shape; coinciding lines do not necessarily return true. |
475 |
|
for (int i = 0; i < npoints; i++) |
476 |
|
{ |
477 |
|
int priorx = curx; |
478 |
|
int priory = cury; |
479 |
|
curx = xpoints[i]; |
480 |
|
cury = ypoints[i]; |
481 |
|
if (priorx == curx) // Vertical segment. |
482 |
|
{ |
483 |
|
if (curx < x || curx >= x + w) // Outside rectangle. |
484 |
|
continue; |
485 |
|
if ((cury >= y + h && priory <= y) |
486 |
|
|| (cury <= y && priory >= y + h)) |
487 |
|
return true; // Bisects rectangle. |
488 |
|
continue; |
489 |
|
} |
490 |
|
if (priory == cury) // Horizontal segment. |
491 |
|
{ |
492 |
|
if (cury < y || cury >= y + h) // Outside rectangle. |
493 |
|
continue; |
494 |
|
if ((curx >= x + w && priorx <= x) |
495 |
|
|| (curx <= x && priorx >= x + w)) |
496 |
|
return true; // Bisects rectangle. |
497 |
|
continue; |
498 |
|
} |
499 |
|
// Slanted segment. |
500 |
|
double slope = (double) (cury - priory) / (curx - priorx); |
501 |
|
double intersect = slope * (x - curx) + cury; |
502 |
|
if (intersect > y && intersect < y + h) // Intersects left edge. |
503 |
|
return true; |
504 |
|
intersect = slope * (x + w - curx) + cury; |
505 |
|
if (intersect > y && intersect < y + h) // Intersects right edge. |
506 |
|
return true; |
507 |
|
intersect = (y - cury) / slope + curx; |
508 |
|
if (intersect > x && intersect < x + w) // Intersects bottom edge. |
509 |
|
return true; |
510 |
|
intersect = (y + h - cury) / slope + cury; |
511 |
|
if (intersect > x && intersect < x + w) // Intersects top edge. |
512 |
|
return true; |
513 |
|
} |
514 |
|
return false; |
515 |
|
} |
516 |
|
|
517 |
|
/** |
518 |
|
* Test if a high-precision rectangle intersects the shape. This is true |
519 |
|
* if any point in the rectangle is in the shape. This implementation is |
520 |
|
* precise. |
521 |
|
* |
522 |
|
* @param r the rectangle |
523 |
|
* @return true if the rectangle intersects this shape |
524 |
|
* @throws NullPointerException if r is null |
525 |
|
* @see #intersects(double, double, double, double) |
526 |
|
* @since 1.2 |
527 |
|
*/ |
528 |
|
public boolean intersects(Rectangle2D r) |
529 |
|
{ |
530 |
|
return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); |
531 |
|
} |
532 |
|
|
533 |
|
/** |
534 |
|
* Test if a high-precision rectangle lies completely in the shape. This is |
535 |
|
* true if all points in the rectangle are in the shape. This implementation |
536 |
|
* is precise. |
537 |
|
* |
538 |
|
* @param x the x coordinate of the rectangle |
539 |
|
* @param y the y coordinate of the rectangle |
540 |
|
* @param w the width of the rectangle, treated as point if negative |
541 |
|
* @param h the height of the rectangle, treated as point if negative |
542 |
|
* @return true if the rectangle is contained in this shape |
543 |
|
* @since 1.2 |
544 |
|
*/ |
545 |
|
public boolean contains(double x, double y, double w, double h) |
546 |
|
{ |
547 |
|
// First, the obvious bounds checks. |
548 |
|
if (w <= 0 || h <= 0 || ! contains(x, y) |
549 |
|
|| ! bounds.contains(x, y, w, h)) |
550 |
|
return false; |
551 |
|
// Now, if any of the four bounding lines intersects a polygon segment, |
552 |
|
// return false. The previous check had the side effect of setting |
553 |
|
// the condensed array, which we use. Be careful of the semantics of |
554 |
|
// Shape; coinciding lines do not necessarily return false. |
555 |
|
int limit = condensed[0]; |
556 |
|
int curx = condensed[(limit << 1) - 1]; |
557 |
|
int cury = condensed[limit << 1]; |
558 |
|
for (int i = 1; i <= limit; i++) |
559 |
|
{ |
560 |
|
int priorx = curx; |
561 |
|
int priory = cury; |
562 |
|
curx = condensed[(i << 1) - 1]; |
563 |
|
cury = condensed[i << 1]; |
564 |
|
if (curx > x && curx < x + w && cury > y && cury < y + h) |
565 |
|
return false; // Vertex is in rectangle. |
566 |
|
if (priorx == curx) // Vertical segment. |
567 |
|
{ |
568 |
|
if (curx < x || curx > x + w) // Outside rectangle. |
569 |
|
continue; |
570 |
|
if ((cury >= y + h && priory <= y) |
571 |
|
|| (cury <= y && priory >= y + h)) |
572 |
|
return false; // Bisects rectangle. |
573 |
|
continue; |
574 |
|
} |
575 |
|
if (priory == cury) // Horizontal segment. |
576 |
|
{ |
577 |
|
if (cury < y || cury > y + h) // Outside rectangle. |
578 |
|
continue; |
579 |
|
if ((curx >= x + w && priorx <= x) |
580 |
|
|| (curx <= x && priorx >= x + w)) |
581 |
|
return false; // Bisects rectangle. |
582 |
|
continue; |
583 |
|
} |
584 |
|
// Slanted segment. |
585 |
|
double slope = (double) (cury - priory) / (curx - priorx); |
586 |
|
double intersect = slope * (x - curx) + cury; |
587 |
|
if (intersect > y && intersect < y + h) // Intersects left edge. |
588 |
|
return false; |
589 |
|
intersect = slope * (x + w - curx) + cury; |
590 |
|
if (intersect > y && intersect < y + h) // Intersects right edge. |
591 |
|
return false; |
592 |
|
intersect = (y - cury) / slope + curx; |
593 |
|
if (intersect > x && intersect < x + w) // Intersects bottom edge. |
594 |
|
return false; |
595 |
|
intersect = (y + h - cury) / slope + cury; |
596 |
|
if (intersect > x && intersect < x + w) // Intersects top edge. |
597 |
|
return false; |
598 |
|
} |
599 |
|
return true; |
600 |
|
} |
601 |
|
|
602 |
|
/** |
603 |
|
* Test if a high-precision rectangle lies completely in the shape. This is |
604 |
|
* true if all points in the rectangle are in the shape. This implementation |
605 |
|
* is precise. |
606 |
|
* |
607 |
|
* @param r the rectangle |
608 |
|
* @return true if the rectangle is contained in this shape |
609 |
|
* @throws NullPointerException if r is null |
610 |
|
* @see #contains(double, double, double, double) |
611 |
|
* @since 1.2 |
612 |
|
*/ |
613 |
|
public boolean contains(Rectangle2D r) |
614 |
|
{ |
615 |
|
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); |
616 |
|
} |
617 |
|
|
618 |
|
/** |
619 |
|
* Return an iterator along the shape boundary. If the optional transform |
620 |
|
* is provided, the iterator is transformed accordingly. Each call returns |
621 |
|
* a new object, independent from others in use. This class is not |
622 |
|
* threadsafe to begin with, so the path iterator is not either. |
623 |
|
* |
624 |
|
* @param transform an optional transform to apply to the iterator |
625 |
|
* @return a new iterator over the boundary |
626 |
|
* @since 1.2 |
627 |
|
*/ |
628 |
|
public PathIterator getPathIterator(final AffineTransform transform) |
629 |
|
{ |
630 |
|
return new PathIterator() |
631 |
{ |
{ |
632 |
xpoints[i] += dx; |
/** The current vertex of iteration. */ |
633 |
xpoints[i] += dy; |
private int vertex; |
|
} |
|
|
|
|
|
calculateBounds(); |
|
|
} |
|
|
|
|
|
/*************************************************************************/ |
|
|
|
|
|
/** |
|
|
* Adds the specified endpoint to the polygon. |
|
|
* |
|
|
* @param x The X coordinate of the point to add. |
|
|
* @param y The Y coordiante of the point to add. |
|
|
*/ |
|
|
public void |
|
|
addPoint(int x, int y) |
|
|
{ |
|
|
int newxpoints[] = new int[npoints + 1]; |
|
|
int newypoints[] = new int[npoints + 1]; |
|
|
|
|
|
System.arraycopy(xpoints, 0, newxpoints, 0, npoints); |
|
|
System.arraycopy(ypoints, 0, newypoints, 0, npoints); |
|
|
|
|
|
newxpoints[npoints] = x; |
|
|
newypoints[npoints] = y; |
|
|
|
|
|
xpoints = newxpoints; |
|
|
ypoints = newypoints; |
|
|
++npoints; |
|
|
} |
|
|
|
|
|
/*************************************************************************/ |
|
|
|
|
|
/** |
|
|
* Returns the bounding box of this polygon. This is the smallest |
|
|
* rectangle with sides parallel to the X axis that will contain this |
|
|
* polygon. |
|
|
* |
|
|
* @return The bounding box for this polygon. |
|
|
*/ |
|
|
public Rectangle |
|
|
getBounds() |
|
|
{ |
|
|
return(bounds); |
|
|
} |
|
|
|
|
|
/*************************************************************************/ |
|
|
|
|
|
/** |
|
|
* Returns the bounding box of this polygon. This is the smallest |
|
|
* rectangle with sides parallel to the X axis that will contain this |
|
|
* polygon. |
|
|
* |
|
|
* @return The bounding box for this polygon. |
|
|
* |
|
|
* @deprecated This method has been replaced by <code>getBounds()</code>. |
|
|
*/ |
|
|
public Rectangle |
|
|
getBoundingBox() |
|
|
{ |
|
|
return(bounds); |
|
|
} |
|
|
|
|
|
/*************************************************************************/ |
|
|
|
|
|
/** |
|
|
* Tests whether or not the specified point is inside this polygon. |
|
|
* |
|
|
* @param x The X coordinate of the point to test. |
|
|
* @param y the Y coordinate of the point to test. |
|
|
* |
|
|
* @return <code>true</code> if the point is inside this polygon, |
|
|
* <code>false</code> otherwise. |
|
|
*/ |
|
|
public boolean |
|
|
contains(int x, int y) |
|
|
{ |
|
|
// Is inside bounding box. |
|
|
if (!bounds.contains(x, y)) |
|
|
return(false); |
|
|
|
|
|
int sign = 0; |
|
|
for (int i = 0; i < npoints; i ++) |
|
|
{ |
|
|
int nx = xpoints[(i + 1) % npoints] - xpoints[i]; |
|
|
int ny = ypoints[(i + 1) % npoints] - ypoints[i]; |
|
|
|
|
|
int dx = x - xpoints[i]; |
|
|
int dy = y - ypoints[i]; |
|
|
|
|
|
int val = (dx*nx) + (dy*nx); |
|
|
|
|
|
if (sign == 0) |
|
|
{ |
|
|
if (val < 1) |
|
|
sign = -1; |
|
|
else if (val > 1) |
|
|
sign = 1; |
|
|
} |
|
|
|
|
|
if ((val > 1) && (sign < 1)) |
|
|
return(false); |
|
|
if ((val < 1) && (sign > 1)) |
|
|
return(false); |
|
|
} |
|
|
|
|
|
return(true); |
|
|
} |
|
|
|
|
|
/*************************************************************************/ |
|
|
|
|
|
/** |
|
|
* Tests whether or not the specified point is inside this polygon. |
|
|
* |
|
|
* @param x The X coordinate of the point to test. |
|
|
* @param y the Y coordinate of the point to test. |
|
|
* |
|
|
* @return <code>true</code> if the point is inside this polygon, |
|
|
* <code>false</code> otherwise. |
|
|
* |
|
|
* @deprecated This method has been replaced by <code>contains()</code>. |
|
|
*/ |
|
|
public boolean |
|
|
inside(int x, int y) |
|
|
{ |
|
|
return(contains(x, y)); |
|
|
} |
|
|
|
|
|
} // class Polygon |
|
634 |
|
|
635 |
|
public int getWindingRule() |
636 |
|
{ |
637 |
|
return WIND_EVEN_ODD; |
638 |
|
} |
639 |
|
|
640 |
|
public boolean isDone() |
641 |
|
{ |
642 |
|
return vertex >= npoints; |
643 |
|
} |
644 |
|
|
645 |
|
public void next() |
646 |
|
{ |
647 |
|
vertex++; |
648 |
|
} |
649 |
|
|
650 |
|
public int currentSegment(float[] coords) |
651 |
|
{ |
652 |
|
if (vertex >= npoints) |
653 |
|
return SEG_CLOSE; |
654 |
|
coords[0] = xpoints[vertex]; |
655 |
|
coords[1] = ypoints[vertex]; |
656 |
|
if (transform != null) |
657 |
|
transform.transform(coords, 0, coords, 0, 1); |
658 |
|
return SEG_LINETO; |
659 |
|
} |
660 |
|
|
661 |
|
public int currentSegment(double[] coords) |
662 |
|
{ |
663 |
|
if (vertex >= npoints) |
664 |
|
return SEG_CLOSE; |
665 |
|
coords[0] = xpoints[vertex]; |
666 |
|
coords[1] = ypoints[vertex]; |
667 |
|
if (transform != null) |
668 |
|
transform.transform(coords, 0, coords, 0, 1); |
669 |
|
return SEG_LINETO; |
670 |
|
} |
671 |
|
}; |
672 |
|
} |
673 |
|
|
674 |
|
/** |
675 |
|
* Return an iterator along the flattened version of the shape boundary. |
676 |
|
* Since rectangles are already flat, the flatness parameter is ignored, and |
677 |
|
* the resulting iterator only has SEG_LINETO and SEG_CLOSE points. If the |
678 |
|
* optional transform is provided, the iterator is transformed accordingly. |
679 |
|
* Each call returns a new object, independent from others in use. This |
680 |
|
* class is not threadsafe to begin with, so the path iterator is not either. |
681 |
|
* |
682 |
|
* @param transform an optional transform to apply to the iterator |
683 |
|
* @param double the maximum distance for deviation from the real boundary |
684 |
|
* @return a new iterator over the boundary |
685 |
|
* @since 1.2 |
686 |
|
*/ |
687 |
|
public PathIterator getPathIterator(AffineTransform transform, |
688 |
|
double flatness) |
689 |
|
{ |
690 |
|
return getPathIterator(transform); |
691 |
|
} |
692 |
|
|
693 |
|
/** |
694 |
|
* Helper for contains, which caches a condensed version of the polygon. |
695 |
|
* This condenses all colinear points, so that consecutive segments in |
696 |
|
* the condensed version always have different slope. |
697 |
|
* |
698 |
|
* @return true if the condensed polygon has area |
699 |
|
* @see #condensed |
700 |
|
* @see #contains(double, double) |
701 |
|
*/ |
702 |
|
private boolean condense() |
703 |
|
{ |
704 |
|
if (npoints <= 2) |
705 |
|
return false; |
706 |
|
if (condensed != null) |
707 |
|
return condensed[0] > 2; |
708 |
|
condensed = new int[npoints * 2 + 1]; |
709 |
|
int curx = xpoints[npoints - 1]; |
710 |
|
int cury = ypoints[npoints - 1]; |
711 |
|
double curslope = Double.NaN; |
712 |
|
int count = 0; |
713 |
|
outer: |
714 |
|
for (int i = 0; i < npoints; i++) |
715 |
|
{ |
716 |
|
int priorx = curx; |
717 |
|
int priory = cury; |
718 |
|
double priorslope = curslope; |
719 |
|
curx = xpoints[i]; |
720 |
|
cury = ypoints[i]; |
721 |
|
while (curx == priorx && cury == priory) |
722 |
|
{ |
723 |
|
if (++i == npoints) |
724 |
|
break outer; |
725 |
|
curx = xpoints[i]; |
726 |
|
cury = ypoints[i]; |
727 |
|
} |
728 |
|
curslope = (curx == priorx ? Double.POSITIVE_INFINITY |
729 |
|
: (double) (cury - priory) / (curx - priorx)); |
730 |
|
if (priorslope == curslope) |
731 |
|
{ |
732 |
|
if (count > 1 && condensed[(count << 1) - 3] == curx |
733 |
|
&& condensed[(count << 1) - 2] == cury) |
734 |
|
{ |
735 |
|
count--; |
736 |
|
continue; |
737 |
|
} |
738 |
|
} |
739 |
|
else |
740 |
|
count++; |
741 |
|
condensed[(count << 1) - 1] = curx; |
742 |
|
condensed[count << 1] = cury; |
743 |
|
} |
744 |
|
condensed[0] = count; |
745 |
|
return count > 2; |
746 |
|
} |
747 |
|
} // class Polygon |