view src/share/tools/IdealGraphVisualizer/ControlFlowEditor/src/at/ssw/visualizer/cfg/visual/PolylineRouterV2.java @ 4487:aae5b3773e63

Added CFG editor from c1visualizer.
author Thomas Wuerthinger <thomas.wuerthinger@oracle.com>
date Tue, 31 Jan 2012 00:23:10 +0100
parents
children c43083cc96e9
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.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. 
 * 
 * This class intend to solve the same problem as the class PolyLineRouter but
 * uses slightly different methods.
 * + uses another algorithm for solving the collision.
 * + the obstacle bounds are calulated in advance to avoid a recalculation.
 * - there is no heuristic, the shortest path around the widget is choosen asap.
 * 
 * 
 *  
 */

public class PolylineRouterV2 implements Router {    
    private static final int HEXPAND = 3;
    private static final int VEXPAND = 3;
    private static final int NUMBER_OF_ITERATIONS = 8;   
    
    WidgetCollisionCollector collector;
    
     
    public PolylineRouterV2(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();
        }
        
        
        Point srcCenter = this.getSceneLocation(sourceWidget);
        Point tarCenter = this.getSceneLocation(targetWidget);
        
        List<Widget> widgetObstacles = new ArrayList<Widget>();
        
        if(collector != null){
            collector.collectCollisions(widgetObstacles);                    
        }
        
        List<Rectangle> obstacles = new ArrayList<Rectangle>(widgetObstacles.size());
        this.collectObstacles(obstacles, widgetObstacles, widget);
       
                    
        List<Point> controlPoints = optimize(obstacles, srcCenter, tarCenter);     
//        size==2 => straight line
//        size==3 => ensures a collision between cp0 and cp2 therefore its not possible to simplify it
        if(controlPoints.size() > 3){
            Point rstart = this.computateAnchorPosition(sourceWidget, controlPoints.get(1));            
            Point rend  = this.computateAnchorPosition(targetWidget, controlPoints.get(controlPoints.size()-2)); 
            controlPoints.set(0, rstart);           
            controlPoints.set(controlPoints.size()-1, rend);       
            controlPoints = simplify(obstacles, controlPoints);
        } else if (controlPoints.size()>=2){
                //use old points
            controlPoints.set(0, start);           
            controlPoints.set(controlPoints.size()-1, end);           
        
        }
        return controlPoints;        
    }  
    
    
    private int collectObstacles(List<Rectangle> colrects, List<Widget> colwidgets , ConnectionWidget cw){        
        int count=0;                        
        Anchor sourceAnchor = cw.getSourceAnchor();
        Anchor targetAnchor = cw.getTargetAnchor();
        Widget sourceWidget = sourceAnchor.getRelatedWidget();
        Widget targetWidget = targetAnchor.getRelatedWidget(); 
        Point start = sourceAnchor.compute(cw.getSourceAnchorEntry()).getAnchorSceneLocation();
        Point end =   targetAnchor.compute(cw.getTargetAnchorEntry()).getAnchorSceneLocation();
                        
        for(Widget w : colwidgets){
            
            if(w==sourceWidget || w == targetWidget) continue;
           
            Rectangle r = w.convertLocalToScene(w.getBounds());
            r.grow(HEXPAND, VEXPAND);
            if(r.intersectsLine(start.x,start.y,end.x,end.y))
                count++;
            colrects.add(r);
        }
        return count;
    }
    
    
    private Point center (Rectangle bounds) {
        return new Point (bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
    }

    /**
     * Returns the scene location of a related widget.
     * bounds might be null if the widget was not added to the scene
     * @return the scene location; null if no related widget is assigned
     */
    public Point getSceneLocation (Widget relatedWidget) {
        if (relatedWidget != null) {
            Rectangle bounds = relatedWidget.getBounds ();
            if(bounds != null)
                return center(relatedWidget.convertLocalToScene(bounds));           
        }
        return null;
    }
       
    /**
     * 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();
        
        //todo: fix, center of widget must be cacluated trough the bounds 
        //since there are some wheird widgets where the location is not the center of the widget
        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> simplify(List<Rectangle> obstacles, List<Point> list) {        
        List<Point> result = new ArrayList<Point>(list.size());
        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(obstacles, prev, cur)) {
                    i = j;
                }               
            }
            result.add(list.get(i));
        }      
        return result;
    }
  
    private List<Point> optimize(List<Rectangle> nodeWidgets, Point start, Point end) {
        
        List<Point> list = new ArrayList<Point>();
        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<Point>();      
            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;   
            
        }
        
        return list;
    }
 
  
    /**
     *  trys to optimize a line from p1 to p2 to avoid collisions with rectangles
     *  returns null if the line doesn`t intersect with any nodewidget or
     *  if the obstacles are overlapping
     *  ----------------------------------------------------------------------
     *  if the collision is solved it returns a list with immediate points 
     *  between p1 and p2. The points are taken from hull points of and grown 
     *  rectangle.
     */
    private List<Point> optimizeLine(List<Rectangle> obstacles, Point p1, Point p2) {        
        Line2D line = new Line2D.Double(p1, p2);
        boolean inbounds=false;
        Rectangle ibr=null;
        ArrayList<Point> sol = new ArrayList<Point>();
        boolean leftIntersection;
        boolean rightIntersection; 
        boolean bottomIntersection; 
        boolean topIntersection; 
        Point interLeft=new Point();
        Point interRight=new Point();
        Point interBot=new Point();
        Point interTop=new Point();
        
    
        
        for(Rectangle r : obstacles ) {                      
            if (!line.intersects(r)) continue;
            
            int w=r.width+2;
            int h=r.height+2;
            Point topLeft = r.getLocation();
            topLeft.x-=1;
            topLeft.y-=1;
            Point topRight = new Point(topLeft.x+w, topLeft.y);
            Point bottomLeft = new Point(topLeft.x, topLeft.y+h);
            Point bottomRight = new Point(topRight.x, bottomLeft.y);  
            leftIntersection = findIntersectionPoint(p1, p2, topLeft, bottomLeft, interLeft);
            rightIntersection = findIntersectionPoint(p1, p2, topRight, bottomRight, interRight);
            bottomIntersection = findIntersectionPoint(p1, p2, bottomLeft, bottomRight, interBot);
            topIntersection = findIntersectionPoint(p1, p2, topLeft, topRight, interTop);
            
            //Intersection points are not used yet. This could be actually a 
            //good approach to avoid additional collisions because it would be
            //still the same vector.

            if(leftIntersection) {                      
                if(topIntersection) {//left and top   
                    sol.add(topLeft);                       
                }                        
                else if(bottomIntersection){//left and bottom
                    sol.add(bottomLeft);                                   
                }
                else if(rightIntersection){//left and right
                    double disttl = topLeft.distance(p1);
                    double distbl = bottomLeft.distance(p1);
                    if(disttl > distbl){
                        //pass at the bottom
                        double distbr = bottomRight.distance(p1);
                        if(distbl < distbr){
                            //from the left to the right
                            sol.add(bottomLeft);
                            sol.add(bottomRight);
                        } else {
                            //from the right to the left
                            sol.add(bottomRight);
                            sol.add(bottomLeft);
                        }
                    } else {
                        //pass at the top
                        double disttr = topRight.distance(p1);
                        if(disttl < disttr){
                            //from the left to the right
                            sol.add(topLeft);
                            sol.add(topRight);
                        } else {
                            //from the right to the left
                            sol.add(topRight);
                            sol.add(topLeft);
                        }
                    }                        
                 } else {//only left => inside bounds 
                    inbounds=true;                                  
                 } 
            } else if (rightIntersection) {                
                if(topIntersection) {//right and top
                    sol.add(topRight);
                }
                else if(bottomIntersection){//right and bottom
                    sol.add(bottomRight);
                } else { //only right => inside the bounds
                    inbounds=true;                             
                }                  
            } else if (topIntersection && bottomIntersection) {//top and bottom
                double disttop = interTop.distance(p1);
                double distbot = interBot.distance(p1);
                if(disttop < distbot ){
                    //from the top to the bottom
                    double distleft = interTop.distance(topLeft);
                    double distright = interTop.distance(topRight);
                    if(distleft < distright){
                        //pass left
                        sol.add(topLeft);
                        sol.add(bottomLeft);
                    } else {
                        //pass right
                        sol.add(topRight);
                        sol.add(bottomRight);
                    }   
                } else {
                    //from the bottom to the top
                    double distleft = interBot.distance(bottomLeft);
                    double distright = interBot.distance(bottomRight);
                    if(distleft < distright){
                        //pass left
                        sol.add(bottomLeft);
                        sol.add(topLeft);
                    } else {
                        //pass right
                        sol.add(bottomRight);
                        sol.add(topRight);
                    }   
                } 
            } else {//only bottom or only top
                inbounds=true;                 
            } /* ENDIF */
            
            //breakpoint <-- collision detected
            
            if(sol.size()>0) {//solution found
                assert(!inbounds);
                return sol;
            } else { //no solution found=> inbounds
                assert(inbounds);
                assert(sol.size()==0);
                //handle collision or just skip it and search for the next collisionj               
                ibr=r;
                //jump out of the loop to able to interate over the obstacles
                break;
            } 
        }/* end foreach obstacle */
        
        if(inbounds || ibr != null){
            assert(inbounds);
            assert(ibr!=null);        
        }       
        return null;//no collison found
    }/* end optimizeLine */
    
 
  
    //check intersection between line p0->p1 for a given set of obstacles
    private static boolean intersects(List<Rectangle> obstacles, Point p0, Point p1) {
        for(Rectangle r : obstacles){
            if(r.intersectsLine(p0.x, p0.y, p1.x, p1.y))
                return true;         
        }
        return false;
    }
    
   
    private boolean findIntersectionPoint(
            Point p0, Point p1, Point p2, Point p3, Point pI) {
        float q = (p0.y - p2.y)*(p3.x - p2.x) - (p0.x - p2.x)*(p3.y - p2.y);
        float d = (p1.x - p0.x)*(p3.y - p2.y) - (p1.y - p0.y)*(p3.x - p2.x);
        
        //parallel ?
        if(d==0) return false;
          
        float r = q / d;
        q = (p0.y - p2.y)*(p1.x - p0.x) - (p0.x - p2.x)*(p1.y - p0.y);
        
        float s = q / d;
        if(r<0 || r>1 || s<0 || s>1) return false;
        
        pI.x = p0.x + (int) (0.5f + r * (p1.x - p0.x));
        pI.y = p0.y + (int) (0.5f + r * (p1.y - p0.y));
        return true;
    }    
}