root / trunk / libraries / libTopology / src / com / graphbuilder / curve / ShapeMultiPath.java @ 37940
History | View | Annotate | Download (9.16 KB)
1 |
package com.graphbuilder.curve; |
---|---|
2 |
|
3 |
import java.awt.Rectangle; |
4 |
import java.awt.Shape; |
5 |
import java.awt.geom.AffineTransform; |
6 |
import java.awt.geom.PathIterator; |
7 |
import java.awt.geom.Point2D; |
8 |
import java.awt.geom.Rectangle2D; |
9 |
|
10 |
import com.graphbuilder.geom.Geom; |
11 |
|
12 |
/**
|
13 |
The ShapeMultiPath is-a MultiPath and implements the java.awt.Shape interface.
|
14 |
Here is an example of how to use a ShapeMultiPath:
|
15 |
|
16 |
<pre>
|
17 |
ControlPath cp = new ControlPath();
|
18 |
cp.addPoint(...); // add points
|
19 |
Curve c = new BezierCurve(cp, new GroupIterator("0:n-1"));
|
20 |
|
21 |
ShapeMultiPath smp = new ShapeMultiPath();
|
22 |
c.appendTo(smp);
|
23 |
|
24 |
Graphics2D g = ...;
|
25 |
g.draw(smp);
|
26 |
|
27 |
</pre>
|
28 |
*/
|
29 |
public class ShapeMultiPath extends MultiPath implements Shape { |
30 |
|
31 |
private int windingRule = PathIterator.WIND_EVEN_ODD; |
32 |
private int ai0 = 0; |
33 |
private int ai1 = 1; |
34 |
|
35 |
/**
|
36 |
Constructs a new ShapeMultiPath with a dimension of 2.
|
37 |
*/
|
38 |
public ShapeMultiPath() {
|
39 |
super(2); |
40 |
} |
41 |
|
42 |
/**
|
43 |
Constructs a new ShapeMultiPath with the specified dimension requirement.
|
44 |
|
45 |
@throws IllegalArgumentException If the specified dimension is less than 2.
|
46 |
*/
|
47 |
public ShapeMultiPath(int dimension) { |
48 |
super(dimension);
|
49 |
|
50 |
if (dimension < 2) |
51 |
throw new IllegalArgumentException("dimension >= 2 required"); |
52 |
} |
53 |
|
54 |
/**
|
55 |
The basis vectors specify which index corresponds to the x-axis and which index
|
56 |
corresponds to the y-axis. The value of the x-axis is at index location 0 and the
|
57 |
value of the y-axis is at index location 1.
|
58 |
|
59 |
@throws IllegalArgumentException If the axis values are less than 0 or greater than or
|
60 |
equal to the dimension.
|
61 |
|
62 |
@see #getBasisVectors()
|
63 |
*/
|
64 |
public void setBasisVectors(int[] b) { |
65 |
int b0 = b[0]; |
66 |
int b1 = b[1]; |
67 |
|
68 |
int dimension = getDimension();
|
69 |
|
70 |
if (b0 < 0 || b1 < 0 || b0 >= dimension || b1 >= dimension) |
71 |
throw new IllegalArgumentException("basis vectors must be >= 0 and < dimension"); |
72 |
|
73 |
ai0 = b0; |
74 |
ai1 = b1; |
75 |
} |
76 |
|
77 |
/**
|
78 |
Returns a new integer array with the basis vectors. The default basis vectors are {0, 1}.
|
79 |
|
80 |
@see #setBasisVectors(int[])
|
81 |
*/
|
82 |
public int[] getBasisVectors() { |
83 |
return new int[] { ai0, ai1 }; |
84 |
} |
85 |
|
86 |
/**
|
87 |
Returns the minimum distance^2 from the specified point to the line segments of this multi-path.
|
88 |
*/
|
89 |
public double getDistSq(double x, double y) { |
90 |
int n = getNumPoints();
|
91 |
|
92 |
if (n == 0) |
93 |
return Double.MAX_VALUE; |
94 |
|
95 |
double[] p = get(0); |
96 |
|
97 |
double x2 = p[ai0];
|
98 |
double y2 = p[ai1];
|
99 |
double dist = Double.MAX_VALUE; |
100 |
|
101 |
for (int i = 1; i < n; i++) { |
102 |
p = get(i); |
103 |
double x1 = p[ai0];
|
104 |
double y1 = p[ai1];
|
105 |
|
106 |
if (getType(i) == MultiPath.LINE_TO) {
|
107 |
double d = Geom.ptSegDistSq(x1, y1, x2, y2, x, y, null); |
108 |
if (d < dist)
|
109 |
dist = d; |
110 |
} |
111 |
|
112 |
x2 = x1; |
113 |
y2 = y1; |
114 |
} |
115 |
|
116 |
return dist;
|
117 |
} |
118 |
|
119 |
|
120 |
//------------------------------------------------------------------------------------------
|
121 |
// methods for Shape interface:
|
122 |
|
123 |
|
124 |
/**
|
125 |
Returns the value of the winding rule. The default value is PathIterator.WIND_EVEN_ODD.
|
126 |
|
127 |
@see #setWindingRule(int)
|
128 |
*/
|
129 |
public int getWindingRule() { |
130 |
return windingRule;
|
131 |
} |
132 |
|
133 |
/**
|
134 |
Sets the winding rule. The winding rule can either by PathIterator.WIND_EVEN_ODD or
|
135 |
PathIterator.WIND_NON_ZERO, otherwise an IllegalArgumentException is thrown.
|
136 |
*/
|
137 |
public void setWindingRule(int rule) { |
138 |
if (rule != PathIterator.WIND_EVEN_ODD && rule != PathIterator.WIND_NON_ZERO) |
139 |
throw new IllegalArgumentException("winding rule must be WIND_EVEN_ODD or WIND_NON_ZERO"); |
140 |
|
141 |
windingRule = rule; |
142 |
} |
143 |
|
144 |
/**
|
145 |
Returns a new PathIterator object.
|
146 |
*/
|
147 |
public PathIterator getPathIterator(AffineTransform at) { |
148 |
return new ShapeMultiPathIterator(this, at); |
149 |
} |
150 |
|
151 |
/**
|
152 |
Returns a new PathIterator object. The flatness parameter is ignored since a multi-path, by
|
153 |
definition, is already flat.
|
154 |
*/
|
155 |
public PathIterator getPathIterator(AffineTransform at, double flatness) { |
156 |
return new ShapeMultiPathIterator(this, at); |
157 |
} |
158 |
|
159 |
//---------------------------------------------------------------
|
160 |
|
161 |
/**
|
162 |
See the getBounds2D() method.
|
163 |
|
164 |
@see #getBounds2D()
|
165 |
*/
|
166 |
public Rectangle getBounds() { |
167 |
Rectangle2D r = getBounds2D();
|
168 |
if (r == null) return null; |
169 |
return r.getBounds();
|
170 |
} |
171 |
|
172 |
/**
|
173 |
Computes the bounding box of the points. When computing the bounding box, a point is considered if it is
|
174 |
of type LINE_TO or it is of type MOVE_TO and the next point is of type LINE_TO. A value of null is
|
175 |
returned if there is not enough data to define a bounding box.
|
176 |
*/
|
177 |
public Rectangle2D getBounds2D() { |
178 |
int n = getNumPoints();
|
179 |
|
180 |
double x1 = Double.MAX_VALUE; |
181 |
double y1 = Double.MAX_VALUE; |
182 |
double x2 = -Double.MAX_VALUE; |
183 |
double y2 = -Double.MAX_VALUE; |
184 |
|
185 |
boolean defined = false; |
186 |
|
187 |
for (int i = 0; i < n; i++) { |
188 |
double[] p = get(i); |
189 |
|
190 |
boolean b = false; |
191 |
|
192 |
if (getType(i) == MultiPath.MOVE_TO) {
|
193 |
if (i < n - 1 && getType(i+1) == MultiPath.LINE_TO) |
194 |
b = true;
|
195 |
} |
196 |
else {
|
197 |
b = true;
|
198 |
} |
199 |
|
200 |
if (b) {
|
201 |
defined = true;
|
202 |
if (p[ai0] < x1) x1 = p[ai0];
|
203 |
if (p[ai1] < y1) y1 = p[ai1];
|
204 |
if (p[ai0] > x2) x2 = p[ai0];
|
205 |
if (p[ai1] > y2) y2 = p[ai1];
|
206 |
} |
207 |
} |
208 |
|
209 |
if (!defined)
|
210 |
return null; |
211 |
|
212 |
return new Rectangle2D.Double(x1, y1, x2 - x1, y2 - y1); |
213 |
} |
214 |
|
215 |
|
216 |
//---------------------------------------------------------------
|
217 |
|
218 |
/**
|
219 |
Returns true if the point is contained inside the shape. Otherwise false is returned.
|
220 |
*/
|
221 |
public boolean contains(double x, double y) { |
222 |
int cross = org.gvsig.geoutils.sun.awt.geom.Curve.pointCrossingsForPath(getPathIterator(null), x, y); |
223 |
|
224 |
if (windingRule == PathIterator.WIND_NON_ZERO) |
225 |
return cross != 0; |
226 |
|
227 |
return (cross & 1) != 0; |
228 |
} |
229 |
|
230 |
/**
|
231 |
See the contains(x, y) method.
|
232 |
|
233 |
@see #contains(double,double)
|
234 |
*/
|
235 |
public boolean contains(Point2D p) { |
236 |
return contains(p.getX(), p.getY());
|
237 |
} |
238 |
|
239 |
/**
|
240 |
Returns true only if the shape contains all points of the rectangle. First, if any of
|
241 |
the four corners is not contained in the shape then false is returned. Now we know
|
242 |
that all four corners are inside the shape. Next, we check to see if any line segment
|
243 |
of this shape intersects any of the 4 line segments formed by the rectangle. If there
|
244 |
is an intersection, then false is returned. Otherwise true is returned.
|
245 |
*/
|
246 |
public boolean contains(double x1, double y1, double w, double h) { |
247 |
double x2 = x1 + w;
|
248 |
double y2 = y1 + h;
|
249 |
|
250 |
if (!contains(x1, y1)) return false; |
251 |
if (!contains(x1, y2)) return false; |
252 |
if (!contains(x2, y1)) return false; |
253 |
if (!contains(x2, y2)) return false; |
254 |
|
255 |
int n = getNumPoints();
|
256 |
|
257 |
if (n == 0) return false; |
258 |
|
259 |
double[] p = get(0); |
260 |
|
261 |
double xb = p[ai0];
|
262 |
double yb = p[ai1];
|
263 |
|
264 |
for (int i = 1; i < n; i++) { |
265 |
p = get(i); |
266 |
double xa = p[ai0];
|
267 |
double ya = p[ai1];
|
268 |
|
269 |
if (getType(i) == MultiPath.LINE_TO) {
|
270 |
if (Geom.getSegSegIntersection(xa, ya, xb, yb, x1, y1, x2, y1, null) == Geom.INTERSECT) |
271 |
return false; |
272 |
if (Geom.getSegSegIntersection(xa, ya, xb, yb, x1, y1, x1, y2, null) == Geom.INTERSECT) |
273 |
return false; |
274 |
if (Geom.getSegSegIntersection(xa, ya, xb, yb, x1, y2, x2, y2, null) == Geom.INTERSECT) |
275 |
return false; |
276 |
if (Geom.getSegSegIntersection(xa, ya, xb, yb, x2, y1, x2, y2, null) == Geom.INTERSECT) |
277 |
return false; |
278 |
} |
279 |
|
280 |
xb = xa; |
281 |
yb = ya; |
282 |
} |
283 |
|
284 |
return true; |
285 |
} |
286 |
|
287 |
/**
|
288 |
See the contains(x, y, w, h) method.
|
289 |
|
290 |
@see #contains(double,double,double,double)
|
291 |
*/
|
292 |
public boolean contains(Rectangle2D r) { |
293 |
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
|
294 |
} |
295 |
//---------------------------------------------------------------
|
296 |
|
297 |
/**
|
298 |
This method returns true if any line segment in this multi-path intersects any of the
|
299 |
4 line segments formed by the rectangle or any corner of the rectangle is inside the
|
300 |
shape or any point of the shape is inside the rectangle. Otherwise false is returned.
|
301 |
*/
|
302 |
public boolean intersects(double x1, double y1, double w, double h) { |
303 |
double x2 = x1 + w;
|
304 |
double y2 = y1 + h;
|
305 |
|
306 |
if (contains(x1, y1)) return true; |
307 |
if (contains(x1, y2)) return true; |
308 |
if (contains(x2, y1)) return true; |
309 |
if (contains(x2, y2)) return true; |
310 |
|
311 |
int n = getNumPoints();
|
312 |
|
313 |
if (n == 0) return false; |
314 |
|
315 |
double[] p = get(0); |
316 |
|
317 |
double xb = p[ai0];
|
318 |
double yb = p[ai1];
|
319 |
|
320 |
for (int i = 1; i < n; i++) { |
321 |
p = get(i); |
322 |
double xa = p[ai0];
|
323 |
double ya = p[ai1];
|
324 |
|
325 |
if (getType(i) == MultiPath.LINE_TO) {
|
326 |
if (Geom.getSegSegIntersection(xa, ya, xb, yb, x1, y1, x2, y1, null) == Geom.INTERSECT) |
327 |
return true; |
328 |
if (Geom.getSegSegIntersection(xa, ya, xb, yb, x1, y1, x1, y2, null) == Geom.INTERSECT) |
329 |
return true; |
330 |
if (Geom.getSegSegIntersection(xa, ya, xb, yb, x1, y2, x2, y2, null) == Geom.INTERSECT) |
331 |
return true; |
332 |
if (Geom.getSegSegIntersection(xa, ya, xb, yb, x2, y1, x2, y2, null) == Geom.INTERSECT) |
333 |
return true; |
334 |
|
335 |
if (xa >= x1 && ya >= y1 && xa <= x2 && ya <= y2) return true; |
336 |
if (xb >= x1 && yb >= y1 && xb <= x2 && yb <= y2) return true; |
337 |
} |
338 |
|
339 |
xb = xa; |
340 |
yb = ya; |
341 |
} |
342 |
|
343 |
return false; |
344 |
} |
345 |
|
346 |
/**
|
347 |
See the intersects(x, y, w, h) method.
|
348 |
|
349 |
@see #intersects(double,double,double,double)
|
350 |
*/
|
351 |
public boolean intersects(Rectangle2D r) { |
352 |
return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
|
353 |
} |
354 |
} |