Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
epoxy
GitHub Repository: epoxy/proj11
Path: blob/master/SLICK_HOME/src/org/newdawn/slick/geom/GeomUtil.java
1461 views
1
package org.newdawn.slick.geom;
2
3
import java.util.ArrayList;
4
5
/**
6
* A set of utilities to play with geometry
7
*
8
* @author kevin
9
*/
10
public class GeomUtil {
11
/** The tolerance for determining changes and steps */
12
public float EPSILON = 0.0001f;
13
/** The tolerance for determining direction change */
14
public float EDGE_SCALE = 1f;
15
/** The maximum number of points returned by an operation - prevents full lockups */
16
public int MAX_POINTS = 10000;
17
/** The listener to notify of operations */
18
public GeomUtilListener listener;
19
20
/**
21
* Subtract one shape from another - note this is experimental and doesn't
22
* currently handle islands
23
*
24
* @param target The target to be subtracted from
25
* @param missing The shape to subtract
26
* @return The newly created shapes
27
*/
28
public Shape[] subtract(Shape target, Shape missing) {
29
target = target.transform(new Transform());
30
missing = missing.transform(new Transform());
31
32
int count = 0;
33
for (int i=0;i<target.getPointCount();i++) {
34
if (missing.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
35
count++;
36
}
37
}
38
39
if (count == target.getPointCount()) {
40
return new Shape[0];
41
}
42
43
if (!target.intersects(missing)) {
44
return new Shape[] {target};
45
}
46
47
int found = 0;
48
for (int i=0;i<missing.getPointCount();i++) {
49
if (target.contains(missing.getPoint(i)[0], missing.getPoint(i)[1])) {
50
if (!onPath(target, missing.getPoint(i)[0], missing.getPoint(i)[1])) {
51
found++;
52
}
53
}
54
}
55
for (int i=0;i<target.getPointCount();i++) {
56
if (missing.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
57
if (!onPath(missing, target.getPoint(i)[0], target.getPoint(i)[1]))
58
{
59
found++;
60
}
61
}
62
}
63
64
if (found < 1) {
65
return new Shape[] {target};
66
}
67
68
return combine(target, missing, true);
69
}
70
71
/**
72
* Check if the given point is on the path
73
*
74
* @param path The path to check
75
* @param x The x coordinate of the point to check
76
* @param y The y coordiante of teh point to check
77
* @return True if the point is on the path
78
*/
79
private boolean onPath(Shape path, float x, float y) {
80
for (int i=0;i<path.getPointCount()+1;i++) {
81
int n = rationalPoint(path, i+1);
82
Line line = getLine(path, rationalPoint(path, i), n);
83
if (line.distance(new Vector2f(x,y)) < EPSILON*100) {
84
return true;
85
}
86
}
87
88
return false;
89
}
90
91
/**
92
* Set the listener to be notified of geometry based operations
93
*
94
* @param listener The listener to be notified of geometry based operations
95
*/
96
public void setListener(GeomUtilListener listener) {
97
this.listener = listener;
98
}
99
100
/**
101
* Join to shapes together. Note that the shapes must be touching
102
* for this method to work.
103
*
104
* @param target The target shape to union with
105
* @param other The additional shape to union
106
* @return The newly created shapes
107
*/
108
public Shape[] union(Shape target, Shape other) {
109
target = target.transform(new Transform());
110
other = other.transform(new Transform());
111
112
if (!target.intersects(other)) {
113
return new Shape[] {target, other};
114
}
115
116
// handle the case where intersects is true but really we're talking
117
// about edge points
118
boolean touches = false;
119
int buttCount = 0;
120
for (int i=0;i<target.getPointCount();i++) {
121
if (other.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
122
if (!other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
123
touches = true;
124
break;
125
}
126
}
127
if (other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
128
buttCount++;
129
}
130
}
131
for (int i=0;i<other.getPointCount();i++) {
132
if (target.contains(other.getPoint(i)[0], other.getPoint(i)[1])) {
133
if (!target.hasVertex(other.getPoint(i)[0], other.getPoint(i)[1])) {
134
touches = true;
135
break;
136
}
137
}
138
}
139
140
if ((!touches) && (buttCount < 2)) {
141
return new Shape[] {target, other};
142
}
143
144
// so they are definitely touching, consider the union
145
return combine(target, other, false);
146
}
147
148
/**
149
* Perform the combination
150
*
151
* @param target The target shape we're updating
152
* @param other The other shape in the operation
153
* @param subtract True if it's a subtract operation, otherwise it's union
154
* @return The set of shapes produced
155
*/
156
private Shape[] combine(Shape target, Shape other, boolean subtract) {
157
if (subtract) {
158
ArrayList shapes = new ArrayList();
159
ArrayList used = new ArrayList();
160
161
// remove any points that are contianed in the shape we're removing, these
162
// are implicitly used
163
for (int i=0;i<target.getPointCount();i++) {
164
float[] point = target.getPoint(i);
165
if (other.contains(point[0], point[1])) {
166
used.add(new Vector2f(point[0], point[1]));
167
if (listener != null) {
168
listener.pointExcluded(point[0], point[1]);
169
}
170
}
171
}
172
173
for (int i=0;i<target.getPointCount();i++) {
174
float[] point = target.getPoint(i);
175
Vector2f pt = new Vector2f(point[0], point[1]);
176
177
if (!used.contains(pt)) {
178
Shape result = combineSingle(target, other, true, i);
179
shapes.add(result);
180
for (int j=0;j<result.getPointCount();j++) {
181
float[] kpoint = result.getPoint(j);
182
Vector2f kpt = new Vector2f(kpoint[0], kpoint[1]);
183
used.add(kpt);
184
}
185
}
186
}
187
188
return (Shape[]) shapes.toArray(new Shape[0]);
189
} else {
190
for (int i=0;i<target.getPointCount();i++) {
191
if (!other.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
192
if (!other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
193
Shape shape = combineSingle(target, other, false, i);
194
return new Shape[] {shape};
195
}
196
}
197
}
198
199
return new Shape[] {other};
200
}
201
}
202
203
/**
204
* Combine two shapes
205
*
206
* @param target The target shape
207
* @param missing The second shape to apply
208
* @param subtract True if we should subtract missing from target, otherwise union
209
* @param start The point to start at
210
* @return The newly created shape
211
*/
212
private Shape combineSingle(Shape target, Shape missing, boolean subtract, int start) {
213
Shape current = target;
214
Shape other = missing;
215
int point = start;
216
int dir = 1;
217
218
Polygon poly = new Polygon();
219
boolean first = true;
220
221
int loop = 0;
222
223
// while we've not reached the same point
224
float px = current.getPoint(point)[0];
225
float py = current.getPoint(point)[1];
226
227
while (!poly.hasVertex(px, py) || (first) || (current != target)) {
228
first = false;
229
loop++;
230
if (loop > MAX_POINTS) {
231
break;
232
}
233
234
// add the current point to the result shape
235
poly.addPoint(px,py);
236
if (listener != null) {
237
listener.pointUsed(px,py);
238
}
239
240
// if the line between the current point and the next one intersect the
241
// other shape work out where on the other shape and start traversing it's
242
// path instead
243
Line line = getLine(current, px, py, rationalPoint(current, point+dir));
244
HitResult hit = intersect(other, line);
245
246
if (hit != null) {
247
Line hitLine = hit.line;
248
Vector2f pt = hit.pt;
249
px = pt.x;
250
py = pt.y;
251
252
if (listener != null) {
253
listener.pointIntersected(px,py);
254
}
255
256
if (other.hasVertex(px, py)) {
257
point = other.indexOf(pt.x,pt.y);
258
dir = 1;
259
px = pt.x;
260
py = pt.y;
261
262
Shape temp = current;
263
current = other;
264
other = temp;
265
continue;
266
}
267
268
float dx = hitLine.getDX() / hitLine.length();
269
float dy = hitLine.getDY() / hitLine.length();
270
dx *= EDGE_SCALE;
271
dy *= EDGE_SCALE;
272
273
if (current.contains(pt.x + dx, pt.y + dy)) {
274
// the point is the next one, we need to take the first and traverse
275
// the path backwards
276
if (subtract) {
277
if (current == missing) {
278
point = hit.p2;
279
dir = -1;
280
} else {
281
point = hit.p1;
282
dir = 1;
283
}
284
} else {
285
if (current == target) {
286
point = hit.p2;
287
dir = -1;
288
} else {
289
point = hit.p2;
290
dir = -1;
291
}
292
}
293
294
// swap the shapes over, we'll traverse the other one
295
Shape temp = current;
296
current = other;
297
other = temp;
298
} else if (current.contains(pt.x - dx, pt.y - dy)) {
299
if (subtract) {
300
if (current == target) {
301
point = hit.p2;
302
dir = -1;
303
} else {
304
point = hit.p1;
305
dir = 1;
306
}
307
} else {
308
if (current == missing) {
309
point = hit.p1;
310
dir = 1;
311
} else {
312
point = hit.p1;
313
dir = 1;
314
}
315
}
316
317
// swap the shapes over, we'll traverse the other one
318
Shape temp = current;
319
current = other;
320
other = temp;
321
} else {
322
// give up
323
if (subtract) {
324
break;
325
} else {
326
point = hit.p1;
327
dir = 1;
328
Shape temp = current;
329
current = other;
330
other = temp;
331
332
point = rationalPoint(current, point+dir);
333
px = current.getPoint(point)[0];
334
py = current.getPoint(point)[1];
335
}
336
}
337
} else {
338
// otherwise just move to the next point in the current shape
339
point = rationalPoint(current, point+dir);
340
px = current.getPoint(point)[0];
341
py = current.getPoint(point)[1];
342
}
343
}
344
345
poly.addPoint(px,py);
346
if (listener != null) {
347
listener.pointUsed(px,py);
348
}
349
350
return poly;
351
}
352
353
/**
354
* Intersect a line with a shape
355
*
356
* @param shape The shape to compare
357
* @param line The line to intersect against the shape
358
* @return The result describing the intersection or null if none
359
*/
360
public HitResult intersect(Shape shape, Line line) {
361
float distance = Float.MAX_VALUE;
362
HitResult hit = null;
363
364
for (int i=0;i<shape.getPointCount();i++) {
365
int next = rationalPoint(shape, i+1);
366
Line local = getLine(shape, i, next);
367
368
Vector2f pt = line.intersect(local, true);
369
if (pt != null) {
370
float newDis = pt.distance(line.getStart());
371
if ((newDis < distance) && (newDis > EPSILON)) {
372
hit = new HitResult();
373
hit.pt = pt;
374
hit.line = local;
375
hit.p1 = i;
376
hit.p2 = next;
377
distance = newDis;
378
}
379
}
380
}
381
382
return hit;
383
}
384
385
/**
386
* Rationalise a point in terms of a given shape
387
*
388
* @param shape The shape
389
* @param p The index of the point
390
* @return The index that is rational for the shape
391
*/
392
public static int rationalPoint(Shape shape, int p) {
393
while (p < 0) {
394
p += shape.getPointCount();
395
}
396
while (p >= shape.getPointCount()) {
397
p -= shape.getPointCount();
398
}
399
400
return p;
401
}
402
403
/**
404
* Get a line between two points in a shape
405
*
406
* @param shape The shape
407
* @param s The index of the start point
408
* @param e The index of the end point
409
* @return The line between the two points
410
*/
411
public Line getLine(Shape shape, int s, int e) {
412
float[] start = shape.getPoint(s);
413
float[] end = shape.getPoint(e);
414
415
Line line = new Line(start[0],start[1],end[0],end[1]);
416
return line;
417
}
418
419
/**
420
* Get a line between two points in a shape
421
*
422
* @param shape The shape
423
* @param sx The x coordinate of the start point
424
* @param sy The y coordinate of the start point
425
* @param e The index of the end point
426
* @return The line between the two points
427
*/
428
public Line getLine(Shape shape, float sx, float sy, int e) {
429
float[] end = shape.getPoint(e);
430
431
Line line = new Line(sx,sy,end[0],end[1]);
432
return line;
433
}
434
435
/**
436
* A lightweigtht description of a intersection between a shape and
437
* line.
438
*/
439
public class HitResult {
440
/** The line on the target shape that intersected */
441
public Line line;
442
/** The index of the first point on the target shape that forms the line */
443
public int p1;
444
/** The index of the second point on the target shape that forms the line */
445
public int p2;
446
/** The position of the intersection */
447
public Vector2f pt;
448
}
449
}
450
451