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 }