Mercurial > projects > dwt-addons
comparison dwtx/draw2d/graph/ShortestPathRouter.d @ 98:95307ad235d9
Added Draw2d code, still work in progress
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sun, 03 Aug 2008 00:52:14 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
96:b492ba44e44d | 98:95307ad235d9 |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2004, 2005 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 * Port to the D programming language: | |
11 * Frank Benoit <benoit@tionex.de> | |
12 *******************************************************************************/ | |
13 module dwtx.draw2d.graph.ShortestPathRouter; | |
14 | |
15 import dwt.dwthelper.utils; | |
16 import dwtx.dwtxhelper.Collection; | |
17 | |
18 import dwtx.draw2d.PositionConstants; | |
19 import dwtx.draw2d.geometry.Point; | |
20 import dwtx.draw2d.geometry.PointList; | |
21 import dwtx.draw2d.geometry.Rectangle; | |
22 import dwtx.draw2d.graph.Path; | |
23 import dwtx.draw2d.graph.Vertex; | |
24 import dwtx.draw2d.graph.Segment; | |
25 import dwtx.draw2d.graph.Obstacle; | |
26 | |
27 /** | |
28 * Bends a collection of {@link Path Paths} around rectangular obstacles. This class | |
29 * maintains a list of paths and obstacles. Updates can be made to the paths and/or | |
30 * obstacles, and then an incremental solve can be invoked. | |
31 * <P> | |
32 * The algorithm will attempt to find the shortest non-intersecting path between each | |
33 * path's start and end points. Once all paths have been found, they will be offset based | |
34 * on how many paths bend around the same corner of each obstacle. | |
35 * <P> | |
36 * The worst-case performance of this algorithm is p * s * n^2, where p is the number of | |
37 * paths, n is the number of obstacles, and s is the average number of segments in each | |
38 * path's final solution. | |
39 * <P> | |
40 * This class is not intended to be subclassed. | |
41 * @author Whitney Sorenson | |
42 * @author Randy Hudson | |
43 * @since 3.0 | |
44 */ | |
45 public class ShortestPathRouter { | |
46 | |
47 /** | |
48 * A stack of Paths. | |
49 */ | |
50 static class PathStack : ArrayList { | |
51 | |
52 Path pop() { | |
53 return cast(Path)remove(size() - 1); | |
54 } | |
55 | |
56 void push(Path path) { | |
57 add(path); | |
58 } | |
59 } | |
60 | |
61 /** | |
62 * The number of times to grow obstacles and test for intersections. This is a tradeoff | |
63 * between performance and quality of output. | |
64 */ | |
65 private static const int NUM_GROW_PASSES = 2; | |
66 | |
67 private int spacing = 4; | |
68 private bool growPassChangedObstacles; | |
69 private List orderedPaths; | |
70 private Map pathsToChildPaths; | |
71 | |
72 private PathStack stack; | |
73 private List subPaths; | |
74 | |
75 private List userObstacles; | |
76 private List userPaths; | |
77 private List workingPaths; | |
78 | |
79 /** | |
80 * Creates a new shortest path routing. | |
81 */ | |
82 public this() { | |
83 userPaths = new ArrayList(); | |
84 workingPaths = new ArrayList(); | |
85 pathsToChildPaths = new HashMap(); | |
86 userObstacles = new ArrayList(); | |
87 } | |
88 | |
89 /** | |
90 * Adds an obstacle with the given bounds to the obstacles. | |
91 * | |
92 * @param rect the bounds of this obstacle | |
93 * @return <code>true</code> if the added obstacle has dirtied one or more paths | |
94 */ | |
95 public bool addObstacle(Rectangle rect) { | |
96 return internalAddObstacle(new Obstacle(rect, this)); | |
97 } | |
98 | |
99 /** | |
100 * Adds a path to the routing. | |
101 * @param path the path to add. | |
102 */ | |
103 public void addPath(Path path) { | |
104 userPaths.add(path); | |
105 workingPaths.add(path); | |
106 } | |
107 | |
108 /** | |
109 * Fills the point lists of the Paths to the correct bent points. | |
110 */ | |
111 private void bendPaths() { | |
112 for (int i = 0; i < orderedPaths.size(); i++) { | |
113 Path path = cast(Path) orderedPaths.get(i); | |
114 Segment segment = null; | |
115 path.points.addPoint(new Point(path.start.x, path.start.y)); | |
116 for (int v = 0; v < path.grownSegments.size(); v++) { | |
117 segment = cast(Segment) path.grownSegments.get(v); | |
118 Vertex vertex = segment.end; | |
119 | |
120 if (vertex !is null && v < path.grownSegments.size() - 1) { | |
121 if (vertex.type is Vertex.INNIE) { | |
122 vertex.count++; | |
123 path.points.addPoint(vertex.bend(vertex.count)); | |
124 } else { | |
125 path.points.addPoint(vertex.bend(vertex.totalCount)); | |
126 vertex.totalCount--; | |
127 } | |
128 } | |
129 } | |
130 path.points.addPoint(new Point(path.end.x, path.end.y)); | |
131 } | |
132 } | |
133 | |
134 /** | |
135 * Checks a vertex to see if its offset should shrink | |
136 * @param vertex the vertex to check | |
137 */ | |
138 private void checkVertexForIntersections(Vertex vertex) { | |
139 if (vertex.nearestObstacle !is 0 || vertex.nearestObstacleChecked) | |
140 return; | |
141 int sideLength, x, y; | |
142 | |
143 sideLength = 2 * (vertex.totalCount * getSpacing()) + 1; | |
144 | |
145 if ((vertex.positionOnObstacle & PositionConstants.NORTH) > 0) | |
146 y = vertex.y - sideLength; | |
147 else | |
148 y = vertex.y; | |
149 if ((vertex.positionOnObstacle & PositionConstants.EAST) > 0) | |
150 x = vertex.x; | |
151 else | |
152 x = vertex.x - sideLength; | |
153 | |
154 Rectangle r = new Rectangle(x, y, sideLength, sideLength); | |
155 | |
156 int xDist, yDist; | |
157 | |
158 for (int o = 0; o < userObstacles.size(); o++) { | |
159 Obstacle obs = cast(Obstacle)userObstacles.get(o); | |
160 if (obs !is vertex.obs && r.intersects(obs)) { | |
161 int pos = obs.getPosition(vertex); | |
162 if (pos is 0) | |
163 continue; | |
164 | |
165 if ((pos & PositionConstants.NORTH) > 0) | |
166 // use top | |
167 yDist = obs.y - vertex.y; | |
168 else | |
169 // use bottom | |
170 yDist = vertex.y - obs.bottom() + 1; | |
171 if ((pos & PositionConstants.EAST) > 0) | |
172 // use right | |
173 xDist = vertex.x - obs.right() + 1; | |
174 else | |
175 // use left | |
176 xDist = obs.x - vertex.x; | |
177 | |
178 if (Math.max(xDist, yDist) < vertex.nearestObstacle | |
179 || vertex.nearestObstacle is 0) { | |
180 vertex.nearestObstacle = Math.max(xDist, yDist); | |
181 vertex.updateOffset(); | |
182 } | |
183 | |
184 } | |
185 } | |
186 | |
187 vertex.nearestObstacleChecked = true; | |
188 } | |
189 | |
190 /** | |
191 * Checks all vertices along paths for intersections | |
192 */ | |
193 private void checkVertexIntersections() { | |
194 for (int i = 0; i < workingPaths.size(); i++) { | |
195 Path path = cast(Path)workingPaths.get(i); | |
196 | |
197 for (int s = 0; s < path.segments.size() - 1; s++) { | |
198 Vertex vertex = (cast(Segment)path.segments.get(s)).end; | |
199 checkVertexForIntersections(vertex); | |
200 } | |
201 } | |
202 } | |
203 | |
204 /** | |
205 * Frees up fields which aren't needed between invocations. | |
206 */ | |
207 private void cleanup() { | |
208 for (int i = 0; i < workingPaths.size(); i++) { | |
209 Path path = cast(Path)workingPaths.get(i); | |
210 path.cleanup(); | |
211 } | |
212 } | |
213 | |
214 /** | |
215 * Counts how many paths are on given vertices in order to increment their total count. | |
216 */ | |
217 private void countVertices() { | |
218 for (int i = 0; i < workingPaths.size(); i++) { | |
219 Path path = cast(Path) workingPaths.get(i); | |
220 for (int v = 0; v < path.segments.size() - 1; v++) | |
221 (cast(Segment)path.segments.get(v)).end.totalCount++; | |
222 } | |
223 } | |
224 | |
225 /** | |
226 * Dirties the paths that are on the given vertex | |
227 * @param vertex the vertex that has the paths | |
228 */ | |
229 private bool dirtyPathsOn(Vertex vertex) { | |
230 List paths = vertex.paths; | |
231 if (paths !is null && paths.size() !is 0) { | |
232 for (int i = 0; i < paths.size(); i++) | |
233 (cast(Path)paths.get(i)).isDirty = true; | |
234 return true; | |
235 } | |
236 return false; | |
237 } | |
238 | |
239 /** | |
240 * Resyncs the parent paths with any new child paths that are necessary because bendpoints | |
241 * have been added to the parent path. | |
242 * | |
243 private void generateChildPaths() { | |
244 for (int i = 0; i < userPaths.size(); i++) { | |
245 Path path = (Path)userPaths.get(i); | |
246 PointList bendPoints = path.bendpoints; | |
247 if (bendPoints !is null && bendPoints.size() !is 0) { | |
248 List childPaths = new ArrayList(bendPoints.size() + 1); | |
249 Path child = null; | |
250 Vertex prevVertex = path.start; | |
251 Vertex currVertex = null; | |
252 | |
253 for (int b = 0; b < bendPoints.size(); b++) { | |
254 Point bp = (Point)bendPoints.getPoint(b); | |
255 currVertex = new Vertex(bp, null); | |
256 child = new Path(prevVertex, currVertex); | |
257 childPaths.add(child); | |
258 workingPaths.add(child); | |
259 prevVertex = currVertex; | |
260 } | |
261 | |
262 child = new Path(prevVertex, path.end); | |
263 childPaths.add(child); | |
264 workingPaths.add(child); | |
265 pathsToChildPaths.put(path, childPaths); | |
266 } else | |
267 workingPaths.add(path); | |
268 } //End FOR | |
269 }*/ | |
270 | |
271 /** | |
272 * Returns the closest vertex to the given segment. | |
273 * @param v1 the first vertex | |
274 * @param v2 the second vertex | |
275 * @param segment the segment | |
276 * @return v1, or v2 whichever is closest to the segment | |
277 */ | |
278 private Vertex getNearestVertex(Vertex v1, Vertex v2, Segment segment) { | |
279 if (segment.start.getDistance(v1) + segment.end.getDistance(v1) | |
280 > segment.start.getDistance(v2) + segment.end.getDistance(v2)) | |
281 return v2; | |
282 else | |
283 return v1; | |
284 } | |
285 | |
286 /** | |
287 * Returns the spacing maintained between paths. | |
288 * @return the default path spacing | |
289 * @see #setSpacing(int) | |
290 * @since 3.2 | |
291 */ | |
292 public int getSpacing() { | |
293 return spacing; | |
294 } | |
295 | |
296 /** | |
297 * Returns the subpath for a split on the given path at the given segment. | |
298 * @param path the path | |
299 * @param segment the segment | |
300 * @return the new subpath | |
301 */ | |
302 private Path getSubpathForSplit(Path path, Segment segment) { | |
303 Path newPath = path.getSubPath(segment); | |
304 workingPaths.add(newPath); | |
305 subPaths.add(newPath); | |
306 return newPath; | |
307 } | |
308 | |
309 /** | |
310 * Grows all obstacles in in routing and tests for new intersections | |
311 */ | |
312 private void growObstacles() { | |
313 growPassChangedObstacles = false; | |
314 for (int i = 0; i < NUM_GROW_PASSES; i++) { | |
315 if (i is 0 || growPassChangedObstacles) | |
316 growObstaclesPass(); | |
317 } | |
318 } | |
319 | |
320 /** | |
321 * Performs a single pass of the grow obstacles step, this can be repeated as desired. | |
322 * Grows obstacles, then tests paths against the grown obstacles. | |
323 */ | |
324 private void growObstaclesPass() { | |
325 // grow obstacles | |
326 for (int i = 0; i < userObstacles.size(); i++) | |
327 (cast(Obstacle)userObstacles.get(i)).growVertices(); | |
328 | |
329 // go through paths and test segments | |
330 for (int i = 0; i < workingPaths.size(); i++) { | |
331 Path path = cast(Path) workingPaths.get(i); | |
332 | |
333 for (int e = 0; e < path.excludedObstacles.size(); e++) | |
334 (cast(Obstacle)path.excludedObstacles.get(e)).exclude = true; | |
335 | |
336 if (path.grownSegments.size() is 0) { | |
337 for (int s = 0; s < path.segments.size(); s++) | |
338 testOffsetSegmentForIntersections(cast(Segment)path.segments.get(s), -1, path); | |
339 } else { | |
340 int counter = 0; | |
341 List currentSegments = new ArrayList(path.grownSegments); | |
342 for (int s = 0; s < currentSegments.size(); s++) | |
343 counter += testOffsetSegmentForIntersections(cast(Segment)currentSegments.get(s), s + counter, path); | |
344 } | |
345 | |
346 for (int e = 0; e < path.excludedObstacles.size(); e++) | |
347 (cast(Obstacle)path.excludedObstacles.get(e)).exclude = false; | |
348 | |
349 } | |
350 | |
351 // revert obstacles | |
352 for (int i = 0; i < userObstacles.size(); i++) | |
353 (cast(Obstacle)userObstacles.get(i)).shrinkVertices(); | |
354 } | |
355 | |
356 /** | |
357 * Adds an obstacle to the routing | |
358 * @param obs the obstacle | |
359 */ | |
360 private bool internalAddObstacle(Obstacle obs) { | |
361 userObstacles.add(obs); | |
362 return testAndDirtyPaths(obs); | |
363 } | |
364 | |
365 /** | |
366 * Removes an obstacle from the routing. | |
367 * @param rect the bounds of the obstacle | |
368 * @return the obstacle removed | |
369 */ | |
370 private bool internalRemoveObstacle(Rectangle rect) { | |
371 Obstacle obs = null; | |
372 int index = -1; | |
373 for (int i = 0; i < userObstacles.size(); i++) { | |
374 obs = cast(Obstacle)userObstacles.get(i); | |
375 if (obs.opEquals(rect)) { | |
376 index = i; | |
377 break; | |
378 } | |
379 } | |
380 | |
381 userObstacles.remove(index); | |
382 | |
383 bool result = false; | |
384 result |= dirtyPathsOn(obs.bottomLeft); | |
385 result |= dirtyPathsOn(obs.topLeft); | |
386 result |= dirtyPathsOn(obs.bottomRight); | |
387 result |= dirtyPathsOn(obs.topRight); | |
388 | |
389 for (int p = 0; p < workingPaths.size(); p++) { | |
390 Path path = cast(Path)workingPaths.get(p); | |
391 if (path.isDirty) | |
392 continue; | |
393 if (path.isObstacleVisible(obs)) | |
394 path.isDirty = result = true; | |
395 } | |
396 | |
397 return result; | |
398 } | |
399 | |
400 /** | |
401 * Labels the given path's vertices as innies, or outies, as well as determining if this | |
402 * path is inverted. | |
403 * @param path the path | |
404 */ | |
405 private void labelPath(Path path) { | |
406 Segment segment = null; | |
407 Segment nextSegment = null; | |
408 Vertex vertex = null; | |
409 bool agree = false; | |
410 for (int v = 0; v < path.grownSegments.size() - 1; v++) { | |
411 segment = cast(Segment) path.grownSegments.get(v); | |
412 nextSegment = cast(Segment) path.grownSegments.get(v + 1); | |
413 vertex = segment.end; | |
414 long crossProduct = segment.crossProduct(new Segment(vertex, vertex.obs.center)); | |
415 | |
416 if (vertex.type is Vertex.NOT_SET) { | |
417 labelVertex(segment, crossProduct, path); | |
418 } else if (!path.isInverted | |
419 && ((crossProduct > 0 && vertex.type is Vertex.OUTIE) | |
420 || (crossProduct < 0 && vertex.type is Vertex.INNIE))) { | |
421 if (agree) { | |
422 // split detected. | |
423 stack.push(getSubpathForSplit(path, segment)); | |
424 return; | |
425 } else { | |
426 path.isInverted = true; | |
427 path.invertPriorVertices(segment); | |
428 } | |
429 } else if (path.isInverted | |
430 && ((crossProduct < 0 && vertex.type is Vertex.OUTIE) | |
431 || (crossProduct > 0 && vertex.type is Vertex.INNIE))) { | |
432 // split detected. | |
433 stack.push(getSubpathForSplit(path, segment)); | |
434 return; | |
435 } else | |
436 agree = true; | |
437 | |
438 if (vertex.paths !is null) { | |
439 for (int i = 0;i < vertex.paths.size();i++) { | |
440 Path nextPath = cast(Path)vertex.paths.get(i); | |
441 if (!nextPath.isMarked) { | |
442 nextPath.isMarked = true; | |
443 stack.push(nextPath); | |
444 } | |
445 } | |
446 } | |
447 | |
448 vertex.addPath(path, segment, nextSegment); | |
449 } | |
450 } | |
451 | |
452 /** | |
453 * Labels all path's vertices in the routing. | |
454 */ | |
455 private void labelPaths() { | |
456 Path path = null; | |
457 for (int i = 0; i < workingPaths.size(); i++) { | |
458 path = cast(Path) workingPaths.get(i); | |
459 stack.push(path); | |
460 } | |
461 | |
462 while (!stack.isEmpty()) { | |
463 path = stack.pop(); | |
464 if (!path.isMarked) { | |
465 path.isMarked = true; | |
466 labelPath(path); | |
467 } | |
468 } | |
469 | |
470 // revert is marked so we can use it again in ordering. | |
471 for (int i = 0;i < workingPaths.size(); i++) { | |
472 path = cast(Path)workingPaths.get(i); | |
473 path.isMarked = false; | |
474 } | |
475 } | |
476 | |
477 /** | |
478 * Labels the vertex at the end of the semgent based on the cross product. | |
479 * @param segment the segment to this vertex | |
480 * @param crossProduct the cross product of this segment and a segment to the obstacles center | |
481 * @param path the path | |
482 */ | |
483 private void labelVertex(Segment segment, long crossProduct, Path path) { | |
484 // assumes vertex in question is segment.end | |
485 if (crossProduct > 0) { | |
486 if (path.isInverted) | |
487 segment.end.type = Vertex.OUTIE; | |
488 else | |
489 segment.end.type = Vertex.INNIE; | |
490 } else if (crossProduct < 0) { | |
491 if (path.isInverted) | |
492 segment.end.type = Vertex.INNIE; | |
493 else | |
494 segment.end.type = Vertex.OUTIE; | |
495 } else if (segment.start.type !is Vertex.NOT_SET) | |
496 segment.end.type = segment.start.type; | |
497 else | |
498 segment.end.type = Vertex.INNIE; | |
499 } | |
500 | |
501 /** | |
502 * Orders the path by comparing its angle at shared vertices with other paths. | |
503 * @param path the path | |
504 */ | |
505 private void orderPath(Path path) { | |
506 if (path.isMarked) | |
507 return; | |
508 path.isMarked = true; | |
509 Segment segment = null; | |
510 Vertex vertex = null; | |
511 for (int v = 0; v < path.grownSegments.size() - 1; v++) { | |
512 segment = cast(Segment) path.grownSegments.get(v); | |
513 vertex = segment.end; | |
514 double thisAngle = (cast(Double)vertex.cachedCosines.get(path)).doubleValue(); | |
515 if (path.isInverted) | |
516 thisAngle = -thisAngle; | |
517 | |
518 for (int i = 0; i < vertex.paths.size(); i++) { | |
519 Path vPath = cast(Path)vertex.paths.get(i); | |
520 if (!vPath.isMarked) { | |
521 double otherAngle = (cast(Double)vertex.cachedCosines.get(vPath)).doubleValue(); | |
522 | |
523 if (vPath.isInverted) | |
524 otherAngle = -otherAngle; | |
525 | |
526 if (otherAngle < thisAngle) | |
527 orderPath(vPath); | |
528 } | |
529 } | |
530 } | |
531 | |
532 orderedPaths.add(path); | |
533 } | |
534 | |
535 /** | |
536 * Orders all paths in the graph. | |
537 */ | |
538 private void orderPaths() { | |
539 for (int i = 0; i < workingPaths.size(); i++) { | |
540 Path path = cast(Path) workingPaths.get(i); | |
541 orderPath(path); | |
542 } | |
543 } | |
544 | |
545 /** | |
546 * Populates the parent paths with all the child paths that were created to represent | |
547 * bendpoints. | |
548 */ | |
549 private void recombineChildrenPaths() { | |
550 // only populate those paths with children paths. | |
551 Iterator keyItr = pathsToChildPaths.keySet().iterator(); | |
552 while (keyItr.hasNext()) { | |
553 Path path = cast(Path)keyItr.next(); | |
554 | |
555 path.fullReset(); | |
556 | |
557 List childPaths = cast(List)pathsToChildPaths.get(path); | |
558 Path childPath = null; | |
559 | |
560 for (int i = 0; i < childPaths.size(); i++) { | |
561 childPath = cast(Path)childPaths.get(i); | |
562 path.points.addAll(childPath.getPoints()); | |
563 // path will overlap | |
564 path.points.removePoint(path.points.size() - 1); | |
565 //path.grownSegments.addAll(childPath.grownSegments); | |
566 path.segments.addAll(childPath.segments); | |
567 path.visibleObstacles.addAll(childPath.visibleObstacles); | |
568 } | |
569 | |
570 // add last point. | |
571 path.points.addPoint(childPath.points.getLastPoint()); | |
572 } | |
573 } | |
574 | |
575 /** | |
576 * Reconnects all subpaths. | |
577 */ | |
578 private void recombineSubpaths() { | |
579 for (int p = 0; p < orderedPaths.size(); p++) { | |
580 Path path = cast(Path)orderedPaths.get(p); | |
581 path.reconnectSubPaths(); | |
582 } | |
583 | |
584 orderedPaths.removeAll(subPaths); | |
585 workingPaths.removeAll(subPaths); | |
586 subPaths = null; | |
587 } | |
588 | |
589 /** | |
590 * Removes the obstacle with the rectangle's bounds from the routing. | |
591 * | |
592 * @param rect the bounds of the obstacle to remove | |
593 * @return <code>true</code> if the removal has dirtied one or more paths | |
594 */ | |
595 public bool removeObstacle(Rectangle rect) { | |
596 return internalRemoveObstacle(rect); | |
597 } | |
598 | |
599 /** | |
600 * Removes the given path from the routing. | |
601 * | |
602 * @param path the path to remove. | |
603 * @return <code>true</code> if the removal may have affected one of the remaining paths | |
604 */ | |
605 public bool removePath(Path path) { | |
606 userPaths.remove(path); | |
607 List children = cast(List)pathsToChildPaths.get(path); | |
608 if (children is null) | |
609 workingPaths.remove(path); | |
610 else | |
611 workingPaths.removeAll(children); | |
612 return true; | |
613 } | |
614 | |
615 /** | |
616 * Resets exclude field on all obstacles | |
617 */ | |
618 private void resetObstacleExclusions() { | |
619 for (int i = 0; i < userObstacles.size(); i++) | |
620 (cast(Obstacle)userObstacles.get(i)).exclude = false; | |
621 } | |
622 | |
623 /** | |
624 * Resets all vertices found on paths and obstacles. | |
625 */ | |
626 private void resetVertices() { | |
627 for (int i = 0; i < userObstacles.size(); i++) { | |
628 Obstacle obs = cast(Obstacle)userObstacles.get(i); | |
629 obs.reset(); | |
630 } | |
631 for (int i = 0; i < workingPaths.size(); i++) { | |
632 Path path = cast(Path)workingPaths.get(i); | |
633 path.start.fullReset(); | |
634 path.end.fullReset(); | |
635 } | |
636 } | |
637 | |
638 /** | |
639 * Sets the default spacing between paths. The spacing is the minimum distance that path | |
640 * should be offset from other paths or obstacles. The default value is 4. When this value | |
641 * can not be satisfied, paths will be squeezed together uniformly. | |
642 * @param spacing the path spacing | |
643 * @since 3.2 | |
644 */ | |
645 public void setSpacing(int spacing) { | |
646 this.spacing = spacing; | |
647 } | |
648 | |
649 /** | |
650 * Updates the points in the paths in order to represent the current solution | |
651 * with the given paths and obstacles. | |
652 * | |
653 * @return returns the list of paths which were updated. | |
654 */ | |
655 public List solve() { | |
656 | |
657 solveDirtyPaths(); | |
658 | |
659 countVertices(); | |
660 checkVertexIntersections(); | |
661 growObstacles(); | |
662 | |
663 subPaths = new ArrayList(); | |
664 stack = new PathStack(); | |
665 labelPaths(); | |
666 stack = null; | |
667 | |
668 orderedPaths = new ArrayList(); | |
669 orderPaths(); | |
670 bendPaths(); | |
671 | |
672 recombineSubpaths(); | |
673 orderedPaths = null; | |
674 subPaths = null; | |
675 | |
676 recombineChildrenPaths(); | |
677 cleanup(); | |
678 | |
679 return Collections.unmodifiableList(userPaths); | |
680 } | |
681 | |
682 /** | |
683 * Solves paths that are dirty. | |
684 * @return number of dirty paths | |
685 */ | |
686 private int solveDirtyPaths() { | |
687 int numSolved = 0; | |
688 | |
689 for (int i = 0; i < userPaths.size(); i++) { | |
690 Path path = cast(Path)userPaths.get(i); | |
691 if (!path.isDirty) | |
692 continue; | |
693 List children = cast(List)pathsToChildPaths.get(path); | |
694 int prevCount = 1, newCount = 1; | |
695 if (children is null) | |
696 children = Collections.EMPTY_LIST; | |
697 else | |
698 prevCount = children.size(); | |
699 | |
700 if (path.getBendPoints() !is null) | |
701 newCount = path.getBendPoints().size() + 1; | |
702 | |
703 if (prevCount !is newCount) | |
704 children = regenerateChildPaths(path, children, prevCount, newCount); | |
705 refreshChildrenEndpoints(path, children); | |
706 } | |
707 | |
708 for (int i = 0; i < workingPaths.size(); i++) { | |
709 Path path = cast(Path)workingPaths.get(i); | |
710 path.refreshExcludedObstacles(userObstacles); | |
711 if (!path.isDirty) { | |
712 path.resetPartial(); | |
713 continue; | |
714 } | |
715 | |
716 numSolved++; | |
717 path.fullReset(); | |
718 | |
719 bool pathFoundCheck = path.generateShortestPath(userObstacles); | |
720 if (!pathFoundCheck || path.end.cost > path.threshold) { | |
721 // path not found, or path found was too long | |
722 resetVertices(); | |
723 path.fullReset(); | |
724 path.threshold = 0; | |
725 pathFoundCheck = path.generateShortestPath(userObstacles); | |
726 } | |
727 | |
728 resetVertices(); | |
729 } | |
730 | |
731 resetObstacleExclusions(); | |
732 | |
733 if (numSolved is 0) | |
734 resetVertices(); | |
735 | |
736 return numSolved; | |
737 } | |
738 | |
739 /** | |
740 * @since 3.0 | |
741 * @param path | |
742 * @param children | |
743 */ | |
744 private void refreshChildrenEndpoints(Path path, List children) { | |
745 Point previous = path.getStartPoint(); | |
746 Point next; | |
747 PointList bendpoints = path.getBendPoints(); | |
748 Path child; | |
749 | |
750 for (int i = 0; i < children.size(); i++) { | |
751 if (i < bendpoints.size()) | |
752 next = bendpoints.getPoint(i); | |
753 else | |
754 next = path.getEndPoint(); | |
755 child = cast(Path)children.get(i); | |
756 child.setStartPoint(previous); | |
757 child.setEndPoint(next); | |
758 previous = next; | |
759 } | |
760 } | |
761 | |
762 /** | |
763 * @since 3.0 | |
764 * @param path | |
765 * @param children | |
766 */ | |
767 private List regenerateChildPaths(Path path, List children, int currentSize, int newSize) { | |
768 //Path used to be simple but now is compound, children is EMPTY. | |
769 if (currentSize is 1) { | |
770 workingPaths.remove(path); | |
771 currentSize = 0; | |
772 children = new ArrayList(newSize); | |
773 pathsToChildPaths.put(path, cast(Object)children); | |
774 } else | |
775 //Path is becoming simple but was compound. children becomes empty. | |
776 if (newSize is 1) { | |
777 workingPaths.removeAll(children); | |
778 workingPaths.add(path); | |
779 pathsToChildPaths.remove(path); | |
780 return Collections.EMPTY_LIST; | |
781 } | |
782 | |
783 //Add new working paths until the sizes are the same | |
784 while (currentSize < newSize) { | |
785 Path child = new Path(); | |
786 workingPaths.add(child); | |
787 children.add(child); | |
788 currentSize++; | |
789 } | |
790 | |
791 while (currentSize > newSize) { | |
792 Path child = cast(Path)children.remove(children.size() - 1); | |
793 workingPaths.remove(child); | |
794 currentSize--; | |
795 } | |
796 | |
797 return children; | |
798 } | |
799 | |
800 /** | |
801 * Tests a segment that has been offset for new intersections | |
802 * @param segment the segment | |
803 * @param index the index of the segment along the path | |
804 * @param path the path | |
805 * @return 1 if new segments have been inserted | |
806 */ | |
807 private int testOffsetSegmentForIntersections(Segment segment, int index, Path path) { | |
808 for (int i = 0; i < userObstacles.size(); i++) { | |
809 Obstacle obs = cast(Obstacle) userObstacles.get(i); | |
810 | |
811 if (segment.end.obs is obs || segment.start.obs is obs || obs.exclude) | |
812 continue; | |
813 Vertex vertex = null; | |
814 | |
815 int offset = getSpacing(); | |
816 if (segment.getSlope() < 0) { | |
817 if (segment.intersects(obs.topLeft.x - offset, | |
818 obs.topLeft.y - offset, | |
819 obs.bottomRight.x + offset, | |
820 obs.bottomRight.y + offset)) | |
821 vertex = getNearestVertex(obs.topLeft, obs.bottomRight, segment); | |
822 else if (segment.intersects(obs.bottomLeft.x - offset, | |
823 obs.bottomLeft.y + offset, | |
824 obs.topRight.x + offset, | |
825 obs.topRight.y - offset)) | |
826 vertex = getNearestVertex(obs.bottomLeft, obs.topRight, segment); | |
827 } else { | |
828 if (segment.intersects(obs.bottomLeft.x - offset, | |
829 obs.bottomLeft.y + offset, | |
830 obs.topRight.x + offset, | |
831 obs.topRight.y - offset)) | |
832 vertex = getNearestVertex(obs.bottomLeft, obs.topRight, segment); | |
833 else if (segment.intersects(obs.topLeft.x - offset, | |
834 obs.topLeft.y - offset, | |
835 obs.bottomRight.x + offset, | |
836 obs.bottomRight.y + offset)) | |
837 vertex = getNearestVertex(obs.topLeft, obs.bottomRight, segment); | |
838 } | |
839 | |
840 if (vertex !is null) { | |
841 Rectangle vRect = vertex.getDeformedRectangle(offset); | |
842 if (segment.end.obs !is null) { | |
843 Rectangle endRect = segment.end.getDeformedRectangle(offset); | |
844 if (vRect.intersects(endRect)) | |
845 continue; | |
846 } | |
847 if (segment.start.obs !is null) { | |
848 Rectangle startRect = segment.start.getDeformedRectangle(offset); | |
849 if (vRect.intersects(startRect)) | |
850 continue; | |
851 } | |
852 | |
853 Segment newSegmentStart = new Segment(segment.start, vertex); | |
854 Segment newSegmentEnd = new Segment(vertex, segment.end); | |
855 | |
856 vertex.totalCount++; | |
857 vertex.nearestObstacleChecked = false; | |
858 | |
859 vertex.shrink(); | |
860 checkVertexForIntersections(vertex); | |
861 vertex.grow(); | |
862 | |
863 if (vertex.nearestObstacle !is 0) | |
864 vertex.updateOffset(); | |
865 | |
866 growPassChangedObstacles = true; | |
867 | |
868 if (index !is -1) { | |
869 path.grownSegments.remove(segment); | |
870 path.grownSegments.add(index, newSegmentStart); | |
871 path.grownSegments.add(index + 1, newSegmentEnd); | |
872 } else { | |
873 path.grownSegments.add(newSegmentStart); | |
874 path.grownSegments.add(newSegmentEnd); | |
875 } | |
876 return 1; | |
877 } | |
878 } | |
879 if (index is -1) | |
880 path.grownSegments.add(segment); | |
881 return 0; | |
882 } | |
883 | |
884 /** | |
885 * Tests all paths against the given obstacle | |
886 * @param obs the obstacle | |
887 */ | |
888 private bool testAndDirtyPaths(Obstacle obs) { | |
889 bool result = false; | |
890 for (int i = 0; i < workingPaths.size(); i++) { | |
891 Path path = cast(Path)workingPaths.get(i); | |
892 result |= path.testAndSet(obs); | |
893 } | |
894 return result; | |
895 } | |
896 | |
897 /** | |
898 * Updates the position of an existing obstacle. | |
899 * @param oldBounds the old bounds(used to find the obstacle) | |
900 * @param newBounds the new bounds | |
901 * @return <code>true</code> if the change the current results to become stale | |
902 */ | |
903 public bool updateObstacle(Rectangle oldBounds, Rectangle newBounds) { | |
904 bool result = internalRemoveObstacle(oldBounds); | |
905 result |= addObstacle(newBounds); | |
906 return result; | |
907 } | |
908 | |
909 } |