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