/******************************************************************************* * Copyright 2005-2006, CHISEL Group, University of Victoria, Victoria, BC, * Canada. 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: The Chisel Group, University of Victoria *******************************************************************************/ package org.eclipse.zest.core.widgets.internal; import org.eclipse.draw2d.PolylineConnection; import org.eclipse.draw2d.RectangleFigure; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.PointList; /* * A connection that draws an arc between nodes, based on a given depth for the * arc. This connection is drawn as an arc, defined as the circular arc with the * chord (ax, ay) - (bx, by) (where a and b are the anchors) and a depth d * defined as the maximum distance from any point on the chord (i.e. a vector * normal to the chord with magnitude d). * * @author Del Myers */ // @tag zest(bug(154391-ArcEnds(fix))) : force the endpoints to match by using a // polyline connection. // This will be more accurate than the regular ArcConnection, but it may be // slower. public class PolylineArcConnection extends PolylineConnection { private int depth; private boolean inverse = false; private static final float PI = (float) 3.14159; private RectangleFigure center; { this.depth = 0; center = new RectangleFigure(); } /* * (non-Javadoc) * * @see org.eclipse.draw2d.Polyline#setPoints(org.eclipse.draw2d.geometry.PointList) */ public void setPoints(PointList points) { updateArc(points); } /** * This method is not supported by this kind of connection. Points are * calculated based on the arc definition. */ public void addPoint(Point pt) { } /** * @param depth * the depth to set */ public void setDepth(int depth) { this.inverse = depth < 0 ? true : false; this.depth = depth; updateArc(getPoints()); } protected void updateArc(PointList pointList) { if (pointList.size() < 2) { return; } if (center.getParent() == this) { remove(center); } Point start = pointList.getFirstPoint(); Point end = pointList.getLastPoint(); if (depth == 0) { super.setPoints(pointList); return; } PointList points = new PointList(); float arcStart = 0; float arcEnd = 0; float arcLength = 0; float cartCenterX = 0; float cartCenterY = 0; float r = 0; float x1 = start.x; float y1 = -start.y; float x2 = end.x; float y2 = -end.y; float depth = this.depth; if (start.equals(end)) { // do a circle arcStart = -PI / 2; arcLength = PI * 2; // @tag drawing(arcs) : try making the center on a line from the // center of the parent figure. cartCenterX = x1; cartCenterY = y1 + depth / 2; r = depth / 2; } else { if (x1 >= x2) { depth = -depth; } // the center of the chord float cartChordX = (x2 + x1) / 2; float cartChordY = (y2 + y1) / 2; float chordLength = (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); if (Math.abs(depth) >= chordLength / 2) { depth = (chordLength / 3) * (depth / Math.abs(depth)); } r = ((((chordLength / 2) * (chordLength / 2)) + (depth * depth)) / (2 * depth)); // Find a vector normal to the chord. This will be used for // translating the // circle back to screen coordinates. float chordNormal = 0; if (Math.abs(x1 - x2) <= .000001) { // slope of 0. NaN is easier to detect than 0. chordNormal = Float.NaN; } else if (Math.abs(y1 - y2) <= 0.000001) { // infinite slope. chordNormal = Float.POSITIVE_INFINITY; } else { chordNormal = -1 * (y2 - y1) / (x2 - x1); } float th1; if (Float.isNaN(chordNormal)) { cartCenterX = (y1 > y2) ? (cartChordX - r + (depth)) : (cartChordX + r - (depth)); cartCenterY = cartChordY; th1 = PI / 2; } else if (Float.isInfinite(chordNormal)) { cartCenterX = cartChordX; cartCenterY = cartChordY + r - (depth); th1 = 0; } else { // assume that the center of the chord is on the origin. th1 = (float) Math.atan(chordNormal); cartCenterX = (r - (depth)) * (float) Math.sin(th1) + cartChordX;// cartChordX+r // -depth; cartCenterY = (r - (depth)) * (float) Math.cos(th1) + cartChordY;// cartChordY+r-depth; } // figure out the new angles // translate the points to the center of the circle float cartArcX1 = x1 - cartCenterX; float cartArcY1 = y1 - cartCenterY; float cartArcX2 = x2 - cartCenterX; float cartArcY2 = y2 - cartCenterY; // calculate the length of the arc arcStart = angleRadians(cartArcX1, cartArcY1); arcEnd = angleRadians(cartArcX2, cartArcY2); if (arcEnd < arcStart) { arcEnd = arcEnd + PI + PI; } // make sure that we are between the two nodes. arcLength = arcEnd - arcStart; float pad = PI / Math.abs(r); arcStart += pad; arcEnd -= pad; arcLength = (arcEnd) - (arcStart); if (inverse) { arcLength = (2 * PI - arcLength); } } // calculate the points r = Math.abs(r); float x = 0, y = 0; Point p = null; points.addPoint(start); float length = arcLength * r; int steps = (int) length / 16; if (steps < 10 && length > 10) { steps = 10; } if (arcLength < PI / 4 && steps > 6) { steps = 6; } if (steps < 4 && length > 4) { steps = 4; } float stepSize = arcLength / steps; if (inverse) { float step = arcStart - stepSize; for (int i = 1; i < steps; i++, step -= stepSize) { x = (r) * (float) Math.cos(step) + cartCenterX; y = (r) * (float) Math.sin(step) + cartCenterY; p = new Point(Math.round(x), Math.round(-y)); points.addPoint(p); } } else { float step = stepSize + arcStart; for (int i = 1; i < steps; i++, step += stepSize) { x = (r) * (float) Math.cos(step) + cartCenterX; y = (r) * (float) Math.sin(step) + cartCenterY; p = new Point(Math.round(x), Math.round(-y)); points.addPoint(p); } } points.addPoint(end); super.setPoints(points); } /* * Gets an angle in radians for the x, y coordinates. The angle will be * between 0 and 2PI. */ float angleRadians(float x, float y) { float theta = (float) Math.atan(y / x); switch (findQuadrant(x, y)) { case 1: return theta; case 2: return (theta + PI); case 4: theta = (theta + PI); case 3: return (theta + PI); default: return theta; } } // find the quadrant, assume points are centered at 0,0 protected int findQuadrant(float x, float y) { if (y > 0) { if (x > 0) { return 1; } else { return 2; } } else { if (x > 0) { return 4; } else { return 3; } } } }