package org.archstudio.bna.things.shapes; import java.awt.Shape; import java.awt.geom.Arc2D; import java.awt.geom.Point2D; import java.awt.geom.QuadCurve2D; import org.archstudio.bna.IBNAModel; import org.archstudio.bna.IThingListener; import org.archstudio.bna.ThingEvent; import org.archstudio.bna.keys.IThingKey; import org.archstudio.bna.logics.coordinating.StickPointLogic; import org.archstudio.bna.utils.BNAUtils; import org.archstudio.swtutils.constants.Orientation; import org.archstudio.sysutils.SystemUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.swt.graphics.Point; @NonNullByDefault public class CurvedSplineThing extends CurvedSplineThingBase { private Point referencePoint = new Point(0, 0); Shape shape; Point2D endpoint1StemPoint; Point2D endpoint2StemPoint; public CurvedSplineThing(@Nullable Object id) { super(id); updateProperties(); addThingListener(new IThingListener() { @Override public void thingChanged(ThingEvent thingEvent) { if (isShapeModifyingKey(thingEvent.getPropertyName())) { updateProperties(); } } }); } void updateProperties() { Point2D p1 = getEndpoint1(); Point2D p2 = getEndpoint2(); // curve amount double l = getCurve(); double spacing = getSpacing(); double a = l + spacing; // midpoint between p1 & p2 double mx = (p1.getX() + p2.getX()) / 2; double my = (p1.getY() + p2.getY()) / 2; // delta of p1 & p2 double dx = p2.getX() - p1.getX(); double dy = p2.getY() - p1.getY(); // angle between p1 & p2 double angle = Math.PI - Math.atan2(dy, dx); switch (getLoopOrientation()) { case NONE: { double cx = mx + 2 * l * Math.sin(angle); double cy = my + 2 * l * Math.cos(angle); double ax = mx + a * Math.sin(angle); double ay = my + a * Math.cos(angle); shape = new QuadCurve2D.Double(p1.getX(), p1.getY(), cx, cy, p2.getX(), p2.getY()); setAnchorPoint(new Point2D.Double(ax, ay)); double sx = mx + 5 * l * Math.sin(angle) / 4; double sy = my + 5 * l * Math.cos(angle) / 4; endpoint1StemPoint = new Point2D.Double(sx, sy); endpoint2StemPoint = new Point2D.Double(sx, sy); } break; case NORTHWEST: case NORTH: case NORTHEAST: case WEST: case EAST: case SOUTHWEST: case SOUTH: case SOUTHEAST: { double dl = Math.sqrt(dx * dx + dy * dy); double radius = dl / 2 + Math.abs(l) / 2; double ml = Math.sqrt(radius * radius - dl * dl / 4); double cx = mx + ml * Math.sin(angle); double cy = my + ml * Math.cos(angle); double p2Angle = Math.atan2(cy - p1.getY(), cx - p1.getX()); double p1Angle = Math.atan2(cy - p2.getY(), cx - p2.getX()); double angleExtent = SystemUtils.loop(0, p1Angle - p2Angle, 2 * Math.PI); shape = new Arc2D.Double(cx - radius, cy - radius, radius * 2, radius * 2, // (Math.PI - p1Angle) * 180 / Math.PI, angleExtent * 180 / Math.PI, Arc2D.OPEN); double ax = mx + (ml + radius + spacing) * Math.sin(angle); double ay = my + (ml + radius + spacing) * Math.cos(angle); setAnchorPoint(new Point2D.Double(ax, ay)); double arrowheadStemLength = 20; double circumference = radius * 2 * Math.PI; double dAngle = 2 * Math.PI * arrowheadStemLength / circumference; double d2Angle = p1Angle + Math.PI - dAngle; double d1Angle = p2Angle + Math.PI + dAngle; endpoint1StemPoint = new Point2D.Double(cx + radius * Math.cos(d1Angle), cy + radius * Math.sin(d1Angle)); endpoint2StemPoint = new Point2D.Double(cx + radius * Math.cos(d2Angle), cy + radius * Math.sin(d2Angle)); } break; default: //throw new IllegalArgumentException(getLoopOrientation().toString()); } setBoundingBox(BNAUtils.toRectangle(shape.getBounds2D())); } @Override public boolean shouldIncrementRotatingOffset() { return isSelected(); } @Override public Point2D getSecondaryPoint(IBNAModel model, StickPointLogic stickLogic, IThingKey<Point2D> pointKey) { if (stickLogic.isLoopingPoint(this, ENDPOINT_1_KEY, ENDPOINT_2_KEY)) { if (ENDPOINT_1_KEY.equals(pointKey)) { return endpoint1StemPoint; } else if (ENDPOINT_2_KEY.equals(pointKey)) { return endpoint2StemPoint; } else { throw new IllegalArgumentException(pointKey.toString()); } } else { if (ENDPOINT_1_KEY.equals(pointKey) || ENDPOINT_2_KEY.equals(pointKey)) { Point2D p1 = stickLogic.getStuckPoint(this, ENDPOINT_1_KEY); Point2D p2 = stickLogic.getStuckPoint(this, ENDPOINT_2_KEY); double l = -getCurve(); double dx = p2.getX() - p1.getX(); double dy = p2.getY() - p1.getY(); double angle = Math.PI - Math.atan2(dy, dx); double mx = (p1.getX() + p2.getX()) / 2; double my = (p1.getY() + p2.getY()) / 2; double cx = mx + l * -Math.sin(angle); double cy = my + l * -Math.cos(angle); return new Point2D.Double(cx, cy); } else { throw new IllegalArgumentException(pointKey.toString()); } } } @Override public Orientation getLoopOrientation(IBNAModel model, StickPointLogic stickLogic, IThingKey<Point2D> pointKey) { if (ENDPOINT_1_KEY.equals(pointKey) || ENDPOINT_2_KEY.equals(pointKey)) { if (stickLogic.isLoopingPoint(this, ENDPOINT_1_KEY, ENDPOINT_2_KEY)) { return getCurve() >= 0 ? Orientation.NORTHEAST : Orientation.SOUTHWEST; } return Orientation.NONE; } throw new IllegalArgumentException(pointKey.toString()); } @Override public Point getReferencePoint() { return BNAUtils.clone(referencePoint); } @Override public void setReferencePoint(Point value) { double dx = value.x - referencePoint.x; double dy = value.y - referencePoint.y; referencePoint.x = value.x; referencePoint.y = value.y; Point2D p1 = getEndpoint1(); p1.setLocation(p1.getX() + dx, p1.getY() + dy); setEndpoint1(p1); Point2D p2 = getEndpoint2(); p2.setLocation(p2.getX() + dx, p2.getY() + dy); setEndpoint2(p2); } }