
org.eclipse.draw2d.graph.Path Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of draw2d Show documentation
Show all versions of draw2d Show documentation
Eclipse GEF Draw2D Library
The newest version!
/*******************************************************************************
* Copyright (c) 2004, 2010 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.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
/**
* A Path representation for the ShortestPathRouting. A Path has a start and end
* point and may have bendpoints. The output of a path is accessed via the
* method getPoints()
.
*
* This class is for internal use only.
*
* @author Whitney Sorenson
* @since 3.0
*/
public class Path {
/**
* A Stack of segments.
*/
private static class SegmentStack extends ArrayList {
Segment pop() {
return (Segment) remove(size() - 1);
}
Obstacle popObstacle() {
return (Obstacle) remove(size() - 1);
}
void push(Object obj) {
add(obj);
}
}
private static final Point CURRENT = new Point();
private static final double EPSILON = 1.04;
private static final Point NEXT = new Point();
private static final double OVAL_CONSTANT = 1.13;
/**
* The bendpoint constraints. The path must go through these bendpoints.
*/
PointList bendpoints;
/**
* An arbitrary data field which can be used to map a Path back to some
* client object.
*/
public Object data;
List excludedObstacles;
List grownSegments;
/**
* this field is for internal use only. It is true whenever a property has
* been changed which requires the solver to resolve this path.
*/
public boolean isDirty = true;
boolean isInverted = false;
boolean isMarked = false;
PointList points;
/**
* The previous cost ratio of the path. The cost ratio is the actual path
* length divided by the length from the start to the end.
*/
private double prevCostRatio;
List segments;
private SegmentStack stack;
Vertex start, end;
private Path subPath;
double threshold;
Set visibleObstacles;
Set visibleVertices;
/**
* Constructs a new path.
*
* @since 3.0
*/
public Path() {
segments = new ArrayList();
grownSegments = new ArrayList();
points = new PointList();
visibleVertices = new HashSet();
stack = new SegmentStack();
visibleObstacles = new HashSet();
excludedObstacles = new ArrayList();
}
/**
* Constructs a new path with the given data.
*
* @since 3.0
* @param data
* an arbitrary data field
*/
public Path(Object data) {
this();
this.data = data;
}
/**
* Constructs a new path with the given data, start and end point.
*
* @param start
* the start point for this path
* @param end
* the end point for this path
*/
public Path(Point start, Point end) {
this(new Vertex(start, null), new Vertex(end, null));
}
/**
* Creates a path between the given vertices.
*
* @param start
* start vertex
* @param end
* end vertex
*/
Path(Vertex start, Vertex end) {
this();
this.start = start;
this.end = end;
}
/**
* Attempts to add all segments between the given obstacles to the
* visibility graph.
*
* @param source
* the source obstacle
* @param target
* the target obstacle
*/
private void addAllSegmentsBetween(Obstacle source, Obstacle target) {
addConnectingSegment(new Segment(source.bottomLeft, target.bottomLeft),
source, target, false, false);
addConnectingSegment(
new Segment(source.bottomRight, target.bottomRight), source,
target, true, true);
addConnectingSegment(new Segment(source.topLeft, target.topLeft),
source, target, true, true);
addConnectingSegment(new Segment(source.topRight, target.topRight),
source, target, false, false);
if (source.bottom() == target.bottom()) {
addConnectingSegment(new Segment(source.bottomLeft,
target.bottomRight), source, target, false, true);
addConnectingSegment(new Segment(source.bottomRight,
target.bottomLeft), source, target, true, false);
}
if (source.y == target.y) {
addConnectingSegment(new Segment(source.topLeft, target.topRight),
source, target, true, false);
addConnectingSegment(new Segment(source.topRight, target.topLeft),
source, target, false, true);
}
if (source.x == target.x) {
addConnectingSegment(
new Segment(source.bottomLeft, target.topLeft), source,
target, false, true);
addConnectingSegment(
new Segment(source.topLeft, target.bottomLeft), source,
target, true, false);
}
if (source.right() == target.right()) {
addConnectingSegment(new Segment(source.bottomRight,
target.topRight), source, target, true, false);
addConnectingSegment(new Segment(source.topRight,
target.bottomRight), source, target, false, true);
}
}
/**
* Attempts to add a segment between the given obstacles to the visibility
* graph. This method is specifically written for the case where the two
* obstacles intersect and contains a boolean as to whether to check the
* diagonal that includes the top right point of the other obstacle.
*
* @param segment
* the segment to check
* @param o1
* the first obstacle
* @param o2
* the second obstacle
* @param checkTopRight1
* whether or not to check the diagonal containing top right
* point
*/
private void addConnectingSegment(Segment segment, Obstacle o1,
Obstacle o2, boolean checkTopRight1, boolean checkTopRight2) {
if (threshold != 0
&& (segment.end.getDistance(end)
+ segment.end.getDistance(start) > threshold || segment.start
.getDistance(end) + segment.start.getDistance(start) > threshold))
return;
if (o2.containsProper(segment.start) || o1.containsProper(segment.end))
return;
if (checkTopRight1
&& segment.intersects(o1.x, o1.bottom() - 1, o1.right() - 1,
o1.y))
return;
if (checkTopRight2
&& segment.intersects(o2.x, o2.bottom() - 1, o2.right() - 1,
o2.y))
return;
if (!checkTopRight1
&& segment.intersects(o1.x, o1.y, o1.right() - 1,
o1.bottom() - 1))
return;
if (!checkTopRight2
&& segment.intersects(o2.x, o2.y, o2.right() - 1,
o2.bottom() - 1))
return;
stack.push(o1);
stack.push(o2);
stack.push(segment);
}
/**
* Adds an obstacle to the visibility graph and generates new segments
*
* @param newObs
* the new obstacle, should not be in the graph already
*/
private void addObstacle(Obstacle newObs) {
visibleObstacles.add(newObs);
Iterator oItr = new HashSet(visibleObstacles).iterator();
while (oItr.hasNext()) {
Obstacle currObs = (Obstacle) oItr.next();
if (newObs != currObs)
addSegmentsFor(newObs, currObs);
}
addPerimiterSegments(newObs);
addSegmentsFor(start, newObs);
addSegmentsFor(end, newObs);
}
/**
* Adds the segments along the perimiter of an obstacle to the visiblity
* graph queue.
*
* @param obs
* the obstacle
*/
private void addPerimiterSegments(Obstacle obs) {
Segment seg = new Segment(obs.topLeft, obs.topRight);
stack.push(obs);
stack.push(null);
stack.push(seg);
seg = new Segment(obs.topRight, obs.bottomRight);
stack.push(obs);
stack.push(null);
stack.push(seg);
seg = new Segment(obs.bottomRight, obs.bottomLeft);
stack.push(obs);
stack.push(null);
stack.push(seg);
seg = new Segment(obs.bottomLeft, obs.topLeft);
stack.push(obs);
stack.push(null);
stack.push(seg);
}
/**
* Attempts to add a segment to the visibility graph. First checks to see if
* the segment is outside the threshold oval. Then it compares the segment
* against all obstacles. If it is clean, the segment is finally added to
* the graph.
*
* @param segment
* the segment
* @param exclude1
* an obstacle to exclude from the search
* @param exclude2
* another obstacle to exclude from the search
* @param allObstacles
* the list of all obstacles
*/
private void addSegment(Segment segment, Obstacle exclude1,
Obstacle exclude2, List allObstacles) {
if (threshold != 0
&& (segment.end.getDistance(end)
+ segment.end.getDistance(start) > threshold || segment.start
.getDistance(end) + segment.start.getDistance(start) > threshold))
return;
for (int i = 0; i < allObstacles.size(); i++) {
Obstacle obs = (Obstacle) allObstacles.get(i);
if (obs == exclude1 || obs == exclude2 || obs.exclude)
continue;
if (segment.intersects(obs.x, obs.y, obs.right() - 1,
obs.bottom() - 1)
|| segment.intersects(obs.x, obs.bottom() - 1,
obs.right() - 1, obs.y)
|| obs.containsProper(segment.start)
|| obs.containsProper(segment.end)) {
if (!visibleObstacles.contains(obs))
addObstacle(obs);
return;
}
}
linkVertices(segment);
}
/**
* Adds the segments between the given obstacles.
*
* @param source
* source obstacle
* @param target
* target obstacle
*/
private void addSegmentsFor(Obstacle source, Obstacle target) {
if (source.intersects(target))
addAllSegmentsBetween(source, target);
else if (target.bottom() - 1 < source.y)
addSegmentsTargetAboveSource(source, target);
else if (source.bottom() - 1 < target.y)
addSegmentsTargetAboveSource(target, source);
else if (target.right() - 1 < source.x)
addSegmentsTargetBesideSource(source, target);
else
addSegmentsTargetBesideSource(target, source);
}
/**
* Adds the segments between the given obstacles.
*
* @param source
* source obstacle
* @param target
* target obstacle
*/
private void addSegmentsFor(Vertex vertex, Obstacle obs) {
Segment seg = null;
Segment seg2 = null;
switch (obs.getPosition(vertex)) {
case PositionConstants.SOUTH_WEST:
case PositionConstants.NORTH_EAST:
seg = new Segment(vertex, obs.topLeft);
seg2 = new Segment(vertex, obs.bottomRight);
break;
case PositionConstants.SOUTH_EAST:
case PositionConstants.NORTH_WEST:
seg = new Segment(vertex, obs.topRight);
seg2 = new Segment(vertex, obs.bottomLeft);
break;
case PositionConstants.NORTH:
seg = new Segment(vertex, obs.topLeft);
seg2 = new Segment(vertex, obs.topRight);
break;
case PositionConstants.EAST:
seg = new Segment(vertex, obs.bottomRight);
seg2 = new Segment(vertex, obs.topRight);
break;
case PositionConstants.SOUTH:
seg = new Segment(vertex, obs.bottomRight);
seg2 = new Segment(vertex, obs.bottomLeft);
break;
case PositionConstants.WEST:
seg = new Segment(vertex, obs.topLeft);
seg2 = new Segment(vertex, obs.bottomLeft);
break;
default:
if (vertex.x == obs.x) {
seg = new Segment(vertex, obs.topLeft);
seg2 = new Segment(vertex, obs.bottomLeft);
} else if (vertex.y == obs.y) {
seg = new Segment(vertex, obs.topLeft);
seg2 = new Segment(vertex, obs.topRight);
} else if (vertex.y == obs.bottom() - 1) {
seg = new Segment(vertex, obs.bottomLeft);
seg2 = new Segment(vertex, obs.bottomRight);
} else if (vertex.x == obs.right() - 1) {
seg = new Segment(vertex, obs.topRight);
seg2 = new Segment(vertex, obs.bottomRight);
} else {
throw new RuntimeException("Unexpected vertex conditions"); //$NON-NLS-1$
}
}
stack.push(obs);
stack.push(null);
stack.push(seg);
stack.push(obs);
stack.push(null);
stack.push(seg2);
}
private void addSegmentsTargetAboveSource(Obstacle source, Obstacle target) {
// target located above source
Segment seg = null;
Segment seg2 = null;
if (target.x > source.x) {
seg = new Segment(source.topLeft, target.topLeft);
if (target.x < source.right() - 1)
seg2 = new Segment(source.topRight, target.bottomLeft);
else
seg2 = new Segment(source.bottomRight, target.topLeft);
} else if (source.x == target.x) {
seg = new Segment(source.topLeft, target.bottomLeft);
seg2 = new Segment(source.topRight, target.bottomLeft);
} else {
seg = new Segment(source.bottomLeft, target.bottomLeft);
seg2 = new Segment(source.topRight, target.bottomLeft);
}
stack.push(source);
stack.push(target);
stack.push(seg);
stack.push(source);
stack.push(target);
stack.push(seg2);
seg = null;
seg2 = null;
if (target.right() < source.right()) {
seg = new Segment(source.topRight, target.topRight);
if (target.right() - 1 > source.x)
seg2 = new Segment(source.topLeft, target.bottomRight);
else
seg2 = new Segment(source.bottomLeft, target.topRight);
} else if (source.right() == target.right()) {
seg = new Segment(source.topRight, target.bottomRight);
seg2 = new Segment(source.topLeft, target.bottomRight);
} else {
seg = new Segment(source.bottomRight, target.bottomRight);
seg2 = new Segment(source.topLeft, target.bottomRight);
}
stack.push(source);
stack.push(target);
stack.push(seg);
stack.push(source);
stack.push(target);
stack.push(seg2);
}
private void addSegmentsTargetBesideSource(Obstacle source, Obstacle target) {
// target located above source
Segment seg = null;
Segment seg2 = null;
if (target.y > source.y) {
seg = new Segment(source.topLeft, target.topLeft);
if (target.y < source.bottom() - 1)
seg2 = new Segment(source.bottomLeft, target.topRight);
else
seg2 = new Segment(source.bottomRight, target.topLeft);
} else if (source.y == target.y) {
// degenerate case
seg = new Segment(source.topLeft, target.topRight);
seg2 = new Segment(source.bottomLeft, target.topRight);
} else {
seg = new Segment(source.topRight, target.topRight);
seg2 = new Segment(source.bottomLeft, target.topRight);
}
stack.push(source);
stack.push(target);
stack.push(seg);
stack.push(source);
stack.push(target);
stack.push(seg2);
seg = null;
seg2 = null;
if (target.bottom() < source.bottom()) {
seg = new Segment(source.bottomLeft, target.bottomLeft);
if (target.bottom() - 1 > source.y)
seg2 = new Segment(source.topLeft, target.bottomRight);
else
seg2 = new Segment(source.topRight, target.bottomLeft);
} else if (source.bottom() == target.bottom()) {
seg = new Segment(source.bottomLeft, target.bottomRight);
seg2 = new Segment(source.topLeft, target.bottomRight);
} else {
seg = new Segment(source.bottomRight, target.bottomRight);
seg2 = new Segment(source.topLeft, target.bottomRight);
}
stack.push(source);
stack.push(target);
stack.push(seg);
stack.push(source);
stack.push(target);
stack.push(seg2);
}
/**
*
*/
void cleanup() {
// segments.clear();
visibleVertices.clear();
}
/**
* Begins the creation of the visibility graph with the first segment
*
* @param allObstacles
* list of all obstacles
*/
private void createVisibilityGraph(List allObstacles) {
stack.push(null);
stack.push(null);
stack.push(new Segment(start, end));
while (!stack.isEmpty())
addSegment(stack.pop(), stack.popObstacle(), stack.popObstacle(),
allObstacles);
}
/**
* Once the visibility graph is constructed, this is called to label the
* graph and determine the shortest path. Returns false if no path can be
* found.
*
* @return true if a path can be found.
*/
private boolean determineShortestPath() {
if (!labelGraph())
return false;
Vertex vertex = end;
prevCostRatio = end.cost / start.getDistance(end);
Vertex nextVertex;
while (!vertex.equals(start)) {
nextVertex = vertex.label;
if (nextVertex == null)
return false;
Segment s = new Segment(nextVertex, vertex);
segments.add(s);
vertex = nextVertex;
}
Collections.reverse(segments);
return true;
}
/**
* Resets all necessary fields for a solve.
*/
void fullReset() {
visibleVertices.clear();
segments.clear();
if (prevCostRatio == 0) {
double distance = start.getDistance(end);
threshold = distance * OVAL_CONSTANT;
} else
threshold = prevCostRatio * EPSILON * start.getDistance(end);
visibleObstacles.clear();
resetPartial();
}
/**
* Creates the visibility graph and returns whether or not a shortest path
* could be determined.
*
* @param allObstacles
* the list of all obstacles
* @return true if a shortest path was found
*/
boolean generateShortestPath(List allObstacles) {
createVisibilityGraph(allObstacles);
if (visibleVertices.size() == 0)
return false;
return determineShortestPath();
}
/**
* Returns the list of constrained points through which this path must pass
* or null
.
*
* @see #setBendPoints(PointList)
* @return list of bend points
*/
public PointList getBendPoints() {
return bendpoints;
}
/**
* Returns the end point for this path
*
* @return end point for this path
*/
public Point getEndPoint() {
return end;
}
/**
* Returns the solution to this path.
*
* @return the points for this path.
*/
public PointList getPoints() {
return points;
}
/**
* Returns the start point for this path
*
* @return start point for this path
*/
public Point getStartPoint() {
return start;
}
/**
* Returns a subpath for this path at the given segment
*
* @param currentSegment
* the segment at which the subpath should be created
* @return the new path
*/
Path getSubPath(Segment currentSegment) {
// ready new path
Path newPath = new Path(currentSegment.start, end);
newPath.grownSegments = new ArrayList(grownSegments.subList(
grownSegments.indexOf(currentSegment), grownSegments.size()));
// fix old path
grownSegments = new ArrayList(grownSegments.subList(0,
grownSegments.indexOf(currentSegment) + 1));
end = currentSegment.end;
subPath = newPath;
return newPath;
}
/**
* Resets the vertices that this path has traveled prior to this segment.
* This is called when the path has become inverted and needs to rectify any
* labeling mistakes it made before it knew it was inverted.
*
* @param currentSegment
* the segment at which the path found it was inverted
*/
void invertPriorVertices(Segment currentSegment) {
int stop = grownSegments.indexOf(currentSegment);
for (int i = 0; i < stop; i++) {
Vertex vertex = ((Segment) grownSegments.get(i)).end;
if (vertex.type == Vertex.INNIE)
vertex.type = Vertex.OUTIE;
else
vertex.type = Vertex.INNIE;
}
}
/**
* Returns true if this obstacle is in the visibility graph
*
* @param obs
* the obstacle
* @return true if obstacle is in the visibility graph
*/
boolean isObstacleVisible(Obstacle obs) {
return visibleObstacles.contains(obs);
}
/**
* Labels the visibility graph to assist in finding the shortest path
*
* @return false if there was a gap in the visibility graph
*/
private boolean labelGraph() {
int numPermanentNodes = 1;
Vertex vertex = start;
Vertex neighborVertex = null;
vertex.isPermanent = true;
double newCost;
while (numPermanentNodes != visibleVertices.size()) {
List neighbors = vertex.neighbors;
if (neighbors == null)
return false;
// label neighbors if they have a new shortest path
for (int i = 0; i < neighbors.size(); i++) {
neighborVertex = (Vertex) neighbors.get(i);
if (!neighborVertex.isPermanent) {
newCost = vertex.cost + vertex.getDistance(neighborVertex);
if (neighborVertex.label == null) {
neighborVertex.label = vertex;
neighborVertex.cost = newCost;
} else if (neighborVertex.cost > newCost) {
neighborVertex.label = vertex;
neighborVertex.cost = newCost;
}
}
}
// find the next none-permanent, labeled vertex with smallest cost
double smallestCost = 0;
Vertex tempVertex = null;
Iterator v = visibleVertices.iterator();
while (v.hasNext()) {
tempVertex = (Vertex) v.next();
if (!tempVertex.isPermanent
&& tempVertex.label != null
&& (tempVertex.cost < smallestCost || smallestCost == 0)) {
smallestCost = tempVertex.cost;
vertex = tempVertex;
}
}
// set the new vertex to permanent.
vertex.isPermanent = true;
numPermanentNodes++;
}
return true;
}
/**
* Links two vertices together in the visibility graph
*
* @param segment
* the segment to add
*/
private void linkVertices(Segment segment) {
if (segment.start.neighbors == null)
segment.start.neighbors = new ArrayList();
if (segment.end.neighbors == null)
segment.end.neighbors = new ArrayList();
if (!segment.start.neighbors.contains(segment.end)) {
segment.start.neighbors.add(segment.end);
segment.end.neighbors.add(segment.start);
}
visibleVertices.add(segment.start);
visibleVertices.add(segment.end);
}
/**
* Called to reconnect a subpath back onto this path. Does a depth-first
* search to reconnect all paths. Should be called after sorting.
*/
void reconnectSubPaths() {
if (subPath != null) {
subPath.reconnectSubPaths();
Segment changedSegment = (Segment) subPath.grownSegments.remove(0);
Segment oldSegment = (Segment) grownSegments.get(grownSegments
.size() - 1);
oldSegment.end = changedSegment.end;
grownSegments.addAll(subPath.grownSegments);
subPath.points.removePoint(0);
points.removePoint(points.size() - 1);
points.addAll(subPath.points);
visibleObstacles.addAll(subPath.visibleObstacles);
end = subPath.end;
subPath = null;
}
}
/**
* Refreshes the exclude field on the obstacles in the list. Excludes all
* obstacles that contain the start or end point for this path.
*
* @param allObstacles
* list of all obstacles
*/
void refreshExcludedObstacles(List allObstacles) {
excludedObstacles.clear();
for (int i = 0; i < allObstacles.size(); i++) {
Obstacle o = (Obstacle) allObstacles.get(i);
o.exclude = false;
if (o.contains(start)) {
if (o.containsProper(start))
o.exclude = true;
else {
/*
* $TODO Check for corners. If the path begins exactly at
* the corner of an obstacle, the exclude should also be
* true.
*
* Or, change segment intersection so that two segments that
* share an endpoint do not intersect.
*/
}
}
if (o.contains(end)) {
if (o.containsProper(end))
o.exclude = true;
else {
// check for corners. See above statement.
}
}
if (o.exclude && !excludedObstacles.contains(o))
excludedObstacles.add(o);
}
}
/**
* Resets the fields for everything in the solve after the visibility graph
* steps.
*/
void resetPartial() {
isMarked = false;
isInverted = false;
subPath = null;
isDirty = false;
grownSegments.clear();
points.removeAllPoints();
}
/**
* Sets the list of bend points to the given list and dirties the path.
*
* @param bendPoints
* the list of bend points
*/
public void setBendPoints(PointList bendPoints) {
this.bendpoints = bendPoints;
isDirty = true;
}
/**
* Sets the end point for this path to the given point.
*
* @param end
* the new end point for this path
*/
public void setEndPoint(Point end) {
if (end.equals(this.end))
return;
this.end = new Vertex(end, null);
isDirty = true;
}
/**
* Sets the start point for this path to the given point.
*
* @param start
* the new start point for this path
*/
public void setStartPoint(Point start) {
if (start.equals(this.start))
return;
this.start = new Vertex(start, null);
isDirty = true;
}
/**
* Returns true
if the path is clean and intersects the given
* obstacle. Also dirties the path in the process.
*
* @since 3.0
* @param obs
* the obstacle
* @return true
if a clean path touches the obstacle
*/
boolean testAndSet(Obstacle obs) {
if (isDirty)
return false;
// This will never actually happen because obstacles are not stored by
// identity
if (excludedObstacles.contains(obs))
return false;
Segment seg1 = new Segment(obs.topLeft, obs.bottomRight);
Segment seg2 = new Segment(obs.topRight, obs.bottomLeft);
for (int s = 0; s < points.size() - 1; s++) {
points.getPoint(CURRENT, s);
points.getPoint(NEXT, s + 1);
if (seg1.intersects(CURRENT, NEXT)
|| seg2.intersects(CURRENT, NEXT) || obs.contains(CURRENT)
|| obs.contains(NEXT)) {
isDirty = true;
return true;
}
}
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy