1 package org.newdawn.slick.geom;
2
3 import javax.annotation.Nonnull;
4 import javax.annotation.Nullable;
5 import java.util.ArrayList;
6 import java.util.List;
7
8
9
10
11
12
13 class GeomUtil {
14
15 private static final float EPSILON = 0.0001f;
16
17 private static final float EDGE_SCALE = 1f;
18
19 private static final int MAX_POINTS = 10000;
20
21 private GeomUtilListener listener;
22
23
24
25
26
27
28
29
30
31 @Nonnull
32 public Shape[] subtract(Shape target, Shape missing) {
33 target = target.transform(new Transform());
34 missing = missing.transform(new Transform());
35
36 int count = 0;
37 for (int i=0;i<target.getPointCount();i++) {
38 if (missing.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
39 count++;
40 }
41 }
42
43 if (count == target.getPointCount()) {
44 return new Shape[0];
45 }
46
47 if (!target.intersects(missing)) {
48 return new Shape[] {target};
49 }
50
51 int found = 0;
52 for (int i=0;i<missing.getPointCount();i++) {
53 if (target.contains(missing.getPoint(i)[0], missing.getPoint(i)[1])) {
54 if (!onPath(target, missing.getPoint(i)[0], missing.getPoint(i)[1])) {
55 found++;
56 }
57 }
58 }
59 for (int i=0;i<target.getPointCount();i++) {
60 if (missing.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
61 if (!onPath(missing, target.getPoint(i)[0], target.getPoint(i)[1]))
62 {
63 found++;
64 }
65 }
66 }
67
68 if (found < 1) {
69 return new Shape[] {target};
70 }
71
72 return combine(target, missing, true);
73 }
74
75
76
77
78
79
80
81
82
83 private boolean onPath(@Nonnull Shape path, float x, float y) {
84 for (int i=0;i<path.getPointCount()+1;i++) {
85 int n = rationalPoint(path, i+1);
86 Line line = getLine(path, rationalPoint(path, i), n);
87 if (line.distance(new Vector2f(x,y)) < EPSILON*100) {
88 return true;
89 }
90 }
91
92 return false;
93 }
94
95
96
97
98
99
100 public void setListener(GeomUtilListener listener) {
101 this.listener = listener;
102 }
103
104
105
106
107
108
109
110
111
112 @Nonnull
113 public Shape[] union(Shape target, Shape other) {
114 target = target.transform(new Transform());
115 other = other.transform(new Transform());
116
117 if (!target.intersects(other)) {
118 return new Shape[] {target, other};
119 }
120
121
122
123 boolean touches = false;
124 int buttCount = 0;
125 for (int i=0;i<target.getPointCount();i++) {
126 if (other.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
127 if (!other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
128 touches = true;
129 break;
130 }
131 }
132 if (other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
133 buttCount++;
134 }
135 }
136 for (int i=0;i<other.getPointCount();i++) {
137 if (target.contains(other.getPoint(i)[0], other.getPoint(i)[1])) {
138 if (!target.hasVertex(other.getPoint(i)[0], other.getPoint(i)[1])) {
139 touches = true;
140 break;
141 }
142 }
143 }
144
145 if ((!touches) && (buttCount < 2)) {
146 return new Shape[] {target, other};
147 }
148
149
150 return combine(target, other, false);
151 }
152
153
154
155
156
157
158
159
160
161 @Nonnull
162 private Shape[] combine(@Nonnull Shape target, @Nonnull Shape other, boolean subtract) {
163 if (subtract) {
164 List<Shape> shapes = new ArrayList<>();
165 List<Vector2f> used = new ArrayList<>();
166
167
168
169 for (int i=0;i<target.getPointCount();i++) {
170 float[] point = target.getPoint(i);
171 if (other.contains(point[0], point[1])) {
172 used.add(new Vector2f(point[0], point[1]));
173 if (listener != null) {
174 listener.pointExcluded(point[0], point[1]);
175 }
176 }
177 }
178
179 for (int i=0;i<target.getPointCount();i++) {
180 float[] point = target.getPoint(i);
181 Vector2f pt = new Vector2f(point[0], point[1]);
182
183 if (!used.contains(pt)) {
184 Shape result = combineSingle(target, other, true, i);
185 shapes.add(result);
186 for (int j=0;j<result.getPointCount();j++) {
187 float[] kpoint = result.getPoint(j);
188 Vector2f kpt = new Vector2f(kpoint[0], kpoint[1]);
189 used.add(kpt);
190 }
191 }
192 }
193
194 return (Shape[]) shapes.toArray();
195 } else {
196 for (int i=0;i<target.getPointCount();i++) {
197 if (!other.contains(target.getPoint(i)[0], target.getPoint(i)[1])) {
198 if (!other.hasVertex(target.getPoint(i)[0], target.getPoint(i)[1])) {
199 Shape shape = combineSingle(target, other, false, i);
200 return new Shape[] {shape};
201 }
202 }
203 }
204
205 return new Shape[] {other};
206 }
207 }
208
209
210
211
212
213
214
215
216
217
218 @Nonnull
219 private Shape combineSingle(Shape target, Shape missing, boolean subtract, int start) {
220 Shape current = target;
221 Shape other = missing;
222 int point = start;
223 int dir = 1;
224
225 Polygon poly = new Polygon();
226 boolean first = true;
227
228 int loop = 0;
229
230
231 float px = current.getPoint(point)[0];
232 float py = current.getPoint(point)[1];
233
234 while (!poly.hasVertex(px, py) || (first) || (current != target)) {
235 first = false;
236 loop++;
237 if (loop > MAX_POINTS) {
238 break;
239 }
240
241
242 poly.addPoint(px,py);
243 if (listener != null) {
244 listener.pointUsed(px,py);
245 }
246
247
248
249
250 Line line = getLine(current, px, py, rationalPoint(current, point+dir));
251 HitResult hit = intersect(other, line);
252
253 if (hit != null) {
254 Line hitLine = hit.line;
255 Vector2f pt = hit.pt;
256 px = pt.x;
257 py = pt.y;
258
259 if (listener != null) {
260 listener.pointIntersected(px,py);
261 }
262
263 if (other.hasVertex(px, py)) {
264 point = other.indexOf(pt.x,pt.y);
265 dir = 1;
266 px = pt.x;
267 py = pt.y;
268
269 Shape temp = current;
270 current = other;
271 other = temp;
272 continue;
273 }
274
275 float dx = hitLine.getDX() / hitLine.length();
276 float dy = hitLine.getDY() / hitLine.length();
277 dx *= EDGE_SCALE;
278 dy *= EDGE_SCALE;
279
280 if (current.contains(pt.x + dx, pt.y + dy)) {
281
282
283 if (subtract) {
284 if (current == missing) {
285 point = hit.p2;
286 dir = -1;
287 } else {
288 point = hit.p1;
289 dir = 1;
290 }
291 } else {
292 if (current == target) {
293 point = hit.p2;
294 dir = -1;
295 } else {
296 point = hit.p2;
297 dir = -1;
298 }
299 }
300
301
302 Shape temp = current;
303 current = other;
304 other = temp;
305 } else if (current.contains(pt.x - dx, pt.y - dy)) {
306 if (subtract) {
307 if (current == target) {
308 point = hit.p2;
309 dir = -1;
310 } else {
311 point = hit.p1;
312 dir = 1;
313 }
314 } else {
315 if (current == missing) {
316 point = hit.p1;
317 dir = 1;
318 } else {
319 point = hit.p1;
320 dir = 1;
321 }
322 }
323
324
325 Shape temp = current;
326 current = other;
327 other = temp;
328 } else {
329
330 if (subtract) {
331 break;
332 } else {
333 point = hit.p1;
334 dir = 1;
335 Shape temp = current;
336 current = other;
337 other = temp;
338
339 point = rationalPoint(current, point+dir);
340 px = current.getPoint(point)[0];
341 py = current.getPoint(point)[1];
342 }
343 }
344 } else {
345
346 point = rationalPoint(current, point+dir);
347 px = current.getPoint(point)[0];
348 py = current.getPoint(point)[1];
349 }
350 }
351
352 poly.addPoint(px,py);
353 if (listener != null) {
354 listener.pointUsed(px,py);
355 }
356
357 return poly;
358 }
359
360
361
362
363
364
365
366
367 @Nullable
368 HitResult intersect(@Nonnull Shape shape, @Nonnull Line line) {
369 float distance = Float.MAX_VALUE;
370 HitResult hit = null;
371
372 for (int i=0;i<shape.getPointCount();i++) {
373 int next = rationalPoint(shape, i+1);
374 Line local = getLine(shape, i, next);
375
376 Vector2f pt = line.intersect(local, true);
377 if (pt != null) {
378 float newDis = pt.distance(line.getStart());
379 if ((newDis < distance) && (newDis > EPSILON)) {
380 hit = new HitResult();
381 hit.pt = pt;
382 hit.line = local;
383 hit.p1 = i;
384 hit.p2 = next;
385 distance = newDis;
386 }
387 }
388 }
389
390 return hit;
391 }
392
393
394
395
396
397
398
399
400 private static int rationalPoint(@Nonnull Shape shape, int p) {
401 while (p < 0) {
402 p += shape.getPointCount();
403 }
404 while (p >= shape.getPointCount()) {
405 p -= shape.getPointCount();
406 }
407
408 return p;
409 }
410
411
412
413
414
415
416
417
418
419 @Nonnull
420 Line getLine(@Nonnull Shape shape, int s, int e) {
421 float[] start = shape.getPoint(s);
422 float[] end = shape.getPoint(e);
423
424 Line line = new Line(start[0],start[1],end[0],end[1]);
425 return line;
426 }
427
428
429
430
431
432
433
434
435
436
437 @Nonnull
438 Line getLine(@Nonnull Shape shape, float sx, float sy, int e) {
439 float[] end = shape.getPoint(e);
440
441 Line line = new Line(sx,sy,end[0],end[1]);
442 return line;
443 }
444
445
446
447
448
449 public class HitResult {
450
451 public Line line;
452
453 public int p1;
454
455 public int p2;
456
457 @Nullable
458 public Vector2f pt;
459 }
460 }