Mercurial > hg > graal-jvmci-8
view src/share/tools/IdealGraphVisualizer/ControlFlowEditor/src/at/ssw/visualizer/cfg/visual/.svn/text-base/SplineConnectionWidget.java.svn-base @ 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 |
line wrap: on
line source
package at.ssw.visualizer.cfg.visual; import org.netbeans.api.visual.widget.ConnectionWidget; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.CubicCurve2D; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.List; import org.netbeans.api.visual.anchor.AnchorShape; import org.netbeans.api.visual.graph.GraphScene; import org.netbeans.api.visual.widget.Scene; import org.netbeans.api.visual.widget.Widget; /** * In comparison to the default ConnectionWidget this class is able to connect * widgets with a curve instead of a straight line sequence. * In conjunction with a suitable router the connection will be a straight line * or a curve depending on the amount and the position of the controlpoints. * Controlpoints supplied by the router, are treated as curve intersection points. * For Reflexive edges the router doesn`t necessarily need to supply * any controlpoints, they get painted by this automatically, this can be * excepted if the router supplys more as 2 control points for a self edge, * then the edge gets painted with the default curve interpolation algorithm. * The method used for drawing curves uses a piecewise cubic interpolation * algorithm. Between two control points a curve is painted as cubic bezier * curve the, inner bezier points are calculated automatically with FMILL * tangents and a chord parametrization, this interpolant is also known as * cutmull-rom spline. The resulting spline fullfills c^1 continuity. * The the end points the interpolation algorithm uses the bessel end condition. */ public class SplineConnectionWidget extends ConnectionWidget { private static final double ENDPOINT_DEVIATION = 3;//curve endpoint approximation accuracy private static final double HIT_DISTANCE_SQUARE = 4.0;//distance for intersection test private GraphScene scene=null; private Point2D [] bezierPoints = null; public SplineConnectionWidget(Scene scene) { super(scene); if(scene instanceof GraphScene) this.scene=(GraphScene) scene; } //check for self - edge private boolean isReflexive(){ return getSourceAnchor().getRelatedWidget() == getTargetAnchor().getRelatedWidget(); } @Override protected Rectangle calculateClientArea() { Rectangle bounds = null; if(this.getControlPoints().size()>2){ bezierPoints = createBezierPoints(getControlPoints()); } //minmax method - returns the smallest bounding rectangle //Curves and surfaces for CAGD (3rd ed.), p.54 //exact calculation of the bounding min rect if(bezierPoints != null) { Rectangle2D bounds2D = null; for (int i = 0; i < bezierPoints.length; i++) { Point2D point = bezierPoints[i]; if(bounds2D==null) bounds2D = new Rectangle2D.Double(point.getX(),point.getY(),0,0); else bounds2D.add(point); } bounds = bounds2D.getBounds(); bounds.grow(2, 2); } else if (getControlPoints().size()>0){ for(Point p : this.getControlPoints()){ if(bounds==null) bounds = new Rectangle(p); else bounds.add(p); } bounds.grow(5,5); } else if (isReflexive()) { Widget related = this.getTargetAnchor().getRelatedWidget(); bounds = related.convertLocalToScene(related.getBounds()); bounds.grow(10, 10); } if(bounds==null) bounds = super.calculateClientArea(); return bounds; } /** * if the edge is reflexive its painted as a cyclic self edge, if there * are two controlpoints the connection is painted as a straight line from * the source to the targetanchor, if there are more as 2 controlpoints the * connection path between two control points is painted as cutmull rom * spline with bessel end tangents. */ @Override protected void paintWidget () { List<Point> contrPoints = this.getControlPoints(); int listSize = contrPoints.size(); Graphics2D gr = getGraphics (); if (listSize <= 2) { this.bezierPoints=null;//set bezier Points null for calulateClientArea() if(isReflexive()) { //special case for reflexive connection widgets this.drawReflexive(gr); } else { super.paintWidget(); } return; } //bezier curve... listSize > 2 GeneralPath curvePath = new GeneralPath(); double lastControlPointRotation = 0.0; Point2D [] bezPoints = this.createBezierPoints(contrPoints); curvePath.moveTo(bezPoints[0].getX(), bezPoints[0].getY());//b00 //last segment is added by subdivision thats why its -5 for (int i = 1; i < bezPoints.length-5; i+=3) { curvePath.curveTo( bezPoints[i].getX(), bezPoints[i].getY(),//b1i bezPoints[i+1].getX(), bezPoints[i+1].getY(),//b2i bezPoints[i+2].getX(), bezPoints[i+2].getY());//b3i } GeneralPath lastseg = subdivide2D( bezPoints[bezPoints.length-4], bezPoints[bezPoints.length-3], bezPoints[bezPoints.length-2], bezPoints[bezPoints.length-1]); if(lastseg != null) curvePath.append(lastseg, true); Point2D cur = curvePath.getCurrentPoint(); Point lastControlPoint = contrPoints.get(listSize-1); lastControlPointRotation = //anchor anchorAngle Math.atan2 (cur.getY() - lastControlPoint.y, cur.getX() - lastControlPoint.x); gr.setStroke(getStroke()); gr.setColor(getLineColor()); gr.draw(curvePath); AffineTransform previousTransform = gr.getTransform (); gr.translate (lastControlPoint.x, lastControlPoint.y); gr.rotate (lastControlPointRotation); AnchorShape targetAnchorShape = this.getTargetAnchorShape(); targetAnchorShape.paint (gr, false); gr.setTransform (previousTransform); //paint ControlPoints if enabled if (isPaintControlPoints()) { int last = listSize - 1; for (int index = 0; index <= last; index ++) { Point point = contrPoints.get (index); previousTransform = gr.getTransform (); gr.translate (point.x, point.y); if (index == 0 || index == last) getEndPointShape().paint (gr); else getControlPointShape().paint (gr); gr.setTransform (previousTransform); } } } private void drawReflexive(Graphics2D gr){ Widget related = this.getTargetAnchor().getRelatedWidget(); int position = this.edgeBalance(related); Rectangle bounds = related.convertLocalToScene(related.getBounds()); gr.setColor (getLineColor()); Point first = new Point(); Point last = new Point(); double centerX = bounds.getCenterX(); first.x = (int) (centerX + bounds.width / 4); first.y = bounds.y + bounds.height; last.x = first.x; last.y = bounds.y; gr.setStroke(this.getStroke()); double cutDistance = this.getTargetAnchorShape().getCutDistance(); double anchorAngle = Math.PI/-3.0; double cutX = Math.abs(Math.cos(anchorAngle)*cutDistance); double cutY = Math.abs(Math.sin(anchorAngle)*cutDistance); int ydiff=first.y-last.y; int endy = -ydiff; double height=bounds.getHeight(); double cy = height/4.0; double cx=bounds.getWidth()/5.0; double dcx = cx*2; GeneralPath gp = new GeneralPath(); gp.moveTo(0, 0); gp.quadTo(0, cy, cx, cy); gp.quadTo(dcx, cy, dcx, -height/2.0); gp.quadTo(dcx, endy - cy, cy, -(cy+ydiff)); gp.quadTo(cutX*1.5, endy - cy, cutX, endy-cutY); AffineTransform af = new AffineTransform(); AnchorShape anchorShape = this.getTargetAnchorShape(); if(position < 0) { first.x = (int) (centerX - bounds.width / 4); af.translate(first.x, first.y); af.scale(-1.0, 1.0); last.x = first.x; } else { af.translate(first.x, first.y); } Shape s = gp.createTransformedShape(af); gr.draw(s); if (last != null) { AffineTransform previousTransform = gr.getTransform (); gr.translate (last.x, last.y); if(position < 0) gr.rotate(Math.PI - anchorAngle); else gr.rotate (anchorAngle); anchorShape.paint (gr, false); gr.setTransform (previousTransform); } } //returns prefered location for an edge -1 for left and 1 for right private int edgeBalance(Widget nodeWidget) { if(scene == null) return 1; Point nodeLocation = nodeWidget.getLocation(); int left = 0, right = 0; Object node = scene.findObject(nodeWidget); for(Object e : scene.findNodeEdges(node, true, true)) {//inputedges ConnectionWidget cw = (ConnectionWidget) scene.findWidget(e); if(cw != this) { Widget targetNodeWidget = cw.getTargetAnchor().getRelatedWidget(); Point location; if(targetNodeWidget == nodeWidget) { Widget sourceNodeWidget = cw.getSourceAnchor().getRelatedWidget(); location = sourceNodeWidget.getLocation(); } else { location = targetNodeWidget.getLocation(); } if(location.x < nodeLocation.x) left++; else right++; } } if(left < right) return -1; else return 1; } private Point2D[] createBezierPoints(List<Point> list){ if(list.size()<3) return null ; int lastIdx = list.size()-1; //chord length parametrization double[] uis = new double[list.size()]; uis[0]=0; uis[1] = list.get(1).distance(list.get(0)); for (int i = 1; i < uis.length; i++) { Point cur = list.get(i); Point prev = list.get(i-1); uis[i]=uis[i-1]+ cur.distance(prev); } for (int i = 1; i < uis.length; i++) { uis[i] /= uis[lastIdx]; } double[] delta = new double[uis.length-1]; for (int i = 0; i < delta.length; i++) { double ui = uis[i]; double uin = uis[i+1]; delta[i] = uin-ui; } //FMILL tangent directions (chord length) Point2D[] tangents = new Point2D[list.size()]; for (int i = 1; i < list.size()-1; i++) { Point xBefore = list.get(i-1); Point xAfter = list.get(i+1); Point2D.Double tangent = new Point2D.Double (xAfter.x - xBefore.x, xAfter.y - xBefore.y); tangents[i] = tangent; } Point2D [] bezPoints = new Point2D[(list.size()-1)*2+list.size()]; //Catmull-Rom for (int i = 1; i < list.size()-1; i++) { Point b3i = list.get(i); Point2D b3ib = b3iBefore(b3i, delta[i-1], delta[i], tangents[i]); Point2D b3ia = b3iAfter(b3i, delta[i-1], delta[i], tangents[i]); bezPoints[3*i] = b3i; bezPoints[3*i-1] = b3ib; bezPoints[3*i+1] = b3ia; } bezPoints[0] = list.get(0); bezPoints[bezPoints.length-1] = list.get(list.size()-1); Point p0 = list.get(0); Point p1 = list.get(1); Point p2 = list.get(2); Point pL_2 = list.get(lastIdx-2); Point pL_1 = list.get(lastIdx-1); Point pL = list.get(lastIdx); Point2D m1 = besselTangent(delta[0], delta[1], p0, p1, p2); Point2D m0 = besselEndTangent(p0, p1, delta[0], m1); Point2D mLb = besselTangent(delta[delta.length-2], delta[delta.length-1], pL_2,pL_1, pL); Point2D mL = besselEndTangent(pL_1, pL, delta[delta.length-1], mLb); Point2D scaleM0 = scale(normalize(m0), p0.distance(p1));//increase distx/distxl to make curve rounder at the end Point2D scaleML = scale(normalize(mL), pL.distance(pL_1)); //Catmull-Rom for bessel points Point2D b30a = b3iAfter(p0, delta[0], delta[0],scaleM0); Point2D b33b = b3iBefore(pL, delta[delta.length-1], delta[delta.length-1],scaleML); bezPoints[1] = b30a; bezPoints[bezPoints.length-2] = b33b; return bezPoints; } private static Point2D besselTangent(double delta_ib, double delta_i, Point2D p0, Point2D p1 , Point2D p2){ double alpha_i = delta_ib/(delta_ib+delta_i); double x = (1-alpha_i)/delta_ib * (p1.getX() - p0.getX()) + alpha_i/delta_i * (p2.getX()-p1.getX()); double y = (1-alpha_i)/delta_ib * (p1.getY() - p0.getY()) + alpha_i/delta_i * (p2.getY()-p1.getY()); return new Point2D.Double(x,y); } private static Point2D besselEndTangent(Point2D p0, Point2D p1, double delta_u, Point2D m){ double x = 2*((p1.getX()-p0.getX())/delta_u) - m.getX(); double y = 2*((p1.getY()-p0.getY())/delta_u) - m.getY(); return new Point2D.Double(x,y); } private static Point2D b3iBefore(Point2D b3i, double delta_ib, double delta_i, Point2D li){ double x = b3i.getX() - (delta_ib/(3*(delta_ib+delta_i)))*li.getX(); double y = b3i.getY() - (delta_ib/(3*(delta_ib+delta_i)))*li.getY(); return new Point.Double(x,y); } private static Point2D b3iAfter(Point2D b3i, double delta_ib,double delta_i,Point2D li){ double x = b3i.getX() + (delta_i/(3*(delta_ib+delta_i)))*li.getX(); double y = b3i.getY() + (delta_i/(3*(delta_ib+delta_i)))*li.getY(); return new Point.Double(x,y); } //returns length of vector v private static double norm(Point2D v){ return Math.sqrt(v.getX()*v.getX()+v.getY()*v.getY()); } //returns unity vector of vector v private static Point2D normalize(Point2D v){ double norm = norm(v); if(norm==0) return new Point2D.Double(v.getX(), v.getY()); return new Point2D.Double(v.getX()/norm , v.getY()/norm); } //scale vector to size of length private static Point2D scale(Point2D v, double length){ Point2D tmp = normalize(v); return new Point2D.Double(tmp.getX()*length, tmp.getY()*length); } private GeneralPath subdivide2D (Point2D b0, Point2D b1, Point2D b2, Point2D b3) { //set 2nd intermediate point to endpoint //we could actually use another "better" point if we like to have a smoother curve double cutDistance = getTargetAnchorShape().getCutDistance(); double minDistance = cutDistance - ENDPOINT_DEVIATION; /** * if the cutDistance is valid the last segment of the curve * gets reduced by subdivision until the distance of the endpoint(epDistance) * satisfys the condition (cutDistance > epDistance > (cutDistance - ENDPOINT-DEVIATION) */ if(cutDistance > minDistance && minDistance > 0 ) { GeneralPath path = new GeneralPath(); path.moveTo(b0.getX(), b0.getY()); CubicCurve2D.Double curve = new CubicCurve2D.Double( b0.getX(), b0.getY(), b1.getX(), b1.getY(), b2.getX(), b2.getY(), b3.getX(), b3.getY()); CubicCurve2D right=new CubicCurve2D.Double(); CubicCurve2D left=new CubicCurve2D.Double(); curve.subdivide(left, right); double distance = b3.distance(left.getP2()); //if the distance is bigger as the cutDistance the left segment is added //and the right segment is divided again while(distance>cutDistance){ path.append(left, true); right.subdivide(left, right); distance = b3.distance(left.getP2()); //if the devision removed to much the left segment is divided while(distance < minDistance) { //changes the distance to ~ (distance+distance/2) left.subdivide(left, right); distance = b3.distance(left.getP2()); } } //append the last segment with (minDistance < distance < cutDistance) path.append(left, true); return path; } return null; } /** * Returns whether a specified local point pL is a part of the connection * widget. * First it make a rough bounds check * for Line Segments => use Super call (ConnectionWidget.isHitAt(pL)). * for self-edges => its sufficent to return getBounds.contains(pL). * for Splines => Interate over all Partitial segments of the curve and make * a minmax check with the bezier points. If pL is inside the minmax * rectangle of one segment the curve is constructed and subdivided until * the distance d between center point pC (of the bounding rectangle) * and pL is below HIT_DISTANCE_SQUARE, in this case it returns true. * If no no minmax check was successful or the subdivision lead to an * rectangle witch doesn`t contain pL return false. * @param localLocation the local location * @return true, if the location is a part of the connection widget */ @Override public boolean isHitAt(Point localLocation) { if(!isVisible() || !getBounds ().contains (localLocation)) return false; List<Point> controlPoints = getControlPoints (); if(controlPoints.size() <=2){ if(isReflexive()) return true; return super.isHitAt(localLocation); } if(bezierPoints != null) { for (int i = 0; i < bezierPoints.length-1; i+=3) { Point2D b0 = bezierPoints[i]; Point2D b1 = bezierPoints[i+1]; Point2D b2 = bezierPoints[i+2]; Point2D b3 = bezierPoints[i+3]; CubicCurve2D left = new CubicCurve2D.Double( b0.getX(), b0.getY(), b1.getX(), b1.getY(), b2.getX(), b2.getY(), b3.getX(), b3.getY()); Rectangle2D bounds = left.getBounds2D(); while(bounds.contains(localLocation)) { //calculate the center and use HIT_DISTANCE_SQUARE for a range check Point2D test = new Point2D.Double(bounds.getCenterX(),bounds.getCenterY()); if(test.distance(localLocation) < HIT_DISTANCE_SQUARE){ return true; } CubicCurve2D right = new CubicCurve2D.Double(); left.subdivide(left, right); Rectangle2D lb2d = left.getBounds2D(); Rectangle2D rb2d = right.getBounds2D(); if( lb2d.contains(localLocation)){ bounds = lb2d; } else if (rb2d.contains(localLocation)) { left = right; bounds = rb2d; } else { return false; } }//end while }//end for } return false; } }