diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/draw2d/graph/ShortestPathRouter.d	Sun Aug 03 00:52:14 2008 +0200
@@ -0,0 +1,909 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.draw2d.graph.ShortestPathRouter;
+
+import dwt.dwthelper.utils;
+import dwtx.dwtxhelper.Collection;
+
+import dwtx.draw2d.PositionConstants;
+import dwtx.draw2d.geometry.Point;
+import dwtx.draw2d.geometry.PointList;
+import dwtx.draw2d.geometry.Rectangle;
+import dwtx.draw2d.graph.Path;
+import dwtx.draw2d.graph.Vertex;
+import dwtx.draw2d.graph.Segment;
+import dwtx.draw2d.graph.Obstacle;
+
+/**
+ * Bends a collection of {@link Path Paths} around rectangular obstacles. This class
+ * maintains a list of paths and obstacles. Updates can be made to the paths and/or
+ * obstacles, and then an incremental solve can be invoked.
+ * <P>
+ * The algorithm will attempt to find the shortest non-intersecting path between each
+ * path's start and end points. Once all paths have been found, they will be offset based
+ * on how many paths bend around the same corner of each obstacle.
+ * <P>
+ * The worst-case performance of this algorithm is p * s * n^2, where p is the number of
+ * paths, n is the number of obstacles, and s is the average number of segments in each
+ * path's final solution.
+ * <P>
+ * This class is not intended to be subclassed.
+ * @author Whitney Sorenson
+ * @author Randy Hudson
+ * @since 3.0
+ */
+public class ShortestPathRouter {
+
+/**
+ * A stack of Paths.
+ */
+static class PathStack : ArrayList {
+
+    Path pop() {
+        return cast(Path)remove(size() - 1);
+    }
+
+    void push(Path path) {
+        add(path);
+    }
+}
+
+/**
+ * The number of times to grow obstacles and test for intersections. This is a tradeoff
+ * between performance and quality of output.
+ */
+private static const int NUM_GROW_PASSES = 2;
+
+private int spacing = 4;
+private bool growPassChangedObstacles;
+private List orderedPaths;
+private Map pathsToChildPaths;
+
+private PathStack stack;
+private List subPaths;
+
+private List userObstacles;
+private List userPaths;
+private List workingPaths;
+
+/**
+ * Creates a new shortest path routing.
+ */
+public this() {
+    userPaths = new ArrayList();
+    workingPaths = new ArrayList();
+    pathsToChildPaths = new HashMap();
+    userObstacles = new ArrayList();
+}
+
+/**
+ * Adds an obstacle with the given bounds to the obstacles.
+ *
+ * @param rect the bounds of this obstacle
+ * @return <code>true</code> if the added obstacle has dirtied one or more paths
+ */
+public bool addObstacle(Rectangle rect) {
+    return internalAddObstacle(new Obstacle(rect, this));
+}
+
+/**
+ * Adds a path to the routing.
+ * @param path the path to add.
+ */
+public void addPath(Path path) {
+    userPaths.add(path);
+    workingPaths.add(path);
+}
+
+/**
+ * Fills the point lists of the Paths to the correct bent points.
+ */
+private void bendPaths() {
+    for (int i = 0; i < orderedPaths.size(); i++) {
+        Path path = cast(Path) orderedPaths.get(i);
+        Segment segment = null;
+        path.points.addPoint(new Point(path.start.x, path.start.y));
+        for (int v = 0; v < path.grownSegments.size(); v++) {
+            segment = cast(Segment) path.grownSegments.get(v);
+            Vertex vertex = segment.end;
+
+            if (vertex !is null && v < path.grownSegments.size() - 1) {
+                if (vertex.type is Vertex.INNIE) {
+                    vertex.count++;
+                    path.points.addPoint(vertex.bend(vertex.count));
+                } else {
+                    path.points.addPoint(vertex.bend(vertex.totalCount));
+                    vertex.totalCount--;
+                }
+            }
+        }
+        path.points.addPoint(new Point(path.end.x, path.end.y));
+    }
+}
+
+/**
+ * Checks a vertex to see if its offset should shrink
+ * @param vertex the vertex to check
+ */
+private void checkVertexForIntersections(Vertex vertex) {
+    if (vertex.nearestObstacle !is 0 || vertex.nearestObstacleChecked)
+        return;
+    int sideLength, x, y;
+
+    sideLength = 2 * (vertex.totalCount * getSpacing()) + 1;
+
+    if ((vertex.positionOnObstacle & PositionConstants.NORTH) > 0)
+        y = vertex.y - sideLength;
+    else
+        y = vertex.y;
+    if ((vertex.positionOnObstacle & PositionConstants.EAST) > 0)
+        x = vertex.x;
+    else
+        x = vertex.x - sideLength;
+
+    Rectangle r = new Rectangle(x, y, sideLength, sideLength);
+
+    int xDist, yDist;
+
+    for (int o = 0; o < userObstacles.size(); o++) {
+        Obstacle obs = cast(Obstacle)userObstacles.get(o);
+        if (obs !is vertex.obs && r.intersects(obs)) {
+            int pos = obs.getPosition(vertex);
+            if (pos is 0)
+                continue;
+
+            if ((pos & PositionConstants.NORTH) > 0)
+                //   use top
+                yDist = obs.y - vertex.y;
+            else
+                // use bottom
+                yDist = vertex.y - obs.bottom() + 1;
+            if ((pos & PositionConstants.EAST) > 0)
+                //   use right
+                xDist = vertex.x - obs.right() + 1;
+            else
+                //   use left
+                xDist = obs.x - vertex.x;
+
+            if (Math.max(xDist, yDist) < vertex.nearestObstacle
+                    || vertex.nearestObstacle is 0) {
+                vertex.nearestObstacle = Math.max(xDist, yDist);
+                vertex.updateOffset();
+            }
+
+        }
+    }
+
+    vertex.nearestObstacleChecked = true;
+}
+
+/**
+ * Checks all vertices along paths for intersections
+ */
+private void checkVertexIntersections() {
+    for (int i = 0; i < workingPaths.size(); i++) {
+        Path path = cast(Path)workingPaths.get(i);
+
+        for (int s = 0; s < path.segments.size() - 1; s++) {
+            Vertex vertex = (cast(Segment)path.segments.get(s)).end;
+            checkVertexForIntersections(vertex);
+        }
+    }
+}
+
+/**
+ * Frees up fields which aren't needed between invocations.
+ */
+private void cleanup() {
+    for (int i = 0; i < workingPaths.size(); i++) {
+        Path path = cast(Path)workingPaths.get(i);
+        path.cleanup();
+    }
+}
+
+/**
+ * Counts how many paths are on given vertices in order to increment their total count.
+ */
+private void countVertices() {
+    for (int i = 0; i < workingPaths.size(); i++) {
+        Path path = cast(Path) workingPaths.get(i);
+        for (int v = 0; v < path.segments.size() - 1; v++)
+            (cast(Segment)path.segments.get(v)).end.totalCount++;
+    }
+}
+
+/**
+ * Dirties the paths that are on the given vertex
+ * @param vertex the vertex that has the paths
+ */
+private bool dirtyPathsOn(Vertex vertex) {
+    List paths = vertex.paths;
+    if (paths !is null && paths.size() !is 0) {
+        for (int i = 0; i < paths.size(); i++)
+            (cast(Path)paths.get(i)).isDirty = true;
+        return true;
+    }
+    return false;
+}
+
+/**
+ * Resyncs the parent paths with any new child paths that are necessary because bendpoints
+ * have been added to the parent path.
+ *
+private void generateChildPaths() {
+    for (int i = 0; i < userPaths.size(); i++) {
+        Path path = (Path)userPaths.get(i);
+        PointList bendPoints = path.bendpoints;
+        if (bendPoints !is null && bendPoints.size() !is 0) {
+            List childPaths = new ArrayList(bendPoints.size() + 1);
+            Path child = null;
+            Vertex prevVertex = path.start;
+            Vertex currVertex = null;
+
+            for (int b = 0; b < bendPoints.size(); b++) {
+                Point bp = (Point)bendPoints.getPoint(b);
+                currVertex = new Vertex(bp, null);
+                child = new Path(prevVertex, currVertex);
+                childPaths.add(child);
+                workingPaths.add(child);
+                prevVertex = currVertex;
+            }
+
+            child = new Path(prevVertex, path.end);
+            childPaths.add(child);
+            workingPaths.add(child);
+            pathsToChildPaths.put(path, childPaths);
+        } else
+            workingPaths.add(path);
+    } //End FOR
+}*/
+
+/**
+ * Returns the closest vertex to the given segment.
+ * @param v1 the first vertex
+ * @param v2 the second vertex
+ * @param segment the segment
+ * @return v1, or v2 whichever is closest to the segment
+ */
+private Vertex getNearestVertex(Vertex v1, Vertex v2, Segment segment) {
+    if (segment.start.getDistance(v1) + segment.end.getDistance(v1)
+            > segment.start.getDistance(v2) + segment.end.getDistance(v2))
+        return v2;
+    else
+        return v1;
+}
+
+/**
+ * Returns the spacing maintained between paths.
+ * @return the default path spacing
+ * @see #setSpacing(int)
+ * @since 3.2
+ */
+public int getSpacing() {
+    return spacing;
+}
+
+/**
+ * Returns the subpath for a split on the given path at the given segment.
+ * @param path the path
+ * @param segment the segment
+ * @return the new subpath
+ */
+private Path getSubpathForSplit(Path path, Segment segment) {
+    Path newPath = path.getSubPath(segment);
+    workingPaths.add(newPath);
+    subPaths.add(newPath);
+    return newPath;
+}
+
+/**
+ * Grows all obstacles in in routing and tests for new intersections
+ */
+private void growObstacles() {
+    growPassChangedObstacles = false;
+    for (int i = 0; i < NUM_GROW_PASSES; i++) {
+        if (i is 0 || growPassChangedObstacles)
+            growObstaclesPass();
+    }
+}
+
+/**
+ * Performs a single pass of the grow obstacles step, this can be repeated as desired.
+ * Grows obstacles, then tests paths against the grown obstacles.
+ */
+private void growObstaclesPass() {
+    // grow obstacles
+    for (int i = 0; i < userObstacles.size(); i++)
+        (cast(Obstacle)userObstacles.get(i)).growVertices();
+
+    // go through paths and test segments
+    for (int i = 0; i < workingPaths.size(); i++) {
+        Path path = cast(Path) workingPaths.get(i);
+
+        for (int e = 0; e < path.excludedObstacles.size(); e++)
+            (cast(Obstacle)path.excludedObstacles.get(e)).exclude = true;
+
+        if (path.grownSegments.size() is 0) {
+            for (int s = 0; s < path.segments.size(); s++)
+                testOffsetSegmentForIntersections(cast(Segment)path.segments.get(s), -1, path);
+        } else {
+            int counter = 0;
+            List currentSegments = new ArrayList(path.grownSegments);
+            for (int s = 0; s < currentSegments.size(); s++)
+                counter += testOffsetSegmentForIntersections(cast(Segment)currentSegments.get(s), s + counter, path);
+        }
+
+        for (int e = 0; e < path.excludedObstacles.size(); e++)
+            (cast(Obstacle)path.excludedObstacles.get(e)).exclude = false;
+
+    }
+
+    // revert obstacles
+    for (int i = 0; i < userObstacles.size(); i++)
+        (cast(Obstacle)userObstacles.get(i)).shrinkVertices();
+}
+
+/**
+ * Adds an obstacle to the routing
+ * @param obs the obstacle
+ */
+private bool internalAddObstacle(Obstacle obs) {
+    userObstacles.add(obs);
+    return testAndDirtyPaths(obs);
+}
+
+/**
+ * Removes an obstacle from the routing.
+ * @param rect the bounds of the obstacle
+ * @return the obstacle removed
+ */
+private bool internalRemoveObstacle(Rectangle rect) {
+    Obstacle obs = null;
+    int index = -1;
+    for (int i = 0; i < userObstacles.size(); i++) {
+        obs = cast(Obstacle)userObstacles.get(i);
+        if (obs.opEquals(rect)) {
+            index = i;
+            break;
+        }
+    }
+
+    userObstacles.remove(index);
+
+    bool result = false;
+    result |= dirtyPathsOn(obs.bottomLeft);
+    result |= dirtyPathsOn(obs.topLeft);
+    result |= dirtyPathsOn(obs.bottomRight);
+    result |= dirtyPathsOn(obs.topRight);
+
+    for (int p = 0; p < workingPaths.size(); p++) {
+        Path path = cast(Path)workingPaths.get(p);
+        if (path.isDirty)
+            continue;
+        if (path.isObstacleVisible(obs))
+            path.isDirty = result = true;
+    }
+
+    return result;
+}
+
+/**
+ * Labels the given path's vertices as innies, or outies, as well as determining if this
+ * path is inverted.
+ * @param path the path
+ */
+private void labelPath(Path path) {
+    Segment segment = null;
+    Segment nextSegment = null;
+    Vertex vertex = null;
+    bool agree = false;
+    for (int v = 0; v < path.grownSegments.size() - 1; v++) {
+        segment = cast(Segment) path.grownSegments.get(v);
+        nextSegment = cast(Segment) path.grownSegments.get(v + 1);
+        vertex = segment.end;
+        long crossProduct = segment.crossProduct(new Segment(vertex, vertex.obs.center));
+
+        if (vertex.type is Vertex.NOT_SET) {
+            labelVertex(segment, crossProduct, path);
+        } else if (!path.isInverted
+                && ((crossProduct > 0 && vertex.type is Vertex.OUTIE)
+                        || (crossProduct < 0 && vertex.type is Vertex.INNIE))) {
+            if (agree) {
+                // split detected.
+                stack.push(getSubpathForSplit(path, segment));
+                return;
+            } else {
+                path.isInverted = true;
+                path.invertPriorVertices(segment);
+            }
+        } else if (path.isInverted
+                && ((crossProduct < 0 && vertex.type is Vertex.OUTIE)
+                        || (crossProduct > 0 && vertex.type is Vertex.INNIE))) {
+            // split detected.
+            stack.push(getSubpathForSplit(path, segment));
+            return;
+        } else
+            agree = true;
+
+        if (vertex.paths !is null) {
+            for (int i = 0;i < vertex.paths.size();i++) {
+                Path nextPath = cast(Path)vertex.paths.get(i);
+                if (!nextPath.isMarked) {
+                    nextPath.isMarked = true;
+                    stack.push(nextPath);
+                }
+            }
+        }
+
+        vertex.addPath(path, segment, nextSegment);
+    }
+}
+
+/**
+ * Labels all path's vertices in the routing.
+ */
+private void labelPaths() {
+    Path path = null;
+    for (int i = 0; i < workingPaths.size(); i++) {
+        path = cast(Path) workingPaths.get(i);
+        stack.push(path);
+    }
+
+    while (!stack.isEmpty()) {
+        path = stack.pop();
+        if (!path.isMarked) {
+            path.isMarked = true;
+            labelPath(path);
+        }
+    }
+
+    // revert is marked so we can use it again in ordering.
+    for (int i = 0;i < workingPaths.size(); i++) {
+        path = cast(Path)workingPaths.get(i);
+        path.isMarked = false;
+    }
+}
+
+/**
+ * Labels the vertex at the end of the semgent based on the cross product.
+ * @param segment the segment to this vertex
+ * @param crossProduct the cross product of this segment and a segment to the obstacles center
+ * @param path the path
+ */
+private void labelVertex(Segment segment, long crossProduct, Path path) {
+//   assumes vertex in question is segment.end
+    if (crossProduct > 0) {
+        if (path.isInverted)
+            segment.end.type = Vertex.OUTIE;
+        else
+            segment.end.type = Vertex.INNIE;
+    } else if (crossProduct < 0) {
+        if (path.isInverted)
+            segment.end.type = Vertex.INNIE;
+        else
+            segment.end.type = Vertex.OUTIE;
+    } else if (segment.start.type !is Vertex.NOT_SET)
+        segment.end.type = segment.start.type;
+    else
+        segment.end.type = Vertex.INNIE;
+}
+
+/**
+ * Orders the path by comparing its angle at shared vertices with other paths.
+ * @param path the path
+ */
+private void orderPath(Path path) {
+    if (path.isMarked)
+        return;
+    path.isMarked = true;
+    Segment segment = null;
+    Vertex vertex = null;
+    for (int v = 0; v < path.grownSegments.size() - 1; v++) {
+        segment = cast(Segment) path.grownSegments.get(v);
+        vertex = segment.end;
+        double thisAngle = (cast(Double)vertex.cachedCosines.get(path)).doubleValue();
+        if (path.isInverted)
+            thisAngle = -thisAngle;
+
+        for (int i = 0; i < vertex.paths.size(); i++) {
+            Path vPath = cast(Path)vertex.paths.get(i);
+            if (!vPath.isMarked) {
+                double otherAngle = (cast(Double)vertex.cachedCosines.get(vPath)).doubleValue();
+
+                if (vPath.isInverted)
+                    otherAngle = -otherAngle;
+
+                if (otherAngle < thisAngle)
+                    orderPath(vPath);
+            }
+        }
+    }
+
+    orderedPaths.add(path);
+}
+
+/**
+ * Orders all paths in the graph.
+ */
+private void orderPaths() {
+    for (int i = 0; i < workingPaths.size(); i++) {
+        Path path = cast(Path) workingPaths.get(i);
+        orderPath(path);
+    }
+}
+
+/**
+ * Populates the parent paths with all the child paths that were created to represent
+ * bendpoints.
+ */
+private void recombineChildrenPaths() {
+    // only populate those paths with children paths.
+    Iterator keyItr = pathsToChildPaths.keySet().iterator();
+    while (keyItr.hasNext()) {
+        Path path = cast(Path)keyItr.next();
+
+        path.fullReset();
+
+        List childPaths = cast(List)pathsToChildPaths.get(path);
+        Path childPath = null;
+
+        for (int i = 0; i < childPaths.size(); i++) {
+            childPath = cast(Path)childPaths.get(i);
+            path.points.addAll(childPath.getPoints());
+            // path will overlap
+            path.points.removePoint(path.points.size() - 1);
+            //path.grownSegments.addAll(childPath.grownSegments);
+            path.segments.addAll(childPath.segments);
+            path.visibleObstacles.addAll(childPath.visibleObstacles);
+        }
+
+        // add last point.
+        path.points.addPoint(childPath.points.getLastPoint());
+    }
+}
+
+/**
+ * Reconnects all subpaths.
+ */
+private void recombineSubpaths() {
+    for (int p = 0; p < orderedPaths.size(); p++) {
+        Path path = cast(Path)orderedPaths.get(p);
+        path.reconnectSubPaths();
+    }
+
+    orderedPaths.removeAll(subPaths);
+    workingPaths.removeAll(subPaths);
+    subPaths = null;
+}
+
+/**
+ * Removes the obstacle with the rectangle's bounds from the routing.
+ *
+ * @param rect the bounds of the obstacle to remove
+ * @return <code>true</code> if the removal has dirtied one or more paths
+ */
+public bool removeObstacle(Rectangle rect) {
+    return internalRemoveObstacle(rect);
+}
+
+/**
+ * Removes the given path from the routing.
+ *
+ * @param path the path to remove.
+ * @return <code>true</code> if the removal may have affected one of the remaining paths
+ */
+public bool removePath(Path path) {
+    userPaths.remove(path);
+    List children = cast(List)pathsToChildPaths.get(path);
+    if (children is null)
+        workingPaths.remove(path);
+    else
+        workingPaths.removeAll(children);
+    return true;
+}
+
+/**
+ * Resets exclude field on all obstacles
+ */
+private void resetObstacleExclusions() {
+    for (int i = 0; i < userObstacles.size(); i++)
+        (cast(Obstacle)userObstacles.get(i)).exclude = false;
+}
+
+/**
+ * Resets all vertices found on paths and obstacles.
+ */
+private void resetVertices() {
+    for (int i = 0; i < userObstacles.size(); i++) {
+        Obstacle obs = cast(Obstacle)userObstacles.get(i);
+        obs.reset();
+    }
+    for (int i = 0; i < workingPaths.size(); i++) {
+        Path path = cast(Path)workingPaths.get(i);
+        path.start.fullReset();
+        path.end.fullReset();
+    }
+}
+
+/**
+ * Sets the default spacing between paths. The spacing is the minimum distance that path
+ * should be offset from other paths or obstacles. The default value is 4. When this value
+ * can not be satisfied, paths will be squeezed together uniformly.
+ * @param spacing the path spacing
+ * @since 3.2
+ */
+public void setSpacing(int spacing) {
+    this.spacing = spacing;
+}
+
+/**
+ * Updates the points in the paths in order to represent the current solution
+ * with the given paths and obstacles.
+ *
+ * @return returns the list of paths which were updated.
+ */
+public List solve() {
+
+    solveDirtyPaths();
+
+    countVertices();
+    checkVertexIntersections();
+    growObstacles();
+
+    subPaths = new ArrayList();
+    stack = new PathStack();
+    labelPaths();
+    stack = null;
+
+    orderedPaths = new ArrayList();
+    orderPaths();
+    bendPaths();
+
+    recombineSubpaths();
+    orderedPaths = null;
+    subPaths = null;
+
+    recombineChildrenPaths();
+    cleanup();
+
+    return Collections.unmodifiableList(userPaths);
+}
+
+/**
+ * Solves paths that are dirty.
+ * @return number of dirty paths
+ */
+private int solveDirtyPaths() {
+    int numSolved = 0;
+
+    for (int i = 0; i < userPaths.size(); i++) {
+        Path path = cast(Path)userPaths.get(i);
+        if (!path.isDirty)
+            continue;
+        List children = cast(List)pathsToChildPaths.get(path);
+        int prevCount = 1, newCount = 1;
+        if (children is null)
+            children = Collections.EMPTY_LIST;
+        else
+            prevCount = children.size();
+
+        if (path.getBendPoints() !is null)
+            newCount = path.getBendPoints().size() + 1;
+
+        if (prevCount !is newCount)
+            children = regenerateChildPaths(path, children, prevCount, newCount);
+        refreshChildrenEndpoints(path, children);
+    }
+
+    for (int i = 0; i < workingPaths.size(); i++) {
+        Path path = cast(Path)workingPaths.get(i);
+        path.refreshExcludedObstacles(userObstacles);
+        if (!path.isDirty) {
+            path.resetPartial();
+            continue;
+        }
+
+        numSolved++;
+        path.fullReset();
+
+        bool pathFoundCheck = path.generateShortestPath(userObstacles);
+        if (!pathFoundCheck || path.end.cost > path.threshold) {
+            // path not found, or path found was too long
+            resetVertices();
+            path.fullReset();
+            path.threshold = 0;
+            pathFoundCheck = path.generateShortestPath(userObstacles);
+        }
+
+        resetVertices();
+    }
+
+    resetObstacleExclusions();
+
+    if (numSolved is 0)
+        resetVertices();
+
+    return numSolved;
+}
+
+/**
+ * @since 3.0
+ * @param path
+ * @param children
+ */
+private void refreshChildrenEndpoints(Path path, List children) {
+    Point previous = path.getStartPoint();
+    Point next;
+    PointList bendpoints = path.getBendPoints();
+    Path child;
+
+    for (int i = 0; i < children.size(); i++) {
+        if (i < bendpoints.size())
+            next = bendpoints.getPoint(i);
+        else
+            next = path.getEndPoint();
+        child = cast(Path)children.get(i);
+        child.setStartPoint(previous);
+        child.setEndPoint(next);
+        previous = next;
+    }
+}
+
+/**
+ * @since 3.0
+ * @param path
+ * @param children
+ */
+private List regenerateChildPaths(Path path, List children, int currentSize, int newSize) {
+    //Path used to be simple but now is compound, children is EMPTY.
+    if (currentSize is 1) {
+        workingPaths.remove(path);
+        currentSize = 0;
+        children = new ArrayList(newSize);
+        pathsToChildPaths.put(path, cast(Object)children);
+    } else
+    //Path is becoming simple but was compound.  children becomes empty.
+    if (newSize is 1) {
+        workingPaths.removeAll(children);
+        workingPaths.add(path);
+        pathsToChildPaths.remove(path);
+        return Collections.EMPTY_LIST;
+    }
+
+    //Add new working paths until the sizes are the same
+    while (currentSize < newSize) {
+        Path child = new Path();
+        workingPaths.add(child);
+        children.add(child);
+        currentSize++;
+    }
+
+    while (currentSize > newSize) {
+        Path child = cast(Path)children.remove(children.size() - 1);
+        workingPaths.remove(child);
+        currentSize--;
+    }
+
+    return children;
+}
+
+/**
+ * Tests a segment that has been offset for new intersections
+ * @param segment the segment
+ * @param index the index of the segment along the path
+ * @param path the path
+ * @return 1 if new segments have been inserted
+ */
+private int testOffsetSegmentForIntersections(Segment segment, int index, Path path) {
+    for (int i = 0; i < userObstacles.size(); i++) {
+        Obstacle obs = cast(Obstacle) userObstacles.get(i);
+
+        if (segment.end.obs is obs || segment.start.obs is obs || obs.exclude)
+            continue;
+        Vertex vertex = null;
+
+        int offset = getSpacing();
+        if (segment.getSlope() < 0) {
+            if (segment.intersects(obs.topLeft.x - offset,
+                    obs.topLeft.y - offset,
+                    obs.bottomRight.x + offset,
+                    obs.bottomRight.y + offset))
+                vertex = getNearestVertex(obs.topLeft, obs.bottomRight, segment);
+            else if (segment.intersects(obs.bottomLeft.x - offset,
+                    obs.bottomLeft.y + offset,
+                    obs.topRight.x + offset,
+                    obs.topRight.y - offset))
+                vertex = getNearestVertex(obs.bottomLeft, obs.topRight, segment);
+        } else {
+            if (segment.intersects(obs.bottomLeft.x - offset,
+                    obs.bottomLeft.y + offset,
+                    obs.topRight.x + offset,
+                    obs.topRight.y - offset))
+                vertex = getNearestVertex(obs.bottomLeft, obs.topRight, segment);
+            else if (segment.intersects(obs.topLeft.x - offset,
+                    obs.topLeft.y - offset,
+                    obs.bottomRight.x + offset,
+                    obs.bottomRight.y + offset))
+                vertex = getNearestVertex(obs.topLeft, obs.bottomRight, segment);
+        }
+
+        if (vertex !is null) {
+            Rectangle vRect = vertex.getDeformedRectangle(offset);
+            if (segment.end.obs !is null) {
+                Rectangle endRect = segment.end.getDeformedRectangle(offset);
+                if (vRect.intersects(endRect))
+                    continue;
+            }
+            if (segment.start.obs !is null) {
+                Rectangle startRect = segment.start.getDeformedRectangle(offset);
+                if (vRect.intersects(startRect))
+                    continue;
+            }
+
+            Segment newSegmentStart = new Segment(segment.start, vertex);
+            Segment newSegmentEnd = new Segment(vertex, segment.end);
+
+            vertex.totalCount++;
+            vertex.nearestObstacleChecked = false;
+
+            vertex.shrink();
+            checkVertexForIntersections(vertex);
+            vertex.grow();
+
+            if (vertex.nearestObstacle !is 0)
+                vertex.updateOffset();
+
+            growPassChangedObstacles = true;
+
+            if (index !is -1) {
+                path.grownSegments.remove(segment);
+                path.grownSegments.add(index, newSegmentStart);
+                path.grownSegments.add(index + 1, newSegmentEnd);
+            } else {
+                path.grownSegments.add(newSegmentStart);
+                path.grownSegments.add(newSegmentEnd);
+            }
+            return 1;
+        }
+    }
+    if (index is -1)
+        path.grownSegments.add(segment);
+    return 0;
+}
+
+/**
+ * Tests all paths against the given obstacle
+ * @param obs the obstacle
+ */
+private bool testAndDirtyPaths(Obstacle obs) {
+    bool result = false;
+    for (int i = 0; i < workingPaths.size(); i++) {
+        Path path = cast(Path)workingPaths.get(i);
+        result |= path.testAndSet(obs);
+    }
+    return result;
+}
+
+/**
+ * Updates the position of an existing obstacle.
+ * @param oldBounds the old bounds(used to find the obstacle)
+ * @param newBounds the new bounds
+ * @return <code>true</code> if the change the current results to become stale
+ */
+public bool updateObstacle(Rectangle oldBounds, Rectangle newBounds) {
+    bool result = internalRemoveObstacle(oldBounds);
+    result |= addObstacle(newBounds);
+    return result;
+}
+
+}