1
|
/*******************************************************************************
|
2
|
* Copyright (c) 2004, 2010 IBM Corporation and others.
|
3
|
* All rights reserved. This program and the accompanying materials
|
4
|
* are made available under the terms of the Eclipse Public License v1.0
|
5
|
* which accompanies this distribution, and is available at
|
6
|
* http://www.eclipse.org/legal/epl-v10.html
|
7
|
*
|
8
|
* Contributors:
|
9
|
* IBM Corporation - initial API and implementation
|
10
|
*******************************************************************************/
|
11
|
package org.eclipse.draw2d.graph;
|
12
|
|
13
|
import java.util.ArrayList;
|
14
|
import java.util.Collections;
|
15
|
import java.util.HashSet;
|
16
|
import java.util.Iterator;
|
17
|
import java.util.List;
|
18
|
import java.util.Set;
|
19
|
|
20
|
import org.eclipse.draw2d.PositionConstants;
|
21
|
import org.eclipse.draw2d.geometry.Point;
|
22
|
import org.eclipse.draw2d.geometry.PointList;
|
23
|
|
24
|
/**
|
25
|
* A Path representation for the ShortestPathRouting. A Path has a start and end
|
26
|
* point and may have bendpoints. The output of a path is accessed via the
|
27
|
* method <code>getPoints()</code>.
|
28
|
*
|
29
|
* This class is for internal use only.
|
30
|
*
|
31
|
* @author Whitney Sorenson
|
32
|
* @since 3.0
|
33
|
*/
|
34
|
public class Path {
|
35
|
|
36
|
/**
|
37
|
* A Stack of segments.
|
38
|
*/
|
39
|
private static class SegmentStack extends ArrayList {
|
40
|
|
41
|
Segment pop() {
|
42
|
return (Segment) remove(size() - 1);
|
43
|
}
|
44
|
|
45
|
Obstacle popObstacle() {
|
46
|
return (Obstacle) remove(size() - 1);
|
47
|
}
|
48
|
|
49
|
void push(Object obj) {
|
50
|
add(obj);
|
51
|
}
|
52
|
|
53
|
}
|
54
|
|
55
|
private static final Point CURRENT = new Point();
|
56
|
private static final double EPSILON = 1.04;
|
57
|
private static final Point NEXT = new Point();
|
58
|
private static final double OVAL_CONSTANT = 1.13;
|
59
|
|
60
|
/**
|
61
|
* The bendpoint constraints. The path must go through these bendpoints.
|
62
|
*/
|
63
|
PointList bendpoints;
|
64
|
/**
|
65
|
* An arbitrary data field which can be used to map a Path back to some
|
66
|
* client object.
|
67
|
*/
|
68
|
public Object data;
|
69
|
List excludedObstacles;
|
70
|
List grownSegments;
|
71
|
/**
|
72
|
* this field is for internal use only. It is true whenever a property has
|
73
|
* been changed which requires the solver to resolve this path.
|
74
|
*/
|
75
|
public boolean isDirty = true;
|
76
|
|
77
|
boolean isInverted = false;
|
78
|
boolean isMarked = false;
|
79
|
PointList points;
|
80
|
|
81
|
/**
|
82
|
* The previous cost ratio of the path. The cost ratio is the actual path
|
83
|
* length divided by the length from the start to the end.
|
84
|
*/
|
85
|
private double prevCostRatio;
|
86
|
List segments;
|
87
|
|
88
|
private SegmentStack stack;
|
89
|
Vertex start, end;
|
90
|
private Path subPath;
|
91
|
double threshold;
|
92
|
Set visibleObstacles;
|
93
|
Set visibleVertices;
|
94
|
|
95
|
/**
|
96
|
* Constructs a new path.
|
97
|
*
|
98
|
* @since 3.0
|
99
|
*/
|
100
|
public Path() {
|
101
|
segments = new ArrayList();
|
102
|
grownSegments = new ArrayList();
|
103
|
points = new PointList();
|
104
|
visibleVertices = new HashSet();
|
105
|
stack = new SegmentStack();
|
106
|
visibleObstacles = new HashSet();
|
107
|
excludedObstacles = new ArrayList();
|
108
|
}
|
109
|
|
110
|
/**
|
111
|
* Constructs a new path with the given data.
|
112
|
*
|
113
|
* @since 3.0
|
114
|
* @param data
|
115
|
* an arbitrary data field
|
116
|
*/
|
117
|
public Path(Object data) {
|
118
|
this();
|
119
|
this.data = data;
|
120
|
}
|
121
|
|
122
|
/**
|
123
|
* Constructs a new path with the given data, start and end point.
|
124
|
*
|
125
|
* @param start
|
126
|
* the start point for this path
|
127
|
* @param end
|
128
|
* the end point for this path
|
129
|
*/
|
130
|
public Path(Point start, Point end) {
|
131
|
this(new Vertex(start, null), new Vertex(end, null));
|
132
|
}
|
133
|
|
134
|
/**
|
135
|
* Creates a path between the given vertices.
|
136
|
*
|
137
|
* @param start
|
138
|
* start vertex
|
139
|
* @param end
|
140
|
* end vertex
|
141
|
*/
|
142
|
Path(Vertex start, Vertex end) {
|
143
|
this();
|
144
|
this.start = start;
|
145
|
this.end = end;
|
146
|
}
|
147
|
|
148
|
/**
|
149
|
* Attempts to add all segments between the given obstacles to the
|
150
|
* visibility graph.
|
151
|
*
|
152
|
* @param source
|
153
|
* the source obstacle
|
154
|
* @param target
|
155
|
* the target obstacle
|
156
|
*/
|
157
|
private void addAllSegmentsBetween(Obstacle source, Obstacle target) {
|
158
|
addConnectingSegment(new Segment(source.bottomLeft, target.bottomLeft),
|
159
|
source, target, false, false);
|
160
|
addConnectingSegment(
|
161
|
new Segment(source.bottomRight, target.bottomRight), source,
|
162
|
target, true, true);
|
163
|
addConnectingSegment(new Segment(source.topLeft, target.topLeft),
|
164
|
source, target, true, true);
|
165
|
addConnectingSegment(new Segment(source.topRight, target.topRight),
|
166
|
source, target, false, false);
|
167
|
|
168
|
if (source.bottom() == target.bottom()) {
|
169
|
addConnectingSegment(new Segment(source.bottomLeft,
|
170
|
target.bottomRight), source, target, false, true);
|
171
|
addConnectingSegment(new Segment(source.bottomRight,
|
172
|
target.bottomLeft), source, target, true, false);
|
173
|
}
|
174
|
if (source.y == target.y) {
|
175
|
addConnectingSegment(new Segment(source.topLeft, target.topRight),
|
176
|
source, target, true, false);
|
177
|
addConnectingSegment(new Segment(source.topRight, target.topLeft),
|
178
|
source, target, false, true);
|
179
|
}
|
180
|
if (source.x == target.x) {
|
181
|
addConnectingSegment(
|
182
|
new Segment(source.bottomLeft, target.topLeft), source,
|
183
|
target, false, true);
|
184
|
addConnectingSegment(
|
185
|
new Segment(source.topLeft, target.bottomLeft), source,
|
186
|
target, true, false);
|
187
|
}
|
188
|
if (source.right() == target.right()) {
|
189
|
addConnectingSegment(new Segment(source.bottomRight,
|
190
|
target.topRight), source, target, true, false);
|
191
|
addConnectingSegment(new Segment(source.topRight,
|
192
|
target.bottomRight), source, target, false, true);
|
193
|
}
|
194
|
}
|
195
|
|
196
|
/**
|
197
|
* Attempts to add a segment between the given obstacles to the visibility
|
198
|
* graph. This method is specifically written for the case where the two
|
199
|
* obstacles intersect and contains a boolean as to whether to check the
|
200
|
* diagonal that includes the top right point of the other obstacle.
|
201
|
*
|
202
|
* @param segment
|
203
|
* the segment to check
|
204
|
* @param o1
|
205
|
* the first obstacle
|
206
|
* @param o2
|
207
|
* the second obstacle
|
208
|
* @param checkTopRight1
|
209
|
* whether or not to check the diagonal containing top right
|
210
|
* point
|
211
|
*/
|
212
|
private void addConnectingSegment(Segment segment, Obstacle o1,
|
213
|
Obstacle o2, boolean checkTopRight1, boolean checkTopRight2) {
|
214
|
if (threshold != 0
|
215
|
&& (segment.end.getDistance(end)
|
216
|
+ segment.end.getDistance(start) > threshold || segment.start
|
217
|
.getDistance(end) + segment.start.getDistance(start) > threshold))
|
218
|
return;
|
219
|
|
220
|
if (o2.containsProper(segment.start) || o1.containsProper(segment.end))
|
221
|
return;
|
222
|
|
223
|
if (checkTopRight1
|
224
|
&& segment.intersects(o1.x, o1.bottom() - 1, o1.right() - 1,
|
225
|
o1.y))
|
226
|
return;
|
227
|
if (checkTopRight2
|
228
|
&& segment.intersects(o2.x, o2.bottom() - 1, o2.right() - 1,
|
229
|
o2.y))
|
230
|
return;
|
231
|
if (!checkTopRight1
|
232
|
&& segment.intersects(o1.x, o1.y, o1.right() - 1,
|
233
|
o1.bottom() - 1))
|
234
|
return;
|
235
|
if (!checkTopRight2
|
236
|
&& segment.intersects(o2.x, o2.y, o2.right() - 1,
|
237
|
o2.bottom() - 1))
|
238
|
return;
|
239
|
|
240
|
stack.push(o1);
|
241
|
stack.push(o2);
|
242
|
stack.push(segment);
|
243
|
}
|
244
|
|
245
|
/**
|
246
|
* Adds an obstacle to the visibility graph and generates new segments
|
247
|
*
|
248
|
* @param newObs
|
249
|
* the new obstacle, should not be in the graph already
|
250
|
*/
|
251
|
private void addObstacle(Obstacle newObs) {
|
252
|
visibleObstacles.add(newObs);
|
253
|
Iterator oItr = new HashSet(visibleObstacles).iterator();
|
254
|
while (oItr.hasNext()) {
|
255
|
Obstacle currObs = (Obstacle) oItr.next();
|
256
|
if (newObs != currObs)
|
257
|
addSegmentsFor(newObs, currObs);
|
258
|
}
|
259
|
addPerimiterSegments(newObs);
|
260
|
addSegmentsFor(start, newObs);
|
261
|
addSegmentsFor(end, newObs);
|
262
|
}
|
263
|
|
264
|
/**
|
265
|
* Adds the segments along the perimiter of an obstacle to the visiblity
|
266
|
* graph queue.
|
267
|
*
|
268
|
* @param obs
|
269
|
* the obstacle
|
270
|
*/
|
271
|
private void addPerimiterSegments(Obstacle obs) {
|
272
|
Segment seg = new Segment(obs.topLeft, obs.topRight);
|
273
|
stack.push(obs);
|
274
|
stack.push(null);
|
275
|
stack.push(seg);
|
276
|
seg = new Segment(obs.topRight, obs.bottomRight);
|
277
|
stack.push(obs);
|
278
|
stack.push(null);
|
279
|
stack.push(seg);
|
280
|
seg = new Segment(obs.bottomRight, obs.bottomLeft);
|
281
|
stack.push(obs);
|
282
|
stack.push(null);
|
283
|
stack.push(seg);
|
284
|
seg = new Segment(obs.bottomLeft, obs.topLeft);
|
285
|
stack.push(obs);
|
286
|
stack.push(null);
|
287
|
stack.push(seg);
|
288
|
}
|
289
|
|
290
|
/**
|
291
|
* Attempts to add a segment to the visibility graph. First checks to see if
|
292
|
* the segment is outside the threshold oval. Then it compares the segment
|
293
|
* against all obstacles. If it is clean, the segment is finally added to
|
294
|
* the graph.
|
295
|
*
|
296
|
* @param segment
|
297
|
* the segment
|
298
|
* @param exclude1
|
299
|
* an obstacle to exclude from the search
|
300
|
* @param exclude2
|
301
|
* another obstacle to exclude from the search
|
302
|
* @param allObstacles
|
303
|
* the list of all obstacles
|
304
|
*/
|
305
|
private void addSegment(Segment segment, Obstacle exclude1,
|
306
|
Obstacle exclude2, List allObstacles) {
|
307
|
if (threshold != 0
|
308
|
&& (segment.end.getDistance(end)
|
309
|
+ segment.end.getDistance(start) > threshold || segment.start
|
310
|
.getDistance(end) + segment.start.getDistance(start) > threshold))
|
311
|
return;
|
312
|
|
313
|
for (int i = 0; i < allObstacles.size(); i++) {
|
314
|
Obstacle obs = (Obstacle) allObstacles.get(i);
|
315
|
|
316
|
if (obs == exclude1 || obs == exclude2 || obs.exclude)
|
317
|
continue;
|
318
|
|
319
|
if (segment.intersects(obs.x, obs.y, obs.right() - 1,
|
320
|
obs.bottom() - 1)
|
321
|
|| segment.intersects(obs.x, obs.bottom() - 1,
|
322
|
obs.right() - 1, obs.y)
|
323
|
|| obs.containsProper(segment.start)
|
324
|
|| obs.containsProper(segment.end)) {
|
325
|
if (!visibleObstacles.contains(obs))
|
326
|
addObstacle(obs);
|
327
|
return;
|
328
|
}
|
329
|
}
|
330
|
|
331
|
linkVertices(segment);
|
332
|
}
|
333
|
|
334
|
/**
|
335
|
* Adds the segments between the given obstacles.
|
336
|
*
|
337
|
* @param source
|
338
|
* source obstacle
|
339
|
* @param target
|
340
|
* target obstacle
|
341
|
*/
|
342
|
private void addSegmentsFor(Obstacle source, Obstacle target) {
|
343
|
if (source.intersects(target))
|
344
|
addAllSegmentsBetween(source, target);
|
345
|
else if (target.bottom() - 1 < source.y)
|
346
|
addSegmentsTargetAboveSource(source, target);
|
347
|
else if (source.bottom() - 1 < target.y)
|
348
|
addSegmentsTargetAboveSource(target, source);
|
349
|
else if (target.right() - 1 < source.x)
|
350
|
addSegmentsTargetBesideSource(source, target);
|
351
|
else
|
352
|
addSegmentsTargetBesideSource(target, source);
|
353
|
}
|
354
|
|
355
|
/**
|
356
|
* Adds the segments between the given obstacles.
|
357
|
*
|
358
|
* @param source
|
359
|
* source obstacle
|
360
|
* @param target
|
361
|
* target obstacle
|
362
|
*/
|
363
|
private void addSegmentsFor(Vertex vertex, Obstacle obs) {
|
364
|
Segment seg = null;
|
365
|
Segment seg2 = null;
|
366
|
|
367
|
switch (obs.getPosition(vertex)) {
|
368
|
case PositionConstants.SOUTH_WEST:
|
369
|
case PositionConstants.NORTH_EAST:
|
370
|
seg = new Segment(vertex, obs.topLeft);
|
371
|
seg2 = new Segment(vertex, obs.bottomRight);
|
372
|
break;
|
373
|
case PositionConstants.SOUTH_EAST:
|
374
|
case PositionConstants.NORTH_WEST:
|
375
|
seg = new Segment(vertex, obs.topRight);
|
376
|
seg2 = new Segment(vertex, obs.bottomLeft);
|
377
|
break;
|
378
|
case PositionConstants.NORTH:
|
379
|
seg = new Segment(vertex, obs.topLeft);
|
380
|
seg2 = new Segment(vertex, obs.topRight);
|
381
|
break;
|
382
|
case PositionConstants.EAST:
|
383
|
seg = new Segment(vertex, obs.bottomRight);
|
384
|
seg2 = new Segment(vertex, obs.topRight);
|
385
|
break;
|
386
|
case PositionConstants.SOUTH:
|
387
|
seg = new Segment(vertex, obs.bottomRight);
|
388
|
seg2 = new Segment(vertex, obs.bottomLeft);
|
389
|
break;
|
390
|
case PositionConstants.WEST:
|
391
|
seg = new Segment(vertex, obs.topLeft);
|
392
|
seg2 = new Segment(vertex, obs.bottomLeft);
|
393
|
break;
|
394
|
default:
|
395
|
if (vertex.x == obs.x) {
|
396
|
seg = new Segment(vertex, obs.topLeft);
|
397
|
seg2 = new Segment(vertex, obs.bottomLeft);
|
398
|
} else if (vertex.y == obs.y) {
|
399
|
seg = new Segment(vertex, obs.topLeft);
|
400
|
seg2 = new Segment(vertex, obs.topRight);
|
401
|
} else if (vertex.y == obs.bottom() - 1) {
|
402
|
seg = new Segment(vertex, obs.bottomLeft);
|
403
|
seg2 = new Segment(vertex, obs.bottomRight);
|
404
|
} else if (vertex.x == obs.right() - 1) {
|
405
|
seg = new Segment(vertex, obs.topRight);
|
406
|
seg2 = new Segment(vertex, obs.bottomRight);
|
407
|
} else {
|
408
|
throw new RuntimeException("Unexpected vertex conditions"); //$NON-NLS-1$
|
409
|
}
|
410
|
}
|
411
|
|
412
|
stack.push(obs);
|
413
|
stack.push(null);
|
414
|
stack.push(seg);
|
415
|
stack.push(obs);
|
416
|
stack.push(null);
|
417
|
stack.push(seg2);
|
418
|
}
|
419
|
|
420
|
private void addSegmentsTargetAboveSource(Obstacle source, Obstacle target) {
|
421
|
// target located above source
|
422
|
Segment seg = null;
|
423
|
Segment seg2 = null;
|
424
|
if (target.x > source.x) {
|
425
|
seg = new Segment(source.topLeft, target.topLeft);
|
426
|
if (target.x < source.right() - 1)
|
427
|
seg2 = new Segment(source.topRight, target.bottomLeft);
|
428
|
else
|
429
|
seg2 = new Segment(source.bottomRight, target.topLeft);
|
430
|
} else if (source.x == target.x) {
|
431
|
seg = new Segment(source.topLeft, target.bottomLeft);
|
432
|
seg2 = new Segment(source.topRight, target.bottomLeft);
|
433
|
} else {
|
434
|
seg = new Segment(source.bottomLeft, target.bottomLeft);
|
435
|
seg2 = new Segment(source.topRight, target.bottomLeft);
|
436
|
}
|
437
|
|
438
|
stack.push(source);
|
439
|
stack.push(target);
|
440
|
stack.push(seg);
|
441
|
stack.push(source);
|
442
|
stack.push(target);
|
443
|
stack.push(seg2);
|
444
|
seg = null;
|
445
|
seg2 = null;
|
446
|
|
447
|
if (target.right() < source.right()) {
|
448
|
seg = new Segment(source.topRight, target.topRight);
|
449
|
if (target.right() - 1 > source.x)
|
450
|
seg2 = new Segment(source.topLeft, target.bottomRight);
|
451
|
else
|
452
|
seg2 = new Segment(source.bottomLeft, target.topRight);
|
453
|
} else if (source.right() == target.right()) {
|
454
|
seg = new Segment(source.topRight, target.bottomRight);
|
455
|
seg2 = new Segment(source.topLeft, target.bottomRight);
|
456
|
} else {
|
457
|
seg = new Segment(source.bottomRight, target.bottomRight);
|
458
|
seg2 = new Segment(source.topLeft, target.bottomRight);
|
459
|
}
|
460
|
|
461
|
stack.push(source);
|
462
|
stack.push(target);
|
463
|
stack.push(seg);
|
464
|
stack.push(source);
|
465
|
stack.push(target);
|
466
|
stack.push(seg2);
|
467
|
}
|
468
|
|
469
|
private void addSegmentsTargetBesideSource(Obstacle source, Obstacle target) {
|
470
|
// target located above source
|
471
|
Segment seg = null;
|
472
|
Segment seg2 = null;
|
473
|
if (target.y > source.y) {
|
474
|
seg = new Segment(source.topLeft, target.topLeft);
|
475
|
if (target.y < source.bottom() - 1)
|
476
|
seg2 = new Segment(source.bottomLeft, target.topRight);
|
477
|
else
|
478
|
seg2 = new Segment(source.bottomRight, target.topLeft);
|
479
|
} else if (source.y == target.y) {
|
480
|
// degenerate case
|
481
|
seg = new Segment(source.topLeft, target.topRight);
|
482
|
seg2 = new Segment(source.bottomLeft, target.topRight);
|
483
|
} else {
|
484
|
seg = new Segment(source.topRight, target.topRight);
|
485
|
seg2 = new Segment(source.bottomLeft, target.topRight);
|
486
|
}
|
487
|
stack.push(source);
|
488
|
stack.push(target);
|
489
|
stack.push(seg);
|
490
|
stack.push(source);
|
491
|
stack.push(target);
|
492
|
stack.push(seg2);
|
493
|
seg = null;
|
494
|
seg2 = null;
|
495
|
|
496
|
if (target.bottom() < source.bottom()) {
|
497
|
seg = new Segment(source.bottomLeft, target.bottomLeft);
|
498
|
if (target.bottom() - 1 > source.y)
|
499
|
seg2 = new Segment(source.topLeft, target.bottomRight);
|
500
|
else
|
501
|
seg2 = new Segment(source.topRight, target.bottomLeft);
|
502
|
} else if (source.bottom() == target.bottom()) {
|
503
|
seg = new Segment(source.bottomLeft, target.bottomRight);
|
504
|
seg2 = new Segment(source.topLeft, target.bottomRight);
|
505
|
} else {
|
506
|
seg = new Segment(source.bottomRight, target.bottomRight);
|
507
|
seg2 = new Segment(source.topLeft, target.bottomRight);
|
508
|
}
|
509
|
stack.push(source);
|
510
|
stack.push(target);
|
511
|
stack.push(seg);
|
512
|
stack.push(source);
|
513
|
stack.push(target);
|
514
|
stack.push(seg2);
|
515
|
}
|
516
|
|
517
|
/**
|
518
|
*
|
519
|
*/
|
520
|
void cleanup() {
|
521
|
// segments.clear();
|
522
|
visibleVertices.clear();
|
523
|
}
|
524
|
|
525
|
/**
|
526
|
* Begins the creation of the visibility graph with the first segment
|
527
|
*
|
528
|
* @param allObstacles
|
529
|
* list of all obstacles
|
530
|
*/
|
531
|
private void createVisibilityGraph(List allObstacles) {
|
532
|
stack.push(null);
|
533
|
stack.push(null);
|
534
|
stack.push(new Segment(start, end));
|
535
|
|
536
|
while (!stack.isEmpty())
|
537
|
addSegment(stack.pop(), stack.popObstacle(), stack.popObstacle(),
|
538
|
allObstacles);
|
539
|
}
|
540
|
|
541
|
/**
|
542
|
* Once the visibility graph is constructed, this is called to label the
|
543
|
* graph and determine the shortest path. Returns false if no path can be
|
544
|
* found.
|
545
|
*
|
546
|
* @return true if a path can be found.
|
547
|
*/
|
548
|
private boolean determineShortestPath() {
|
549
|
if (!labelGraph())
|
550
|
return false;
|
551
|
Vertex vertex = end;
|
552
|
prevCostRatio = end.cost / start.getDistance(end);
|
553
|
|
554
|
Vertex nextVertex;
|
555
|
while (!vertex.equals(start)) {
|
556
|
nextVertex = vertex.label;
|
557
|
if (nextVertex == null)
|
558
|
return false;
|
559
|
Segment s = new Segment(nextVertex, vertex);
|
560
|
segments.add(s);
|
561
|
vertex = nextVertex;
|
562
|
}
|
563
|
|
564
|
Collections.reverse(segments);
|
565
|
return true;
|
566
|
}
|
567
|
|
568
|
/**
|
569
|
* Resets all necessary fields for a solve.
|
570
|
*/
|
571
|
void fullReset() {
|
572
|
visibleVertices.clear();
|
573
|
segments.clear();
|
574
|
if (prevCostRatio == 0) {
|
575
|
double distance = start.getDistance(end);
|
576
|
threshold = distance * OVAL_CONSTANT;
|
577
|
} else
|
578
|
threshold = prevCostRatio * EPSILON * start.getDistance(end);
|
579
|
visibleObstacles.clear();
|
580
|
resetPartial();
|
581
|
}
|
582
|
|
583
|
/**
|
584
|
* Creates the visibility graph and returns whether or not a shortest path
|
585
|
* could be determined.
|
586
|
*
|
587
|
* @param allObstacles
|
588
|
* the list of all obstacles
|
589
|
* @return true if a shortest path was found
|
590
|
*/
|
591
|
boolean generateShortestPath(List allObstacles) {
|
592
|
createVisibilityGraph(allObstacles);
|
593
|
|
594
|
if (visibleVertices.size() == 0)
|
595
|
return false;
|
596
|
|
597
|
return determineShortestPath();
|
598
|
}
|
599
|
|
600
|
/**
|
601
|
* Returns the list of constrained points through which this path must pass
|
602
|
* or <code>null</code>.
|
603
|
*
|
604
|
* @see #setBendPoints(PointList)
|
605
|
* @return list of bend points
|
606
|
*/
|
607
|
public PointList getBendPoints() {
|
608
|
return bendpoints;
|
609
|
}
|
610
|
|
611
|
/**
|
612
|
* Returns the end point for this path
|
613
|
*
|
614
|
* @return end point for this path
|
615
|
*/
|
616
|
public Point getEndPoint() {
|
617
|
return end;
|
618
|
}
|
619
|
|
620
|
/**
|
621
|
* Returns the solution to this path.
|
622
|
*
|
623
|
* @return the points for this path.
|
624
|
*/
|
625
|
public PointList getPoints() {
|
626
|
return points;
|
627
|
}
|
628
|
|
629
|
/**
|
630
|
* Returns the start point for this path
|
631
|
*
|
632
|
* @return start point for this path
|
633
|
*/
|
634
|
public Point getStartPoint() {
|
635
|
return start;
|
636
|
}
|
637
|
|
638
|
/**
|
639
|
* Returns a subpath for this path at the given segment
|
640
|
*
|
641
|
* @param currentSegment
|
642
|
* the segment at which the subpath should be created
|
643
|
* @return the new path
|
644
|
*/
|
645
|
Path getSubPath(Segment currentSegment) {
|
646
|
// ready new path
|
647
|
Path newPath = new Path(currentSegment.start, end);
|
648
|
newPath.grownSegments = new ArrayList(grownSegments.subList(
|
649
|
grownSegments.indexOf(currentSegment), grownSegments.size()));
|
650
|
|
651
|
// fix old path
|
652
|
grownSegments = new ArrayList(grownSegments.subList(0,
|
653
|
grownSegments.indexOf(currentSegment) + 1));
|
654
|
end = currentSegment.end;
|
655
|
|
656
|
subPath = newPath;
|
657
|
return newPath;
|
658
|
}
|
659
|
|
660
|
/**
|
661
|
* Resets the vertices that this path has traveled prior to this segment.
|
662
|
* This is called when the path has become inverted and needs to rectify any
|
663
|
* labeling mistakes it made before it knew it was inverted.
|
664
|
*
|
665
|
* @param currentSegment
|
666
|
* the segment at which the path found it was inverted
|
667
|
*/
|
668
|
void invertPriorVertices(Segment currentSegment) {
|
669
|
int stop = grownSegments.indexOf(currentSegment);
|
670
|
for (int i = 0; i < stop; i++) {
|
671
|
Vertex vertex = ((Segment) grownSegments.get(i)).end;
|
672
|
if (vertex.type == Vertex.INNIE)
|
673
|
vertex.type = Vertex.OUTIE;
|
674
|
else
|
675
|
vertex.type = Vertex.INNIE;
|
676
|
}
|
677
|
}
|
678
|
|
679
|
/**
|
680
|
* Returns true if this obstacle is in the visibility graph
|
681
|
*
|
682
|
* @param obs
|
683
|
* the obstacle
|
684
|
* @return true if obstacle is in the visibility graph
|
685
|
*/
|
686
|
boolean isObstacleVisible(Obstacle obs) {
|
687
|
return visibleObstacles.contains(obs);
|
688
|
}
|
689
|
|
690
|
/**
|
691
|
* Labels the visibility graph to assist in finding the shortest path
|
692
|
*
|
693
|
* @return false if there was a gap in the visibility graph
|
694
|
*/
|
695
|
private boolean labelGraph() {
|
696
|
int numPermanentNodes = 1;
|
697
|
Vertex vertex = start;
|
698
|
Vertex neighborVertex = null;
|
699
|
vertex.isPermanent = true;
|
700
|
double newCost;
|
701
|
while (numPermanentNodes != visibleVertices.size()) {
|
702
|
List neighbors = vertex.neighbors;
|
703
|
if (neighbors == null)
|
704
|
return false;
|
705
|
// label neighbors if they have a new shortest path
|
706
|
for (int i = 0; i < neighbors.size(); i++) {
|
707
|
neighborVertex = (Vertex) neighbors.get(i);
|
708
|
if (!neighborVertex.isPermanent) {
|
709
|
newCost = vertex.cost + vertex.getDistance(neighborVertex);
|
710
|
if (neighborVertex.label == null) {
|
711
|
neighborVertex.label = vertex;
|
712
|
neighborVertex.cost = newCost;
|
713
|
} else if (neighborVertex.cost > newCost) {
|
714
|
neighborVertex.label = vertex;
|
715
|
neighborVertex.cost = newCost;
|
716
|
}
|
717
|
}
|
718
|
}
|
719
|
// find the next none-permanent, labeled vertex with smallest cost
|
720
|
double smallestCost = 0;
|
721
|
Vertex tempVertex = null;
|
722
|
Iterator v = visibleVertices.iterator();
|
723
|
while (v.hasNext()) {
|
724
|
tempVertex = (Vertex) v.next();
|
725
|
if (!tempVertex.isPermanent
|
726
|
&& tempVertex.label != null
|
727
|
&& (tempVertex.cost < smallestCost || smallestCost == 0)) {
|
728
|
smallestCost = tempVertex.cost;
|
729
|
vertex = tempVertex;
|
730
|
}
|
731
|
}
|
732
|
// set the new vertex to permanent.
|
733
|
vertex.isPermanent = true;
|
734
|
numPermanentNodes++;
|
735
|
}
|
736
|
return true;
|
737
|
}
|
738
|
|
739
|
/**
|
740
|
* Links two vertices together in the visibility graph
|
741
|
*
|
742
|
* @param segment
|
743
|
* the segment to add
|
744
|
*/
|
745
|
private void linkVertices(Segment segment) {
|
746
|
if (segment.start.neighbors == null)
|
747
|
segment.start.neighbors = new ArrayList();
|
748
|
if (segment.end.neighbors == null)
|
749
|
segment.end.neighbors = new ArrayList();
|
750
|
|
751
|
if (!segment.start.neighbors.contains(segment.end)) {
|
752
|
segment.start.neighbors.add(segment.end);
|
753
|
segment.end.neighbors.add(segment.start);
|
754
|
}
|
755
|
|
756
|
visibleVertices.add(segment.start);
|
757
|
visibleVertices.add(segment.end);
|
758
|
}
|
759
|
|
760
|
/**
|
761
|
* Called to reconnect a subpath back onto this path. Does a depth-first
|
762
|
* search to reconnect all paths. Should be called after sorting.
|
763
|
*/
|
764
|
void reconnectSubPaths() {
|
765
|
if (subPath != null) {
|
766
|
subPath.reconnectSubPaths();
|
767
|
|
768
|
Segment changedSegment = (Segment) subPath.grownSegments.remove(0);
|
769
|
Segment oldSegment = (Segment) grownSegments.get(grownSegments
|
770
|
.size() - 1);
|
771
|
|
772
|
oldSegment.end = changedSegment.end;
|
773
|
grownSegments.addAll(subPath.grownSegments);
|
774
|
|
775
|
subPath.points.removePoint(0);
|
776
|
points.removePoint(points.size() - 1);
|
777
|
points.addAll(subPath.points);
|
778
|
|
779
|
visibleObstacles.addAll(subPath.visibleObstacles);
|
780
|
|
781
|
end = subPath.end;
|
782
|
subPath = null;
|
783
|
}
|
784
|
}
|
785
|
|
786
|
/**
|
787
|
* Refreshes the exclude field on the obstacles in the list. Excludes all
|
788
|
* obstacles that contain the start or end point for this path.
|
789
|
*
|
790
|
* @param allObstacles
|
791
|
* list of all obstacles
|
792
|
*/
|
793
|
void refreshExcludedObstacles(List allObstacles) {
|
794
|
excludedObstacles.clear();
|
795
|
|
796
|
for (int i = 0; i < allObstacles.size(); i++) {
|
797
|
Obstacle o = (Obstacle) allObstacles.get(i);
|
798
|
o.exclude = false;
|
799
|
|
800
|
if (o.contains(start)) {
|
801
|
if (o.containsProper(start))
|
802
|
o.exclude = true;
|
803
|
else {
|
804
|
/*
|
805
|
* $TODO Check for corners. If the path begins exactly at
|
806
|
* the corner of an obstacle, the exclude should also be
|
807
|
* true.
|
808
|
*
|
809
|
* Or, change segment intersection so that two segments that
|
810
|
* share an endpoint do not intersect.
|
811
|
*/
|
812
|
}
|
813
|
}
|
814
|
|
815
|
if (o.contains(end)) {
|
816
|
if (o.containsProper(end))
|
817
|
o.exclude = true;
|
818
|
else {
|
819
|
// check for corners. See above statement.
|
820
|
}
|
821
|
}
|
822
|
|
823
|
if (o.exclude && !excludedObstacles.contains(o))
|
824
|
excludedObstacles.add(o);
|
825
|
}
|
826
|
}
|
827
|
|
828
|
/**
|
829
|
* Resets the fields for everything in the solve after the visibility graph
|
830
|
* steps.
|
831
|
*/
|
832
|
void resetPartial() {
|
833
|
isMarked = false;
|
834
|
isInverted = false;
|
835
|
subPath = null;
|
836
|
isDirty = false;
|
837
|
grownSegments.clear();
|
838
|
points.removeAllPoints();
|
839
|
}
|
840
|
|
841
|
/**
|
842
|
* Sets the list of bend points to the given list and dirties the path.
|
843
|
*
|
844
|
* @param bendPoints
|
845
|
* the list of bend points
|
846
|
*/
|
847
|
public void setBendPoints(PointList bendPoints) {
|
848
|
this.bendpoints = bendPoints;
|
849
|
isDirty = true;
|
850
|
}
|
851
|
|
852
|
/**
|
853
|
* Sets the end point for this path to the given point.
|
854
|
*
|
855
|
* @param end
|
856
|
* the new end point for this path
|
857
|
*/
|
858
|
public void setEndPoint(Point end) {
|
859
|
if (end.equals(this.end))
|
860
|
return;
|
861
|
this.end = new Vertex(end, null);
|
862
|
isDirty = true;
|
863
|
}
|
864
|
|
865
|
/**
|
866
|
* Sets the start point for this path to the given point.
|
867
|
*
|
868
|
* @param start
|
869
|
* the new start point for this path
|
870
|
*/
|
871
|
public void setStartPoint(Point start) {
|
872
|
if (start.equals(this.start))
|
873
|
return;
|
874
|
this.start = new Vertex(start, null);
|
875
|
isDirty = true;
|
876
|
}
|
877
|
|
878
|
/**
|
879
|
* Returns <code>true</code> if the path is clean and intersects the given
|
880
|
* obstacle. Also dirties the path in the process.
|
881
|
*
|
882
|
* @since 3.0
|
883
|
* @param obs
|
884
|
* the obstacle
|
885
|
* @return <code>true</code> if a clean path touches the obstacle
|
886
|
*/
|
887
|
boolean testAndSet(Obstacle obs) {
|
888
|
if (isDirty)
|
889
|
return false;
|
890
|
// This will never actually happen because obstacles are not stored by
|
891
|
// identity
|
892
|
if (excludedObstacles.contains(obs))
|
893
|
return false;
|
894
|
|
895
|
Segment seg1 = new Segment(obs.topLeft, obs.bottomRight);
|
896
|
Segment seg2 = new Segment(obs.topRight, obs.bottomLeft);
|
897
|
|
898
|
for (int s = 0; s < points.size() - 1; s++) {
|
899
|
points.getPoint(CURRENT, s);
|
900
|
points.getPoint(NEXT, s + 1);
|
901
|
|
902
|
if (seg1.intersects(CURRENT, NEXT)
|
903
|
|| seg2.intersects(CURRENT, NEXT) || obs.contains(CURRENT)
|
904
|
|| obs.contains(NEXT)) {
|
905
|
isDirty = true;
|
906
|
return true;
|
907
|
}
|
908
|
}
|
909
|
return false;
|
910
|
}
|
911
|
|
912
|
}
|