Mercurial > hg > truffle
view src/share/tools/IdealGraphVisualizer/ControlFlowEditor/src/at/ssw/visualizer/cfg/visual/PolylineRouter.java @ 4503:c43083cc96e9
Fix router and layout actions. Now works also on multiple scenes and uses preferences. Also, use preferences for currently selected factory.
author | Thomas Wuerthinger <thomas.wuerthinger@oracle.com> |
---|---|
date | Sun, 05 Feb 2012 04:34:57 +0100 |
parents | aae5b3773e63 |
children |
line wrap: on
line source
package at.ssw.visualizer.cfg.visual; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.Line2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.netbeans.api.visual.anchor.Anchor; import org.netbeans.api.visual.router.Router; import org.netbeans.api.visual.widget.ConnectionWidget; import org.netbeans.api.visual.widget.Widget; /** * Router Class with Collision Detection * * * The returned path is a straight line there is no node widget between the source and the target node, * if there are nodewidgets in between it tries to find a path around those abstacles to avoid collisions. * The algorithm for the search of this path is limited by a fixed number of iterations defined in * NUMER_OF_ITERATIONS to return a result in reasonable time. If the calculation exceeds this limit the * path returned contains at least 1 collision with a nodewidget. */ public class PolylineRouter implements Router { private static final float FACTOR = 0.70f; private static final int HEXPAND = 3; private static final int VEXPAND = 3; private static final int NUMBER_OF_ITERATIONS = 8; WidgetCollisionCollector collector; public PolylineRouter(WidgetCollisionCollector collector){ this.collector=collector; } public List<Point> routeConnection(final ConnectionWidget widget) { if(!widget.isVisible()) return Collections.<Point>emptyList(); Anchor sourceAnchor = widget.getSourceAnchor(); Anchor targetAnchor = widget.getTargetAnchor(); if(sourceAnchor == null || targetAnchor == null) return null; Point start = sourceAnchor.compute(widget.getSourceAnchorEntry()).getAnchorSceneLocation(); Point end = targetAnchor.compute(widget.getTargetAnchorEntry()).getAnchorSceneLocation(); Widget sourceWidget = sourceAnchor.getRelatedWidget(); Widget targetWidget = targetAnchor.getRelatedWidget(); if(sourceWidget == targetWidget){//reflexive edges doesnt need any path return Collections.<Point>emptyList(); } List<Widget> nodeWidgets = new ArrayList<>(); if(collector != null){ collector.collectCollisions(nodeWidgets); //source and target widget are not treatet as obstacle nodeWidgets.remove(sourceWidget); nodeWidgets.remove(targetWidget); } List<Point> controlPoints = optimize(nodeWidgets, widget, start, end); //size==2 => straight line //size==3 => ensures a collision between cp0 and cp2 therefore its not possible to simplify it if(controlPoints.size() > 3) controlPoints = simplify(nodeWidgets, controlPoints); return controlPoints; } private List<Point> simplify(Collection<Widget> nodeWidgets, List<Point> list) { List<Point> result = new ArrayList<>(); result.add( list.get(0) );//add startpoint for (int i = 1; i < list.size(); i++) { Point prev = list.get(i - 1); for (int j = i; j < list.size(); j++) { Point cur = list.get(j); if (!intersects(nodeWidgets, prev, cur)) { i = j; } } result.add(list.get(i)); } return result; } /** * Computates the Anchorposition like the Rectangular anchor for a * given widget as source/target and a controlpoint as opposit anchorposition */ private Point computateAnchorPosition(Widget relatedWidget, Point controlPoint) { Rectangle bounds = relatedWidget.getBounds(); Point relatedLocation = relatedWidget.getLocation();//center of the widget if (bounds.isEmpty () || relatedLocation.equals (controlPoint)) return relatedLocation; float dx = controlPoint.x - relatedLocation.x; float dy = controlPoint.y - relatedLocation.y; float ddx = Math.abs (dx) / (float) bounds.width; float ddy = Math.abs (dy) / (float) bounds.height; float scale = 0.5f / Math.max (ddx, ddy); Point point = new Point (Math.round (relatedLocation.x + scale * dx), Math.round (relatedLocation.y + scale * dy)); return point; } private List<Point> optimize(Collection<Widget> nodeWidgets, ConnectionWidget connWidget, Point start, Point end) { List<Point> list = new ArrayList<>(); list.add(start); list.add(end); boolean progress = true; for (int j = 0; progress && j < NUMBER_OF_ITERATIONS ; j++) { progress = false; List<Point> newList = new ArrayList<>(); for (int i = 0; i < list.size() - 1 ; i++) { Point cur = list.get(i); Point next = list.get(i + 1); newList.add(cur); List<Point> intermediate = optimizeLine(nodeWidgets, cur, next); if (intermediate != null && intermediate.size() > 0) { progress = true; newList.addAll(intermediate);//insert new controlpoints between cur and next } } newList.add(list.get(list.size()-1));//add endpoint of the polyline list = newList; } if(list.size() > 2) { Widget sourceNode = connWidget.getSourceAnchor().getRelatedWidget(); Widget targetNode = connWidget.getTargetAnchor().getRelatedWidget(); Rectangle sourceBounds = sourceNode.convertLocalToScene(sourceNode.getBounds()); Rectangle targetBounds = targetNode.convertLocalToScene(targetNode.getBounds()); sourceBounds.grow(HEXPAND, VEXPAND); /** * add only points which are not intersecting the source and the target * widget bounds caused by invalid bad anchor positions. The first * and the last point is ignored cause the anchor is recalculated * anyway. */ ArrayList<Point> tmp = new ArrayList<>(); int listSize=list.size(); tmp.add(list.get(0)); int i=0; while(++i < listSize-1) { Point p = list.get(i); if(!sourceBounds.contains(p) || !targetBounds.contains(p)) tmp.add(p); } tmp.add(list.get(i)); if(tmp.size() < 3) return tmp; list=tmp; //calculate a proper anchor position using the second/penultimate controlpoint for start/end start = this.computateAnchorPosition(connWidget.getSourceAnchor().getRelatedWidget(), list.get(1)); end = this.computateAnchorPosition(connWidget.getTargetAnchor().getRelatedWidget(), list.get(list.size()-2)); list.set(0,start); list.set(list.size()-1, end); } return list; } /** * trys to optimize a line from p1 to p2 to avoid collisions with node widgets * returns null if the line doesn`t intersect with any nodewidget * or a list with immediate points between p1 and p2 to avoid collisions */ private List<Point> optimizeLine(Collection<Widget> nodeWidgets, Point p1, Point p2) { Line2D line = new Line2D.Double(p1, p2); for(Widget w : nodeWidgets ) { if( w.isVisible() ) { Rectangle r = w.convertLocalToScene(w.getBounds()); r.grow(HEXPAND, VEXPAND); if (!line.intersects(r)) continue; Point location = w.getLocation(); int distx = (int) (r.width * FACTOR); int disty = (int) (r.height * FACTOR); int minIntersects = Integer.MAX_VALUE; int min = Integer.MAX_VALUE; List<Point> minSol = null; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { if (i != 0 || j != 0) { Point cur = new Point(location.x + i * distx, location.y + j * disty); List<Point> list1 = new ArrayList<>(); list1.add(p1); list1.add(cur); list1.add(p2); int crossProd = Math.abs(crossProduct(p1, cur, p2)); if (!intersects(w, list1)) { Line2D line1 = new Line2D.Float(p1, cur); Line2D line2 = new Line2D.Float(p2, cur); int curIntersects = this.countNodeIntersections(nodeWidgets, line1, line2); if (curIntersects < minIntersects || (curIntersects == minIntersects && crossProd < min)) { minIntersects = curIntersects; min = crossProd; minSol = new ArrayList<>(); minSol.add(cur); } } if (i == 0 || j == 0) { Point cur1, cur2; if (i == 0) { cur1 = new Point(location.x + distx/2, location.y + j * disty); cur2 = new Point(location.x - distx/2, location.y + j * disty); } else { // (j == 0) cur1 = new Point(location.x + i * distx, location.y + disty/2); cur2 = new Point(location.x + i * distx, location.y - disty/2); } Point vec1 = new Point(p1.x - cur1.x, p1.y - cur1.y); int offset1 = vec1.x * vec1.x + vec1.y * vec1.y; Point vec2 = new Point(p1.x - cur2.x, p1.y - cur2.y); int offset2 = vec2.x * vec2.x + vec2.y * vec2.y; if (offset2 < offset1) { Point tmp = cur1; cur1 = cur2; cur2 = tmp; } List<Point> list2 = new ArrayList<>(); list2.add(p1); list2.add(cur1); list2.add(cur2); list2.add(p2); int cross1 = crossProduct(p1, cur1, cur2); int cross2 = crossProduct(cur1, cur2, p2); if (cross1 > 0) { cross1 = 1; } else if (cross1 < 0) { cross1 = -1; } if (cross2 > 0) { cross2 = 1; } else if (cross2 < 0) { cross2 = -1; } if ((cross1 == cross2 || cross1 == 0 || cross2 == 0) && !intersects(w, list2)) { Line2D line1 = new Line2D.Float(p1, cur1); Line2D line2 = new Line2D.Float(cur1, cur2); Line2D line3 = new Line2D.Float(p2, cur2); int curIntersects = this.countNodeIntersections(nodeWidgets, line1, line2, line3); // This is a bit better crossProd--; if (curIntersects < minIntersects || (curIntersects == minIntersects && crossProd < min)) { minIntersects = curIntersects; min = crossProd; minSol = new ArrayList<>(); minSol.add(cur1); minSol.add(cur2); } } } } } } if (minSol != null) { return minSol; } } } return null; } private int countNodeIntersections(Collection<Widget> nodeWidgets, Line2D... lines){ int count=0; for(Widget nw : nodeWidgets){ if(nw.isVisible()) { Rectangle bounds = nw.convertLocalToScene(nw.getBounds()); for( Line2D line : lines){ if(line.intersects(bounds)) count++; } } } return count; } private boolean intersects(Collection<Widget> nodeWidgets, Point start, Point end) { List<Point> pointlist = new ArrayList<>(); pointlist.add(start); pointlist.add(end); for(Widget w : nodeWidgets){ if(w.isVisible() && intersects(w, pointlist)) { return true; } } return false; } private boolean intersects(Widget w, List<Point> list) { Rectangle r = w.convertLocalToScene(w.getBounds()); r.grow(HEXPAND, VEXPAND); return intersects(list, r); } private boolean intersects(List<Point> list, Rectangle rect){ for(int i=1; i < list.size(); i++) { Point cur = list.get(i-1); Point next = list.get(i); if(rect.intersectsLine(cur.x, cur.y, next.x, next.y)) return true; } return false; } private int crossProduct(Point p1, Point p2, Point p3) { Point off1 = new Point(p1.x - p2.x, p1.y - p2.y); Point off2 = new Point(p3.x - p2.x, p3.y - p2.y); return (off1.x * off2.y - off1.y * off2.x); } }