| /******************************************************************************* |
| * 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; |
| } |
| } |
| } |
| } |