| /******************************************************************************* |
| * 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 |
| *******************************************************************************/ |
| package org.eclipse.draw2d.graph; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.draw2d.PositionConstants; |
| import org.eclipse.draw2d.geometry.Point; |
| import org.eclipse.draw2d.geometry.PointList; |
| import org.eclipse.draw2d.geometry.Rectangle; |
| |
| /** |
| * 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 extends ArrayList { |
| |
| Path pop() { |
| return (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 final int NUM_GROW_PASSES = 2; |
| |
| private int spacing = 4; |
| private boolean 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 ShortestPathRouter() { |
| 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 boolean 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 = (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 = (Segment) path.grownSegments.get(v); |
| Vertex vertex = segment.end; |
| |
| if (vertex != null && v < path.grownSegments.size() - 1) { |
| if (vertex.type == 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 != 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 = (Obstacle)userObstacles.get(o); |
| if (obs != vertex.obs && r.intersects(obs)) { |
| int pos = obs.getPosition(vertex); |
| if (pos == 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 == 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 = (Path)workingPaths.get(i); |
| |
| for (int s = 0; s < path.segments.size() - 1; s++) { |
| Vertex vertex = ((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 = (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 = (Path) workingPaths.get(i); |
| for (int v = 0; v < path.segments.size() - 1; v++) |
| ((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 boolean dirtyPathsOn(Vertex vertex) { |
| List paths = vertex.paths; |
| if (paths != null && paths.size() != 0) { |
| for (int i = 0; i < paths.size(); i++) |
| ((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 != null && bendPoints.size() != 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 == 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++) |
| ((Obstacle)userObstacles.get(i)).growVertices(); |
| |
| // go through paths and test segments |
| for (int i = 0; i < workingPaths.size(); i++) { |
| Path path = (Path) workingPaths.get(i); |
| |
| for (int e = 0; e < path.excludedObstacles.size(); e++) |
| ((Obstacle)path.excludedObstacles.get(e)).exclude = true; |
| |
| if (path.grownSegments.size() == 0) { |
| for (int s = 0; s < path.segments.size(); s++) |
| testOffsetSegmentForIntersections((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((Segment)currentSegments.get(s), s + counter, path); |
| } |
| |
| for (int e = 0; e < path.excludedObstacles.size(); e++) |
| ((Obstacle)path.excludedObstacles.get(e)).exclude = false; |
| |
| } |
| |
| // revert obstacles |
| for (int i = 0; i < userObstacles.size(); i++) |
| ((Obstacle)userObstacles.get(i)).shrinkVertices(); |
| } |
| |
| /** |
| * Adds an obstacle to the routing |
| * @param obs the obstacle |
| */ |
| private boolean 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 boolean internalRemoveObstacle(Rectangle rect) { |
| Obstacle obs = null; |
| int index = -1; |
| for (int i = 0; i < userObstacles.size(); i++) { |
| obs = (Obstacle)userObstacles.get(i); |
| if (obs.equals(rect)) { |
| index = i; |
| break; |
| } |
| } |
| |
| userObstacles.remove(index); |
| |
| boolean 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 = (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; |
| boolean agree = false; |
| for (int v = 0; v < path.grownSegments.size() - 1; v++) { |
| segment = (Segment) path.grownSegments.get(v); |
| nextSegment = (Segment) path.grownSegments.get(v + 1); |
| vertex = segment.end; |
| long crossProduct = segment.crossProduct(new Segment(vertex, vertex.obs.center)); |
| |
| if (vertex.type == Vertex.NOT_SET) { |
| labelVertex(segment, crossProduct, path); |
| } else if (!path.isInverted |
| && ((crossProduct > 0 && vertex.type == Vertex.OUTIE) |
| || (crossProduct < 0 && vertex.type == 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 == Vertex.OUTIE) |
| || (crossProduct > 0 && vertex.type == Vertex.INNIE))) { |
| // split detected. |
| stack.push(getSubpathForSplit(path, segment)); |
| return; |
| } else |
| agree = true; |
| |
| if (vertex.paths != null) { |
| for (int i = 0;i < vertex.paths.size();i++) { |
| Path nextPath = (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 = (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 = (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 != 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 = (Segment) path.grownSegments.get(v); |
| vertex = segment.end; |
| double thisAngle = ((Double)vertex.cachedCosines.get(path)).doubleValue(); |
| if (path.isInverted) |
| thisAngle = -thisAngle; |
| |
| for (int i = 0; i < vertex.paths.size(); i++) { |
| Path vPath = (Path)vertex.paths.get(i); |
| if (!vPath.isMarked) { |
| double otherAngle = ((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 = (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 = (Path)keyItr.next(); |
| |
| path.fullReset(); |
| |
| List childPaths = (List)pathsToChildPaths.get(path); |
| Path childPath = null; |
| |
| for (int i = 0; i < childPaths.size(); i++) { |
| childPath = (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 = (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 boolean 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 boolean removePath(Path path) { |
| userPaths.remove(path); |
| List children = (List)pathsToChildPaths.get(path); |
| if (children == 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++) |
| ((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 = (Obstacle)userObstacles.get(i); |
| obs.reset(); |
| } |
| for (int i = 0; i < workingPaths.size(); i++) { |
| Path path = (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 = (Path)userPaths.get(i); |
| if (!path.isDirty) |
| continue; |
| List children = (List)pathsToChildPaths.get(path); |
| int prevCount = 1, newCount = 1; |
| if (children == null) |
| children = Collections.EMPTY_LIST; |
| else |
| prevCount = children.size(); |
| |
| if (path.getBendPoints() != null) |
| newCount = path.getBendPoints().size() + 1; |
| |
| if (prevCount != newCount) |
| children = regenerateChildPaths(path, children, prevCount, newCount); |
| refreshChildrenEndpoints(path, children); |
| } |
| |
| for (int i = 0; i < workingPaths.size(); i++) { |
| Path path = (Path)workingPaths.get(i); |
| path.refreshExcludedObstacles(userObstacles); |
| if (!path.isDirty) { |
| path.resetPartial(); |
| continue; |
| } |
| |
| numSolved++; |
| path.fullReset(); |
| |
| boolean 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 == 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 = (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 == 1) { |
| workingPaths.remove(path); |
| currentSize = 0; |
| children = new ArrayList(newSize); |
| pathsToChildPaths.put(path, children); |
| } else |
| //Path is becoming simple but was compound. children becomes empty. |
| if (newSize == 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 = (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 = (Obstacle) userObstacles.get(i); |
| |
| if (segment.end.obs == obs || segment.start.obs == 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 != null) { |
| Rectangle vRect = vertex.getDeformedRectangle(offset); |
| if (segment.end.obs != null) { |
| Rectangle endRect = segment.end.getDeformedRectangle(offset); |
| if (vRect.intersects(endRect)) |
| continue; |
| } |
| if (segment.start.obs != 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 != 0) |
| vertex.updateOffset(); |
| |
| growPassChangedObstacles = true; |
| |
| if (index != -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 == -1) |
| path.grownSegments.add(segment); |
| return 0; |
| } |
| |
| /** |
| * Tests all paths against the given obstacle |
| * @param obs the obstacle |
| */ |
| private boolean testAndDirtyPaths(Obstacle obs) { |
| boolean result = false; |
| for (int i = 0; i < workingPaths.size(); i++) { |
| Path path = (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 boolean updateObstacle(Rectangle oldBounds, Rectangle newBounds) { |
| boolean result = internalRemoveObstacle(oldBounds); |
| result |= addObstacle(newBounds); |
| return result; |
| } |
| |
| } |