From 33d355e52f78a8c2b01601a9e8372430b7589d9a Mon Sep 17 00:00:00 2001 From: lempinen Date: Fri, 14 Jan 2011 11:16:46 +0000 Subject: [PATCH] New routers for flows and dependencies. They are functional, but should be tweaked for better appearance. git-svn-id: https://www.simantics.org/svn/simantics/sysdyn/trunk@19373 ac1ea38d-2e2b-0410-8846-a27921b304fc --- .../editor/participant/PointerInteractor.java | 168 +++--- .../editor/participant/SysdynConnectTool.java | 32 +- .../ui/editor/routing/DependencyRouter.java | 122 ++++- .../sysdyn/ui/editor/routing/FlowRouter.java | 184 +++++++ .../ui/editor/routing/SysdynLocalRouter.java | 476 ++++++++++++++++ .../elements2/connections/Dependencies.java | 244 --------- .../DependencyConnectionFactory.java | 4 +- .../connections/DependencyEdgeClass.java | 11 +- .../elements2/connections/DependencyNode.java | 17 +- .../connections/FlowConnectionFactory.java | 4 +- .../elements2/connections/FlowEdgeClass.java | 508 +++++++++++++----- .../elements2/connections/FlowEdgeNode.java | 78 +++ .../ui/elements2/connections/FlowNode.java | 44 +- 13 files changed, 1379 insertions(+), 513 deletions(-) create mode 100644 org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/FlowRouter.java create mode 100644 org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/SysdynLocalRouter.java delete mode 100644 org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/Dependencies.java create mode 100644 org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowEdgeNode.java diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/participant/PointerInteractor.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/participant/PointerInteractor.java index 58860991..70a99882 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/participant/PointerInteractor.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/participant/PointerInteractor.java @@ -36,10 +36,10 @@ import org.simantics.g2d.event.MouseEvent.MouseButtonPressedEvent; import org.simantics.g2d.participant.KeyUtil; import org.simantics.g2d.participant.MouseUtil; import org.simantics.g2d.participant.TransformUtil; -import org.simantics.g2d.routing.RouterFactory; import org.simantics.g2d.utils.GeometryUtils; import org.simantics.sysdyn.ui.editor.participant.SysdynElementClassProviders.ISysdynElementClassProvider; import org.simantics.sysdyn.ui.editor.routing.DependencyRouter; +import org.simantics.sysdyn.ui.editor.routing.FlowRouter; import org.simantics.sysdyn.ui.elements2.AuxiliaryFactory; import org.simantics.sysdyn.ui.elements2.CloudFactory; import org.simantics.sysdyn.ui.elements2.InputFactory; @@ -63,92 +63,82 @@ import org.simantics.sysdyn.ui.elements2.connections.ConnectionClasses; */ public class PointerInteractor extends org.simantics.g2d.diagram.participant.pointertool.PointerInteractor { - @Dependency Selection selection; - @Dependency KeyUtil keys; - @Dependency TransformUtil util; - @Dependency PickContext pickContext; - @Dependency MouseUtil mice; - @Reference TerminalPainter terminalPainter; - - public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, boolean doubleClickEdit, IElementClassProvider newConnectionClassProvider) { - super(clickSelect, boxSelect, dragElement, dndDragElement, connect, doubleClickEdit, newConnectionClassProvider); - } - - @EventHandler(priority = TOOL_PRIORITY) - public boolean handlePress(MouseButtonPressedEvent me) { - if (!connects()) return false; -// if (me.button != MouseEvent.LEFT_BUTTON) return false; - if (getHint(Hints.KEY_TOOL) != Hints.POINTERTOOL) return false; - assertDependencies(); - Point2D curCanvasPos = util.controlToCanvas(me.controlPosition, null); - - // Pick Terminal - TerminalInfo ti = pickTerminal(me.controlPosition); - - if((me.stateMask & MouseEvent.ALT_MASK) == 0) return false; - - if(elementClassProvider != null) { - ConnectTool2 bsi = null; - if (ti != null) { - - IElement terminalElement = ti.e; - -// ElementClass connectionClass = null; - if( me.button == MouseEvent.LEFT_BUTTON) { - if(terminalElement.getElementClass().getId().equals(CloudFactory.class.getSimpleName())) return false; -// diagram.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(false, false)); - diagram.setHint(DiagramHints.ROUTE_ALGORITHM, new DependencyRouter()); -// connectionClass = elementClassProvider.get(ConnectionClasses.DEPENDENCY); - ISysdynElementClassProvider secp = (ISysdynElementClassProvider)elementClassProvider; - secp.put(ElementClasses.CONNECTION, elementClassProvider.get(ConnectionClasses.DEPENDENCY)); - - } else if (me.button == MouseEvent.RIGHT_BUTTON) { - String id = terminalElement.getElementClass().getId(); - if(id.equals(AuxiliaryFactory.class.getSimpleName()) - || id.equals(InputFactory.class.getSimpleName()) - || id.equals(ModuleFactory.class.getSimpleName())) return false; - diagram.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, false)); -// connectionClass = elementClassProvider.get(ConnectionClasses.FLOW); - ISysdynElementClassProvider secp = (ISysdynElementClassProvider)elementClassProvider; - secp.put(ElementClasses.CONNECTION, elementClassProvider.get(ConnectionClasses.FLOW)); - - } - - IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR); - if (advisor == null || (advisor != null && advisor.canBeginConnection(null, ti.e, ti.t))) { -// bsi = new ConnectTool(diagram, connectionClass, ti.e, ti.t, me.mouseId, curCanvasPos); - bsi = new SysdynConnectTool(ti, me.mouseId, curCanvasPos); - } - } - else if (me.button == MouseEvent.RIGHT_BUTTON) { - - /* - ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR); - if(snapAdvisor != null) - snapAdvisor.snap(curCanvasPos); - */ - // Start connection out of thin air, without a terminal. - diagram.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, true)); -// bsi = new ConnectTool(diagram, elementClassProvider.get(ConnectionClasses.FLOW), null, null, me.mouseId, curCanvasPos); - ISysdynElementClassProvider secp = (ISysdynElementClassProvider)elementClassProvider; - secp.put(ElementClasses.CONNECTION, elementClassProvider.get(ConnectionClasses.FLOW)); - bsi = new SysdynConnectTool(null, me.mouseId, curCanvasPos); // FIXME - } - - if (bsi != null) { - getContext().add(bsi); - return true; - } - - } - return false; - } - - public List pickTerminals(Point2D controlPos) - { - Rectangle2D controlPickRect = new Rectangle2D.Double(controlPos.getX()-PointerInteractor.PICK_DIST, controlPos.getY()-PointerInteractor.PICK_DIST, PointerInteractor.PICK_DIST*2+1, PointerInteractor.PICK_DIST*2+1); - Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform()); - List ti = TerminalUtil.pickTerminals(diagram, canvasPickRect, false, true); - return ti; - } + @Dependency Selection selection; + @Dependency KeyUtil keys; + @Dependency TransformUtil util; + @Dependency PickContext pickContext; + @Dependency MouseUtil mice; + @Reference TerminalPainter terminalPainter; + + public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, boolean doubleClickEdit, IElementClassProvider newConnectionClassProvider) { + super(clickSelect, boxSelect, dragElement, dndDragElement, connect, doubleClickEdit, newConnectionClassProvider); + } + + @EventHandler(priority = TOOL_PRIORITY) + public boolean handlePress(MouseButtonPressedEvent me) { + if (!connects()) return false; + if (getHint(Hints.KEY_TOOL) != Hints.POINTERTOOL) return false; + assertDependencies(); + Point2D curCanvasPos = util.controlToCanvas(me.controlPosition, null); + + // Pick Terminal + TerminalInfo ti = pickTerminal(me.controlPosition); + + if((me.stateMask & MouseEvent.ALT_MASK) == 0) return false; + + if(elementClassProvider != null) { + ConnectTool2 bsi = null; + if (ti != null) { + + IElement terminalElement = ti.e; + + if( me.button == MouseEvent.LEFT_BUTTON) { + if(terminalElement.getElementClass().getId().equals(CloudFactory.class.getSimpleName())) return false; + diagram.setHint(DiagramHints.ROUTE_ALGORITHM, new DependencyRouter()); + diagram.setHint(DiagramHints.KEY_USE_CONNECTION_FLAGS, false); + ISysdynElementClassProvider secp = (ISysdynElementClassProvider)elementClassProvider; + secp.put(ElementClasses.CONNECTION, elementClassProvider.get(ConnectionClasses.DEPENDENCY)); + + } else if (me.button == MouseEvent.RIGHT_BUTTON) { + String id = terminalElement.getElementClass().getId(); + if(id.equals(AuxiliaryFactory.class.getSimpleName()) + || id.equals(InputFactory.class.getSimpleName()) + || id.equals(ModuleFactory.class.getSimpleName())) return false; + diagram.setHint(DiagramHints.ROUTE_ALGORITHM, new FlowRouter(false)); + diagram.setHint(DiagramHints.KEY_USE_CONNECTION_FLAGS, true); + ISysdynElementClassProvider secp = (ISysdynElementClassProvider)elementClassProvider; + secp.put(ElementClasses.CONNECTION, elementClassProvider.get(ConnectionClasses.FLOW)); + + } + + IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR); + if (advisor == null || (advisor != null && advisor.canBeginConnection(null, ti.e, ti.t))) { + bsi = new SysdynConnectTool(ti, me.mouseId, curCanvasPos); + } + } + else if (me.button == MouseEvent.RIGHT_BUTTON) { + // Start connection out of thin air, without a terminal. + diagram.setHint(DiagramHints.ROUTE_ALGORITHM, new FlowRouter(false)); + diagram.setHint(DiagramHints.KEY_USE_CONNECTION_FLAGS, true); + ISysdynElementClassProvider secp = (ISysdynElementClassProvider)elementClassProvider; + secp.put(ElementClasses.CONNECTION, elementClassProvider.get(ConnectionClasses.FLOW)); + bsi = new SysdynConnectTool(null, me.mouseId, curCanvasPos); // FIXME + } + + if (bsi != null) { + getContext().add(bsi); + return true; + } + + } + return false; + } + + public List pickTerminals(Point2D controlPos) + { + Rectangle2D controlPickRect = new Rectangle2D.Double(controlPos.getX()-PointerInteractor.PICK_DIST, controlPos.getY()-PointerInteractor.PICK_DIST, PointerInteractor.PICK_DIST*2+1, PointerInteractor.PICK_DIST*2+1); + Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform()); + List ti = TerminalUtil.pickTerminals(diagram, canvasPickRect, false, true); + return ti; + } } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/participant/SysdynConnectTool.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/participant/SysdynConnectTool.java index 38f0b7de..ad4e83e7 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/participant/SysdynConnectTool.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/participant/SysdynConnectTool.java @@ -5,6 +5,7 @@ import java.awt.Color; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -26,7 +27,6 @@ import org.simantics.diagram.ui.DiagramModelHints; import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit; import org.simantics.g2d.connection.IConnectionAdvisor; import org.simantics.g2d.diagram.DiagramHints; -import org.simantics.g2d.diagram.DiagramUtils; import org.simantics.g2d.diagram.handler.Topology.Terminal; import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil; import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo; @@ -54,6 +54,8 @@ import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.nodes.ShapeNode; import org.simantics.structural2.modelingRules.ConnectionJudgement; import org.simantics.sysdyn.SysdynResource; +import org.simantics.sysdyn.ui.editor.routing.FlowRouter; +import org.simantics.sysdyn.ui.elements2.ValveFactory.ValveSceneGraph; import org.simantics.sysdyn.ui.elements2.connections.ConnectionClasses; import org.simantics.ui.SimanticsUI; import org.simantics.utils.datastructures.Callback; @@ -140,6 +142,8 @@ public class SysdynConnectTool extends ConnectTool2 { return segments; } + public interface SysdynConnection extends IConnection { } + protected void updateSG() { if (controlPoints.isEmpty()) return; @@ -149,7 +153,7 @@ public class SysdynConnectTool extends ConnectTool2 { final List segments = toSegments(controlPoints); //System.out.println("controlpoints: " + controlPoints); //System.out.println("segments: " + segments); - router.route(new IConnection() { + router.route(new SysdynConnection() { @Override public Collection getSegments() { return segments; @@ -166,17 +170,24 @@ public class SysdynConnectTool extends ConnectTool2 { } private Connector getConnector(ControlPoint cp) { - Connector c = new Connector(); + Connector c = new Connector(); c.x = cp.getPosition().getX(); c.y = cp.getPosition().getY(); TerminalInfo ti = cp.getAttachedTerminal(); if (ti != null && (ti == startFlag || ti != endFlag)) { - //System.out.println("CP1: " + cp); - c.parentObstacle = DiagramUtils.getObstacleShape(ti.e); + if(ti.e.getElementClass().containsClass(ValveSceneGraph.class)) { + Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e).getBounds2D(); + c.parentObstacle = new Rectangle2D.Double( + bounds.getCenterX() - FlowRouter.OFFSET, + bounds.getCenterY() - FlowRouter.OFFSET, + FlowRouter.OFFSET * 2, + FlowRouter.OFFSET * 2); + } else { + c.parentObstacle = ElementUtils.getElementBoundsOnDiagram(ti.e).getBounds2D(); + } ConnectionDirectionUtil.determineAllowedDirections(c); } else { - //System.out.println("CP2: " + cp); c.parentObstacle = GeometryUtils.transformRectangle(AffineTransform.getTranslateInstance(c.x, c.y), BranchPointClass.DEFAULT_IMAGE2.getBounds()); c.allowedDirections = toAllowedDirections(cp.getDirection()); @@ -230,6 +241,11 @@ public class SysdynConnectTool extends ConnectTool2 { final Terminal st = startTerminal != null ? startTerminal.t : null; if(se.equals(endElement)) return null; + if(Boolean.FALSE.equals(diagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS)) && endElement == null) { + return null; + } else { + System.out.println("FOK"); + } if(endElement == null && endTerminal == null) return advisor.canBeConnected(null, se, st, endElement, endTerminal); @@ -371,7 +387,9 @@ public class SysdynConnectTool extends ConnectTool2 { } @Override - protected void createConnection() { + protected void createConnection() { + + if(this.connectionJudgment == null) return; final UndoContext uctx = diagram.getHint(DiagramModelHints.KEY_UNDO_CONTEXT); final ConnectionJudgement judgment = this.connectionJudgment; diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/DependencyRouter.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/DependencyRouter.java index 24e527a9..e7d42fc5 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/DependencyRouter.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/DependencyRouter.java @@ -2,11 +2,13 @@ package org.simantics.sysdyn.ui.editor.routing; import java.awt.geom.Arc2D; import java.awt.geom.Path2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; import org.simantics.g2d.routing.IConnection; import org.simantics.g2d.routing.IConnection.Connector; import org.simantics.g2d.routing.IRouter2; -import org.simantics.sysdyn.ui.elements2.connections.Dependencies; +import org.simantics.sysdyn.ui.elements2.connections.Arcs; import org.simantics.utils.datastructures.Pair; public class DependencyRouter implements IRouter2 { @@ -20,7 +22,7 @@ public class DependencyRouter implements IRouter2 { Connector end = connection.getEnd(seg); Pair shapes = new Pair(new Arc2D.Double(), new Path2D.Double()); - Dependencies.createArrowShape(shapes, + createArrowShape(shapes, begin.parentObstacle, end.parentObstacle, 0.1); @@ -31,4 +33,120 @@ public class DependencyRouter implements IRouter2 { connection.setPath(seg, path); } + + /* + * Total length of the arrow is ARROW_LENGTH1 + ARROW_LENGTH2 + */ + public static double ARROW_LENGTH1 = 0.2; + public static double ARROW_LENGTH2 = 1.0; + public static double ARROW_WIDTH = 0.5; + + + private static Path2D createArrow(Path2D shape, double x, double y, double dx, double dy) { + if(shape == null) + shape = new Path2D.Double(); + else + shape.reset(); + + shape.moveTo(x+ARROW_LENGTH1*dx, y+ARROW_LENGTH1*dy); + x -= ARROW_LENGTH2*dx; + y -= ARROW_LENGTH2*dy; + shape.lineTo(x-ARROW_WIDTH*dy, y+ARROW_WIDTH*dx); + shape.lineTo(x+ARROW_WIDTH*dy, y-ARROW_WIDTH*dx); + shape.closePath(); + return shape; + } + + public static Arc2D createArc(Arc2D arc, Rectangle2D tail, Rectangle2D head, double angle) { + double x0 = tail.getCenterX(); + double y0 = tail.getCenterY(); + double x1 = head.getCenterX(); + double y1 = head.getCenterY(); + +// System.out.println("createArrowShape " + x0 + " " + y0 + " " + x1 + " " + y1); + + double offset = + Math.abs(angle) < 1.0e-6 + ? 1e3 * Math.signum(angle) + : Math.tan(Math.PI*0.5-angle)*0.5; + + double cx = 0.5*(x0+x1) + offset * (y1-y0); + double cy = 0.5*(y0+y1) + offset * (x0-x1); + double dx0 = x0 - cx; + double dy0 = y0 - cy; + double dx1 = x1 - cx; + double dy1 = y1 - cy; + + double r = Math.sqrt(dx0*dx0 + dy0*dy0); + +// Rectangle2D bounds = new Rectangle2D.Double(); +// tail.getBounds(bounds); + double angle0 = Arcs.nextIntersectingAngle(cx, cy, r, + Math.atan2(-dy0, dx0), tail, angle < 0.0); +// head.getBounds(bounds); + double angle1 = Arcs.nextIntersectingAngle(cx, cy, r, + Math.atan2(-dy1, dx1), head, angle > 0.0); + double extent = angle1-angle0; + //double arcAngle = angle0; + if(angle < 0.0) { + double temp = angle0; + angle0 = angle1; + angle1 = temp; + extent = -extent; + } + if(extent < 0) + extent += Math.PI*2.0; + else if(extent >= 360.0) + extent -= Math.PI*2.0; + if(arc == null) + arc = new Arc2D.Double(); + arc.setArc(cx-r, cy-r, 2*r, 2*r, + Math.toDegrees(angle0), + Math.toDegrees(extent), + Arc2D.OPEN); +// + return arc; + } + + public static Point2D computeCenter(Rectangle2D tail, Rectangle2D head, double angle) { + + double x0 = tail.getCenterX(); + double y0 = tail.getCenterY(); + double x1 = head.getCenterX(); + double y1 = head.getCenterY(); + +// System.out.println("createArrowShape " + x0 + " " + y0 + " " + x1 + " " + y1); + + double offset = + Math.abs(angle) < 1.0e-6 + ? 1e3 * Math.signum(angle) + : Math.tan(Math.PI*0.5-angle)*0.5; + + double cx = 0.5*(x0+x1) + offset * (y1-y0); + double cy = 0.5*(y0+y1) + offset * (x0-x1); + + return new Point2D.Double(cx, cy); + + } + + public static Pair createArrowShape(Pair shapes, Rectangle2D tail, Rectangle2D head, double angle) { + if(shapes == null || shapes.first == null || shapes.second == null) { + shapes = new Pair(new Arc2D.Double(), new Path2D.Double()); + } + + createArc(shapes.first, tail, head, angle); + + double angle0 = Math.toRadians(shapes.first.getAngleStart()); + double angle1 = Math.toRadians(shapes.first.getAngleStart() + shapes.first.getAngleExtent()); + double x = Math.cos(angle > 0.0 ? angle1 : angle0); + double y = -Math.sin(angle > 0.0 ? angle1 : angle0); + double r = shapes.first.getHeight() / 2; + + createArrow(shapes.second, shapes.first.getCenterX() + r*x, shapes.first.getCenterY() + r*y, + angle < 0.0 ? -y : y, + angle > 0.0 ? -x : x); + + return shapes; + + } } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/FlowRouter.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/FlowRouter.java new file mode 100644 index 00000000..57697977 --- /dev/null +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/FlowRouter.java @@ -0,0 +1,184 @@ +package org.simantics.sysdyn.ui.editor.routing; + +import java.awt.geom.AffineTransform; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Rectangle2D; +import java.util.Collection; + +import org.simantics.g2d.routing.Constants; +import org.simantics.g2d.routing.IConnection; +import org.simantics.g2d.routing.IConnection.Connector; +import org.simantics.g2d.routing.IRouter2; +import org.simantics.sysdyn.ui.editor.participant.SysdynConnectTool.SysdynConnection; +import org.simantics.sysdyn.ui.elements2.connections.Flows; + +public class FlowRouter implements IRouter2{ + + SysdynLocalRouter localRouter; + + public static final double OFFSET = 1.0; + + public FlowRouter() { + this(false); + } + + public FlowRouter(boolean roundCorners) { + this.localRouter = new SysdynLocalRouter(); + } + + private Path2D route(double beginX, double beginY, int sDir, Rectangle2D beginObstacle, + double endX, double endY, int tDir, Rectangle2D endObstacle) { + localRouter.sx = beginX; + localRouter.sy = beginY; + if(beginObstacle == null) { + localRouter.aMinX = beginX; + localRouter.aMinY = beginY; + localRouter.aMaxX = beginX; + localRouter.aMaxY = beginY; + } + else { + localRouter.aMinX = beginObstacle.getMinX(); + localRouter.aMinY = beginObstacle.getMinY(); + localRouter.aMaxX = beginObstacle.getMaxX(); + localRouter.aMaxY = beginObstacle.getMaxY(); + } + localRouter.sourceDirection = sDir; + + localRouter.tx = endX; + localRouter.ty = endY; + if(endObstacle == null) { + localRouter.bMinX = endX; + localRouter.bMinY = endY; + localRouter.bMaxX = endX; + localRouter.bMaxY = endY; + } + else { + localRouter.bMinX = endObstacle.getMinX(); + localRouter.bMinY = endObstacle.getMinY(); + localRouter.bMaxX = endObstacle.getMaxX(); + localRouter.bMaxY = endObstacle.getMaxY(); + } + localRouter.targetDirection = tDir; + + // adjust flows to start and stop within the obstacle + if(sDir == Constants.EAST || sDir == Constants.WEST) { + localRouter.aMinY = localRouter.aMinY + OFFSET; + localRouter.aMaxY = localRouter.aMaxY - OFFSET; + } + if(tDir == Constants.EAST || tDir == Constants.WEST) { + localRouter.bMinY = localRouter.bMinY + OFFSET; + localRouter.bMaxY = localRouter.bMaxY - OFFSET; + } + if(sDir == Constants.SOUTH || sDir == Constants.NORTH) { + localRouter.aMinX = localRouter.aMinX + OFFSET; + localRouter.aMaxX = localRouter.aMaxX - OFFSET; + } + if(tDir == Constants.SOUTH || tDir == Constants.NORTH) { + localRouter.bMinX = localRouter.bMinX + OFFSET; + localRouter.bMaxX = localRouter.bMaxX - OFFSET; + } + if(localRouter.sx > localRouter.aMaxX) + localRouter.sx = localRouter.aMaxX; + if(localRouter.sx < localRouter.aMinX) + localRouter.sx = localRouter.aMinX; + if(localRouter.sy > localRouter.aMaxY) + localRouter.sy = localRouter.aMaxY; + if(localRouter.sy < localRouter.aMinY) + localRouter.sy = localRouter.aMinY; + if(localRouter.tx > localRouter.bMaxX) + localRouter.tx = localRouter.bMaxX; + if(localRouter.tx < localRouter.bMinX) + localRouter.tx = localRouter.bMinX; + if(localRouter.ty > localRouter.bMaxY) + localRouter.ty = localRouter.bMaxY; + if(localRouter.ty < localRouter.bMinY) + localRouter.ty = localRouter.bMinY; + + localRouter.route(); + + Path2D completePath = new Path2D.Double(); + + double[] coordinates = new double[localRouter.points.size()]; + for(int i = 0; i < localRouter.points.size() ; i++) + coordinates[i] = localRouter.points.get(i); + + boolean vertical = false; + + if(coordinates.length > 3) { + if(coordinates[0] == coordinates[2]) { + // starts to south or north + double[] tempCoordinates = new double[coordinates.length - 1]; + for(int i = 0; i < coordinates.length - 1 ; i++) + tempCoordinates[i] = coordinates[i + 1]; + + coordinates = new double[tempCoordinates.length]; + for(int i = 0; i < tempCoordinates.length ; i++) + coordinates[i] = tempCoordinates[i]; + vertical = true; + } + } + + Flows.createOffsetLine(completePath, vertical, OFFSET, coordinates); + Flows.createOffsetLine(completePath, vertical, -OFFSET, coordinates); +// return localRouter.path; + +// Flows.createLines(completePath, false, beginObstacle, endObstacle); + return completePath; + } + + @Override + public void route(IConnection connection) { + + if(!(connection instanceof SysdynConnection)) { + return; + } + Collection segments = connection.getSegments(); + if(segments.size() == 1) + for(Object seg : segments) { + Connector begin = connection.getBegin(seg); + Connector end = connection.getEnd(seg); + + double bestLength = Double.POSITIVE_INFINITY; + Path2D bestPath = null; + +// for(int sDir : Constants.POSSIBLE_DIRECTIONS[begin.allowedDirections]) + for(int sDir : Constants.POSSIBLE_DIRECTIONS[15]) +// for(int tDir : Constants.POSSIBLE_DIRECTIONS[end.allowedDirections]) { + for(int tDir : Constants.POSSIBLE_DIRECTIONS[15]) { + Path2D path = route(begin.x, begin.y, sDir, begin.parentObstacle, + end.x, end.y, tDir, end.parentObstacle); + + double length = pathCost(path); + if(length < bestLength) { + bestLength = length; + bestPath = path; + } + } + + if(bestPath != null) + connection.setPath(seg, bestPath); + } + } + + final static AffineTransform IDENTITY = new AffineTransform(); + + static double pathCost(Path2D path) { + double length = 0.0; + PathIterator it = path.getPathIterator(IDENTITY); + double[] temp = new double[6]; + double x=0.0, y=0.0; + double bendCount = 0.0; + while(!it.isDone()) { + bendCount += 1.0; + if(it.currentSegment(temp) != PathIterator.SEG_MOVETO) + length += Math.abs(x - temp[0] + y - temp[1]); + x = temp[0]; + y = temp[1]; + it.next(); + } + //return length * (6.0 + bendCount); + return bendCount - 1.0 / length; + } + +} diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/SysdynLocalRouter.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/SysdynLocalRouter.java new file mode 100644 index 00000000..26a3d97f --- /dev/null +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/editor/routing/SysdynLocalRouter.java @@ -0,0 +1,476 @@ +package org.simantics.sysdyn.ui.editor.routing; + +import java.awt.geom.Path2D; +import java.util.ArrayList; + +import org.simantics.g2d.routing.Constants; + +public class SysdynLocalRouter { + + static final double OFFSET = 1.0; + + double aMinX; + double aMinY; + double aMaxX; + double aMaxY; + + double bMinX; + double bMinY; + double bMaxX; + double bMaxY; + + double sx; + double sy; + + double tx; + double ty; + + int sourceDirection; + int targetDirection; + + ArrayList points; + Path2D path; + + public SysdynLocalRouter() { + } + + /** + * Case where both source and target connection directions are to east. + */ + void routeEast() { + if (bMinX >= aMaxX || tx >= 0 && !(bMaxY < aMinY || aMaxY < bMinY)) { + if (ty != 0.0) { + /* ______ ______ + * | | | | + * | |----\ | | + * | | \--->| | + * |______| |______| + */ + double mx = 0.5 * (aMaxX + bMinX); + point(mx, 0.0); + point(mx, ty); + } else + ; // Just a straight line + } else { + double x0 = bMinX; + double x1 = aMaxX; + double my; + /* ______ + * | | + * | | + * /->| | + * | |______| + * | + * \-------------\ + * ______ | + * | | | + * | |-/ + * | | + * |______| + * + * If the elements are separated in Y-direction, + * route between the elements (this is always the shortest path). + */ + if (bMaxY < aMinY) + my = 0.5 * (aMinY + bMaxY); + else if (aMaxY < bMinY) + my = 0.5 * (aMaxY + bMinY); + else { + /* + * /------------------------\ + * | ______ ______ | + * | | | | | | + * | | | | |--+ + * +->| | | | | + * | |______| |______| | + * | | + * \------------------------/ + * + * or + * + * /-----------\ + * | ______ | + * | | | | + * | | | | + * /--+->| | | + * | ___|______| | + * | | | | + * | | |-+---/ + * | | | | + * | |______| | + * | | + * \----------/ + * + * We may choose either lower or upper path. + */ + double upperX0 = bMinX; + double upperX1 = aMaxX; + double lowerX0 = bMinX; + double lowerX1 = aMaxX; + double upperY = Math.min(aMinY, bMinY); + double lowerY = Math.max(aMaxY, bMaxY); + + if (aMinX < bMinX) { + if (ty < 0.5 * (aMinY + aMaxY)) + lowerX0 = aMinX; + else + upperX0 = aMinX; + } + + if (bMaxX > aMaxX) { + if (ty < 0.5 * (aMinY + aMaxY)) + upperX1 = bMaxX; + else + lowerX1 = bMaxX; + } + + double upperLength = upperX1 - upperY + (upperX1 - upperX0) + + (ty - upperY) + (tx - upperX0); + double lowerLength = lowerX1 + lowerY + (lowerX1 - lowerX0) + + (lowerY - ty) + (tx - lowerX0); + + if (upperLength < lowerLength) { + x0 = upperX0; + x1 = upperX1; + my = upperY; + } else { + x0 = lowerX0; + x1 = lowerX1; + my = lowerY; + } + } + point(x1, 0.0); + point(x1, my); + point(x0, my); + point(x0, ty); + } + } + + void routeWest() { + if (tx >= 0.0) { + double fx = Math.max(aMaxX, bMaxX); + double mx = 0.5 * (aMaxX + bMinX); + if (bMinY >= 0.0 || bMaxY <= 0.0 || mx < 0.0) { + /* ______ + * | | + * | | + * | |<-\ + * ______ |______| | + * | | | + * | |-------------------/ + * | | + * |______| + */ + point(fx, 0.0); + } + else { + /* /-------------\ + * | ______ | + * | | | | + * ______ | | | | + * | | | | |<-+ + * | |----+ |______| | + * | | | | + * |______| \-------------/ + * + * We may choose either upper or lower path + * by the path length. + */ + double my = Math.abs(bMinY) + Math.abs(ty - bMinY) < Math + .abs(bMaxY) + Math.abs(ty - bMaxY) ? bMinY : bMaxY; + point(mx, 0.0); + point(mx, my); + point(fx, my); + } + point(fx, ty); + } else { + double fx = Math.max(aMaxX, bMaxX); + double mx = 0.5 * (aMinX + bMaxX); + point(fx, 0.0); + if (ty <= aMinY || ty >= aMaxY + || (tx >= mx && ty >= aMinY && ty <= aMaxY)) { + /* ______ + * | | + * | | + * | |--\ + * ______ |______| | + * | | | + * | |<------------------/ + * | | + * |______| + */ + point(fx, ty); + } + else { + /* /-------------\ + * | ______ | + * | | | | + * ______ | | | | + * | | | | |--+ + * | |<---+ |______| | + * | | | | + * |______| \-------------/ + * + * We may choose either upper or lower path + * by the path length. + */ + double my = Math.abs(aMinY) + Math.abs(ty - aMinY) < Math + .abs(aMaxY) + Math.abs(ty - aMaxY) ? aMinY : aMaxY; + point(fx, my); + point(mx, my); + point(mx, ty); + } + } + } + + void routeSouth() { + if (tx > 0.0 && (bMinY >= 0.0 || (ty > 0.0 && bMinX <= aMaxX))) + point(tx, 0.0); + else if (bMinX > aMaxX) { + double mx = 0.5 * (aMaxX + bMinX); + point(mx, 0.0); + point(mx, bMinY); + point(tx, bMinY); + } else { + double fx = aMaxX; + double my = 0.5 * (aMaxY + bMinY); + if (my < aMaxY && (tx < aMinX || ty < aMinY)) { + my = Math.min(aMinY, bMinY); + if (bMaxX > aMaxX) + fx = bMaxX; + } + point(fx, 0.0); + point(fx, my); + point(tx, my); + } + } + + double xx, xy, yx, yy; + + void point(double x, double y) { + lineTo(x * xx + y * yx + sx, x * xy + y * yy + sy); + } + + /* + * should draw only horizontal or vertical lines. Determine the offset and + * draw both lines. + */ + void lineTo(double x, double y) { + double cx = path.getCurrentPoint().getX(); + double cy = path.getCurrentPoint().getY(); + + if (Math.abs(cx - x) < 1e-5) { + // Vertical line + if(points.size() % 2 == 0) { + points.add(points.get(points.size()-2)); + } + points.add(y); + } else if (Math.abs(cy - y) < 1e-5) { + // Horizontal line + if(points.size() % 2 != 0) { + points.add(cy); + } + points.add(x); + } + path.lineTo(x, y); + } + void rotate() { + double temp; + + temp = tx; + tx = ty; + ty = -temp; + + temp = aMinX; + aMinX = aMinY; + aMinY = -aMaxX; + aMaxX = aMaxY; + aMaxY = -temp; + + temp = bMinX; + bMinX = bMinY; + bMinY = -bMaxX; + bMaxX = bMaxY; + bMaxY = -temp; + + temp = xx; + xx = -xy; + xy = temp; + + temp = yx; + yx = -yy; + yy = temp; + + --targetDirection; + if (targetDirection < 0) + targetDirection += 4; + --sourceDirection; + } + + void flip() { + double temp; + + ty = -ty; + + temp = aMinY; + aMinY = -aMaxY; + aMaxY = -temp; + + temp = bMinY; + bMinY = -bMaxY; + bMaxY = -temp; + + yx = -yx; + yy = -yy; + + targetDirection = (targetDirection + 2) % 4; + } + + /* + * Puts source terminal to origo and rotates the situation so that the + * connection leaves to east. Finally, the case where target direction is to + * south is eliminated by optionally flipping the situation. + */ + void canonicalize() { + aMinX -= sx; + aMinY -= sy; + aMaxX -= sx; + aMaxY -= sy; + bMinX -= sx; + bMinY -= sy; + bMaxX -= sx; + bMaxY -= sy; + tx -= sx; + ty -= sy; + xx = yy = 1.0; + xy = yx = 0.0; + while (sourceDirection > 0) + rotate(); + + if (targetDirection == Constants.SOUTH) + flip(); + } + + public void route() { + /* + * Three cases: 1. Obstacles share X-axis at some point 2. Obstacles + * share Y-Axis at some point 3. Obstacles don't share axis => Have to + * make corners. + */ + if (aMinX < bMinX && aMaxX > bMinX || aMinX < bMaxX && aMaxX > bMaxX) { + // Obstacles share x-axis => no corner + double minX = aMinX > bMinX ? aMinX : bMinX; + double maxX = aMaxX < bMaxX ? aMaxX : bMaxX; + double middle = minX + (maxX - minX) / 2; + sx = middle; + tx = middle; + if (sy > ty) { + sy = aMinY; + ty = bMaxY; + } else { + sy = aMaxY; + ty = bMinY; + } + } else if (aMinY < bMinY && aMaxY > bMinY || aMinY < bMaxY + && aMaxY > bMaxY) { + // Obstacles share y-axis => no corner + double minY = aMinY > bMinY ? aMinY : bMinY; + double maxY = aMaxY < bMaxY ? aMaxY : bMaxY; + double middle = minY + (maxY - minY) / 2; + sy = middle; + ty = middle; + if (sx > tx) { + sx = aMinX; + tx = bMaxX; + } else { + sx = aMaxX; + tx = bMinX; + } + } else { + // Move starting point to the edge of the start element + switch (sourceDirection) { + case Constants.WEST: + sy = ty > aMaxY ? aMaxY : ty < aMinY ? aMinY : ty; + sx = aMinX; + break; + case Constants.EAST: + sy = ty > aMaxY ? aMaxY : ty < aMinY ? aMinY : ty; + sx = aMaxX; + break; + case Constants.NORTH: + sy = aMaxY; + sx = tx > aMaxX ? aMaxX : tx < aMinX ? aMinX : tx; + break; + case Constants.SOUTH: + sy = aMinY; + sx = tx > aMaxX ? aMaxX : tx < aMinX ? aMinX : tx; + break; + } + + // Move target point to the edge of the ending element + switch (targetDirection) { + case Constants.EAST: + ty = sy > bMaxY ? bMaxY : sy < bMinY ? bMinY : sy; + tx = bMaxX; + break; + case Constants.WEST: + ty = sy > bMaxY ? bMaxY : sy < bMinY ? bMinY : sy; + tx = bMinX; + break; + case Constants.NORTH: + ty = bMinY; + tx = sx > bMaxX ? bMaxX : sx < bMinX ? bMinX : sx; + break; + case Constants.SOUTH: + ty = bMaxY; + tx = sx > bMaxX ? bMaxX : sx < bMinX ? bMinX : sx; + break; + } + } + + path = new Path2D.Double(); + points = new ArrayList(); + + path.moveTo(sx, sy); + points.add(sx); + points.add(sy); + + // Vertical and horizontal cases + if ((Math.abs(sx - tx) < 1e-5 && isVertical()) + || (Math.abs(sy - ty) < 1e-5 && isHorizontal())) { + lineTo(tx, ty); + return; + } + + + canonicalize(); + switch (targetDirection) { + case Constants.EAST: + routeWest(); + break; + case Constants.WEST: + routeEast(); + break; + case Constants.NORTH: + routeSouth(); + break; + } + + point(tx, ty); + + } + + private boolean isVertical() { + return + (sourceDirection == Constants.SOUTH && targetDirection == Constants.SOUTH) + || + (sourceDirection == Constants.NORTH && targetDirection == Constants.NORTH); + } + + private boolean isHorizontal() { + return + (sourceDirection == Constants.EAST && targetDirection == Constants.EAST) + || + (sourceDirection == Constants.WEST && targetDirection == Constants.WEST); + } +} diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/Dependencies.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/Dependencies.java deleted file mode 100644 index bb0a4bd3..00000000 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/Dependencies.java +++ /dev/null @@ -1,244 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010 Association for Decentralized Information Management in - * Industry THTH ry. - * 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: - * VTT Technical Research Centre of Finland - initial API and implementation - *******************************************************************************/ -package org.simantics.sysdyn.ui.elements2.connections; - -import java.awt.geom.Arc2D; -import java.awt.geom.Path2D; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; - -import org.simantics.utils.datastructures.Pair; - -public class Dependencies { - - /* - * Total length of the arrow is ARROW_LENGTH1 + ARROW_LENGTH2 - */ - public static double ARROW_LENGTH1 = 0.2; - public static double ARROW_LENGTH2 = 1.0; - public static double ARROW_WIDTH = 0.5; - -// -// // Auxiliary -// double angle0; -// double angle1; -// double cx; -// double cy; -// double r; -// -// // Scene graph -// ShapeNode arcNode; -// FilledShapeNode arrowNode; -// -// public Dependencies() { -// } -// -// public Dependencies(Connectable tail, Connectable head) { -// super(); -// this.tail = tail; -// this.head = head; -// } -// -// @Override -// public void elementUpdated(IElement element) { -// update(); -// } -// -// @Override -// public void remove() { -// arcNode.remove(); -// arrowNode.remove(); -// tail.removeListener(this); -// head.removeListener(this); -// super.remove(); -// } -// -// @Override -// public void init(G2DParentNode parent) { -// tail.addListener(this); -// head.addListener(this); -// -// arcNode = parent.addNode(ShapeNode.class); -// arcNode.setScaleStroke(true); -// arcNode.setStroke(new BasicStroke(1)); -// arrowNode = parent.addNode(FilledShapeNode.class); -// update(); -// } -// - - private static Path2D createArrow(Path2D shape, double x, double y, double dx, double dy) { - if(shape == null) - shape = new Path2D.Double(); - else - shape.reset(); - - shape.moveTo(x+ARROW_LENGTH1*dx, y+ARROW_LENGTH1*dy); - x -= ARROW_LENGTH2*dx; - y -= ARROW_LENGTH2*dy; - shape.lineTo(x-ARROW_WIDTH*dy, y+ARROW_WIDTH*dx); - shape.lineTo(x+ARROW_WIDTH*dy, y-ARROW_WIDTH*dx); - shape.closePath(); - return shape; - } - - public static Arc2D createArc(Arc2D arc, Rectangle2D tail, Rectangle2D head, double angle) { - double x0 = tail.getCenterX(); - double y0 = tail.getCenterY(); - double x1 = head.getCenterX(); - double y1 = head.getCenterY(); - -// System.out.println("createArrowShape " + x0 + " " + y0 + " " + x1 + " " + y1); - - double offset = - Math.abs(angle) < 1.0e-6 - ? 1e3 * Math.signum(angle) - : Math.tan(Math.PI*0.5-angle)*0.5; - - double cx = 0.5*(x0+x1) + offset * (y1-y0); - double cy = 0.5*(y0+y1) + offset * (x0-x1); - double dx0 = x0 - cx; - double dy0 = y0 - cy; - double dx1 = x1 - cx; - double dy1 = y1 - cy; - - double r = Math.sqrt(dx0*dx0 + dy0*dy0); - -// Rectangle2D bounds = new Rectangle2D.Double(); -// tail.getBounds(bounds); - double angle0 = Arcs.nextIntersectingAngle(cx, cy, r, - Math.atan2(-dy0, dx0), tail, angle < 0.0); -// head.getBounds(bounds); - double angle1 = Arcs.nextIntersectingAngle(cx, cy, r, - Math.atan2(-dy1, dx1), head, angle > 0.0); - double extent = angle1-angle0; - //double arcAngle = angle0; - if(angle < 0.0) { - double temp = angle0; - angle0 = angle1; - angle1 = temp; - extent = -extent; - } - if(extent < 0) - extent += Math.PI*2.0; - else if(extent >= 360.0) - extent -= Math.PI*2.0; - if(arc == null) - arc = new Arc2D.Double(); - arc.setArc(cx-r, cy-r, 2*r, 2*r, - Math.toDegrees(angle0), - Math.toDegrees(extent), - Arc2D.OPEN); -// - return arc; - } - - public static Point2D computeCenter(Rectangle2D tail, Rectangle2D head, double angle) { - - double x0 = tail.getCenterX(); - double y0 = tail.getCenterY(); - double x1 = head.getCenterX(); - double y1 = head.getCenterY(); - -// System.out.println("createArrowShape " + x0 + " " + y0 + " " + x1 + " " + y1); - - double offset = - Math.abs(angle) < 1.0e-6 - ? 1e3 * Math.signum(angle) - : Math.tan(Math.PI*0.5-angle)*0.5; - - double cx = 0.5*(x0+x1) + offset * (y1-y0); - double cy = 0.5*(y0+y1) + offset * (x0-x1); - - return new Point2D.Double(cx, cy); - - } - - public static Pair createArrowShape(Pair shapes, Rectangle2D tail, Rectangle2D head, double angle) { - if(shapes == null || shapes.first == null || shapes.second == null) { - shapes = new Pair(new Arc2D.Double(), new Path2D.Double()); - } - - createArc(shapes.first, tail, head, angle); - - double angle0 = Math.toRadians(shapes.first.getAngleStart()); - double angle1 = Math.toRadians(shapes.first.getAngleStart() + shapes.first.getAngleExtent()); - double x = Math.cos(angle > 0.0 ? angle1 : angle0); - double y = -Math.sin(angle > 0.0 ? angle1 : angle0); - double r = shapes.first.getHeight() / 2; - - createArrow(shapes.second, shapes.first.getCenterX() + r*x, shapes.first.getCenterY() + r*y, - angle < 0.0 ? -y : y, - angle > 0.0 ? -x : x); - - return shapes; - - } -// -// public void update() { -// if(arcNode != null) -// updateSceneGraph(); -// fireElementUpdated(); -// } -// -// @Override -// public void getBounds(Rectangle2D bounds) { -// bounds.setFrame(arcNode.getBounds()); -// } -// -// @Override -// public boolean hitTest(double x, double y, double tolerance) { -// double dx = x-cx; -// double dy = y-cy; -// double dist = dx*dx + dy*dy; -// if(dist < (r+tolerance)*(r+tolerance) && -// dist > (r-tolerance)*(r-tolerance)) { -// double angle = Arcs.normalizeAngle(Math.atan2(-dy, dx)); -// if(Arcs.areClockwiseOrdered(angle0, angle, angle1)) -// return true; -// } -// return false; -// } -// -// class EventHandler extends DragEventHandler { -// @Override -// protected boolean begin(IDiagramEditor editor, DragEvent event) { -// return event.startModifiers.equals("left"); -// } -// -// @Override -// protected void update(IDiagramEditor editor, DragEvent event) { -// if(event == null) -// return; -// angle = Arcs.angleOfArc( -// tail.getOrigo().getX(), tail.getOrigo().getY(), -// event.current.getX(), event.current.getY(), -// head.getOrigo().getX(), head.getOrigo().getY() -// ); -// Dependencies.this.update(); -// editor.requestRepaint(); -// } -// } -// -// @SuppressWarnings("unchecked") -// @Override -// public T getInterface(Class clazz) { -// if(clazz == IEventHandler.class) -// return (T)new EventHandler(); -// return super.getInterface(clazz); -// } -// -// @Override -// public void elementRemoved(IElement element) { -// remove(); -// } - -} diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyConnectionFactory.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyConnectionFactory.java index 71eb30f6..9ef4fa9f 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyConnectionFactory.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyConnectionFactory.java @@ -29,8 +29,8 @@ import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.element.ElementClass; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.handler.impl.StaticObjectAdapter; -import org.simantics.g2d.routing.RouterFactory; import org.simantics.layer0.Layer0; +import org.simantics.sysdyn.ui.editor.routing.DependencyRouter; import org.simantics.utils.datastructures.Pair; /** @@ -65,7 +65,7 @@ public class DependencyConnectionFactory extends ElementFactoryAdapter { final AtomicInteger ready = new AtomicInteger(1); final ConcurrentSkipListMap> properties = new ConcurrentSkipListMap>(); - element.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(false, false)); + element.setHint(DiagramHints.ROUTE_ALGORITHM, new DependencyRouter()); graph.forEachPredicate(elementResource, new AsyncMultiProcedure() { diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyEdgeClass.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyEdgeClass.java index ab652a57..284a7bd4 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyEdgeClass.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyEdgeClass.java @@ -48,6 +48,7 @@ import org.simantics.g2d.elementclass.connection.EdgeClass.EdgeHandler; import org.simantics.g2d.elementclass.connection.EdgeClass.FixedTransform; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.utils.NodeUtil; +import org.simantics.sysdyn.ui.editor.routing.DependencyRouter; import org.simantics.utils.datastructures.Callback; import org.simantics.utils.datastructures.Pair; import org.simantics.utils.datastructures.hints.IHintContext.Key; @@ -72,7 +73,6 @@ public class DependencyEdgeClass { System.out.println("pickTest no node!"); return false; } - return Arcs.hitTest(node.getBeginBounds(), node.getEndBounds(), node.getAngle(), pickRect.getCenterX(), pickRect.getCenterY(), 3.0); } @@ -105,7 +105,9 @@ public class DependencyEdgeClass { @Override public void init(IElement e, G2DParentNode parent) { - ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, "edge_" + e.hashCode(), DependencyNode.class); + DependencyNode node = ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, "edge_" + e.hashCode(), DependencyNode.class); + node.setAngle(0.1); + update(e); } @@ -118,7 +120,6 @@ public class DependencyEdgeClass { DependencyNode node = e.getHint(KEY_SG_NODE); if(node == null) return; - final IDiagram diagram = ElementUtils.peekDiagram(e); node.setFieldListener(new PropertyChangeListener() { @@ -167,7 +168,7 @@ public class DependencyEdgeClass { node.setEndBounds(endTerminalShape.getBounds2D()); node.setStroke(stroke); node.setColor(c); - node.setAngle(0.1); + node.setShapes(DependencyRouter.createArrowShape(node.getShapes(), node.getBeginBounds(), node.getEndBounds(), node.getAngle())); Map> properties = e.getHint(DiagramHints.PROPERTIES); if(properties != null) { @@ -183,7 +184,7 @@ public class DependencyEdgeClass { path = new Path2D.Double(); else path.reset(); - path.append(node.getArc(), false); + path.append(node.getShapes().first, false); eh.setPath(e, path); } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyNode.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyNode.java index 1ccb00d2..04fc95bb 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyNode.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/DependencyNode.java @@ -28,6 +28,7 @@ import org.simantics.scenegraph.ISelectionPainterNode; import org.simantics.scenegraph.g2d.G2DNode; import org.simantics.scenegraph.g2d.events.ISGMouseEvent; import org.simantics.scenegraph.utils.NodeUtil; +import org.simantics.sysdyn.ui.editor.routing.DependencyRouter; import org.simantics.utils.datastructures.Pair; public class DependencyNode extends G2DNode implements ISelectionPainterNode, MouseListener, MouseMotionListener { @@ -100,6 +101,14 @@ public class DependencyNode extends G2DNode implements ISelectionPainterNode, Mo @SyncField("angle") public void setAngle(Double angle) { this.angle = angle.doubleValue(); + if(this.beginBounds != null && this.endBounds != null) + this.shapes = DependencyRouter.createArrowShape(this.shapes, this.beginBounds, this.endBounds, this.angle); + } + + @PropertySetter("shapes") + @SyncField("shapes") + public void setShapes(Pair shapes) { + this.shapes = shapes; } public Color getColor() { @@ -122,10 +131,12 @@ public class DependencyNode extends G2DNode implements ISelectionPainterNode, Mo return angle; } - public Arc2D getArc() { - return shapes.first; + public Pair getShapes() { + return shapes; } + + @Override public void render(Graphics2D g) { if(beginBounds == null || endBounds == null) return; @@ -133,8 +144,6 @@ public class DependencyNode extends G2DNode implements ISelectionPainterNode, Mo // Removed to let the global control handle rendering quality issues. //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - Dependencies.createArrowShape(shapes, beginBounds, endBounds, angle); - boolean selected = NodeUtil.isSelected(this, 2); if(selected) { g.setColor(Color.PINK); diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowConnectionFactory.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowConnectionFactory.java index 3040913f..c770d602 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowConnectionFactory.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowConnectionFactory.java @@ -23,7 +23,7 @@ import org.simantics.g2d.element.ElementClass; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.handler.impl.StaticObjectAdapter; import org.simantics.g2d.elementclass.connection.ConnectionClass; -import org.simantics.g2d.routing.RouterFactory; +import org.simantics.sysdyn.ui.editor.routing.FlowRouter; /** * An element class for single connection entity elements. A connection entity @@ -53,7 +53,7 @@ public class FlowConnectionFactory extends ElementFactoryAdapter { @Override public void load(AsyncReadGraph graph, ICanvasContext canvas, IDiagram diagram, Resource elementResource, final IElement element, final AsyncProcedure procedure) { - element.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, true)); + element.setHint(DiagramHints.ROUTE_ALGORITHM, new FlowRouter(false)); procedure.execute(graph, element); } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowEdgeClass.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowEdgeClass.java index be35e174..c1026fd0 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowEdgeClass.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowEdgeClass.java @@ -11,165 +11,401 @@ *******************************************************************************/ package org.simantics.sysdyn.ui.elements2.connections; -import java.awt.BasicStroke; import java.awt.Color; -import java.awt.Shape; import java.awt.Stroke; -import java.util.Map; +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; -import org.simantics.db.Resource; import org.simantics.g2d.diagram.DiagramHints; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.handler.Topology; import org.simantics.g2d.diagram.handler.Topology.Connection; +import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil; import org.simantics.g2d.element.ElementClass; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; -import org.simantics.g2d.element.SceneGraphNodeKey; +import org.simantics.g2d.element.handler.BendsHandler; +import org.simantics.g2d.element.handler.EdgeVisuals; +import org.simantics.g2d.element.handler.EdgeVisuals.ArrowType; import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd; -import org.simantics.g2d.element.handler.SceneGraph; -import org.simantics.g2d.element.handler.TerminalLayout; -import org.simantics.g2d.element.handler.Transform; import org.simantics.g2d.element.handler.impl.ConfigurableEdgeVisuals; import org.simantics.g2d.element.handler.impl.ConnectionSelectionOutline; import org.simantics.g2d.element.handler.impl.FillColorImpl; import org.simantics.g2d.element.handler.impl.ParentImpl; import org.simantics.g2d.element.handler.impl.ShapePick; import org.simantics.g2d.element.handler.impl.SimpleElementLayers; +import org.simantics.g2d.elementclass.BranchPoint; import org.simantics.g2d.elementclass.connection.EdgeClass.EdgeHandler; import org.simantics.g2d.elementclass.connection.EdgeClass.FixedTransform; +import org.simantics.g2d.elementclass.connection.EdgeSceneGraph; +import org.simantics.g2d.routing.ConnectionDirectionUtil; +import org.simantics.g2d.routing.Constants; +import org.simantics.g2d.routing.IRouter2; +import org.simantics.g2d.utils.PathUtils; import org.simantics.scenegraph.g2d.G2DParentNode; -import org.simantics.scenegraph.utils.NodeUtil; +import org.simantics.scenegraph.g2d.nodes.EdgeNode; +import org.simantics.sysdyn.ui.editor.participant.SysdynConnectTool.SysdynConnection; +import org.simantics.sysdyn.ui.editor.routing.FlowRouter; import org.simantics.sysdyn.ui.elements2.ValveFactory.ValveSceneGraph; -import org.simantics.utils.datastructures.Pair; -import org.simantics.utils.datastructures.hints.IHintContext.Key; -/** - * @author Toni Kalajainen - */ public class FlowEdgeClass { - // TODO scale, rotate, move, transform - public static final ElementClass CLASS = - ElementClass.compile( - SysdynEdgeSceneGraph.INSTANCE, - EdgeHandler.INSTANCE, - ConfigurableEdgeVisuals.DEFAULT, - FillColorImpl.BLACK, - FixedTransform.INSTANCE, - ShapePick.INSTANCE, - ConnectionSelectionOutline.INSTANCE, - SimpleElementLayers.INSTANCE, - ParentImpl.INSTANCE - ).setId("EdgeClass.STRAIGHT"); - - public static class SysdynEdgeSceneGraph implements SceneGraph { - - private static final long serialVersionUID = 2914383071126238996L; - - public static final SysdynEdgeSceneGraph INSTANCE = new SysdynEdgeSceneGraph(); - - public static final Stroke ARROW_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); - - public static final Key KEY_SG_NODE = new SceneGraphNodeKey(FlowNode.class, "EDGE_NODE"); - - @Override - public void init(IElement e, G2DParentNode parent) { - ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, "edge_" + e.hashCode(), FlowNode.class); - update(e); - } - - @Override - public void cleanup(IElement e) { - ElementUtils.removePossibleNode(e, KEY_SG_NODE); - } - - public void update(final IElement e) { - - FlowNode node = e.getHint(KEY_SG_NODE); - if(node == null) return; - - final IDiagram diagram = ElementUtils.peekDiagram(e); - - // Flownode does not change any property, hence listener is not needed -// node.setFieldListener(new PropertyChangeListener() { -// -// @Override -// public void propertyChange(final PropertyChangeEvent event) { -// -// DiagramUtils.mutateDiagram(diagram, new Callback() { -// -// @Override -// public void run(DiagramMutator mutator) { -// -// String field = event.getPropertyName(); -// Map> properties = e.getHint(DiagramHints.PROPERTIES); -// Pair property = properties.get(field); -// -// mutator.modifyProperty(e, property.first, event.getNewValue()); -// -// } -// -// }); -// -// } -// -// }); - - Stroke stroke = new BasicStroke(0.1f); - Color c = ElementUtils.getFillColor(e, Color.BLACK); - - Shape beginTerminalShape = null; - Shape endTerminalShape = null; - boolean toValve = false; - if (diagram != null) { - Topology topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class); - if (topology != null) { - Connection beginConnection = topology.getConnection(e, EdgeEnd.Begin); - Connection endConnection = topology.getConnection(e, EdgeEnd.End); - beginTerminalShape = getCanvasTerminalShape(beginConnection); - endTerminalShape = getCanvasTerminalShape(endConnection); - toValve = endConnection.node.getElementClass().containsClass(ValveSceneGraph.class); -// System.out.println("end connection class = " + endConnection.node.getElementClass()); - } - } - - if(beginTerminalShape == null || endTerminalShape == null) return; - - node.setBeginBounds(beginTerminalShape.getBounds2D()); - node.setEndBounds(endTerminalShape.getBounds2D()); - node.setStroke(stroke); - node.setColor(c); - node.setToValve(toValve); -// System.out.println("set toValve = " + toValve); - - Map> properties = e.getHint(DiagramHints.PROPERTIES); - if(properties != null) { - for(Map.Entry> entry : properties.entrySet()) { - NodeUtil.setPropertyIfSupported(entry.getKey(), entry.getValue().second, node); -// node.setProperty(entry.getKey(), entry.getValue().second); - } - } - - } - - private static Shape getCanvasTerminalShape(Connection connection) { - if (connection != null && connection.node != null && connection.terminal != null) { - TerminalLayout layout = connection.node.getElementClass().getAtMostOneItemOfClass(TerminalLayout.class); - if (layout != null) { - //return layout.getTerminalShape(connection.node, connection.terminal); - Shape shp = layout.getTerminalShape(connection.node, connection.terminal); - Transform tr = connection.node.getElementClass().getAtMostOneItemOfClass(Transform.class); - if (tr == null) - return shp; - - return tr.getTransform(connection.node).createTransformedShape(shp); - - } - } - return null; - } - - } + // TODO scale, rotate, move, transform + public static final ElementClass CLASS = ElementClass.compile( + FlowEdgeSceneGraph.INSTANCE, EdgeHandler.INSTANCE, + ConfigurableEdgeVisuals.DEFAULT, FillColorImpl.BLACK, + FixedTransform.INSTANCE, ShapePick.INSTANCE, + ConnectionSelectionOutline.INSTANCE, SimpleElementLayers.INSTANCE, + ParentImpl.INSTANCE).setId("FlowEdgeClass"); + + public static class FlowEdgeSceneGraph extends EdgeSceneGraph { + + private static final long serialVersionUID = -8737581995034992604L; + + public static final EdgeSceneGraph INSTANCE = new FlowEdgeSceneGraph(); + + @Override + public void init(IElement e, G2DParentNode parent) { + ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, + "edge_" + e.hashCode(), FlowEdgeNode.class); + final IDiagram diagram = ElementUtils.peekDiagram(e); + + boolean toValve = false; + if (diagram != null) { + Topology topology = diagram.getDiagramClass() + .getAtMostOneItemOfClass(Topology.class); + if (topology != null) { + Connection endConnection = topology.getConnection(e, + EdgeEnd.End); + toValve = endConnection.node.getElementClass() + .containsClass(ValveSceneGraph.class); + } + } + + ConfigurableEdgeVisuals cev = e.getElementClass() + .getAtMostOneItemOfClass(ConfigurableEdgeVisuals.class); + if (cev != null) { + if (toValve) + cev.setArrowType(e, EdgeEnd.End, ArrowType.None); + else { + cev.setArrowType(e, EdgeEnd.End, ArrowType.Fill); + cev.setArrowSize(e, EdgeEnd.End, 2); + } + } + + updateRoute(e); + update(e); + + } + + private static Path2D trimLineToArrows(Path2D line, + ArrowType endArrowType, double endArrowSize) { + Path2D result = new Path2D.Double(); + PathIterator pi = line.getPathIterator(null); + + double lineTo[] = new double[2]; + double prevLine[] = new double[2]; + double dummy[] = new double[2]; + + while (!pi.isDone()) { + int type = pi.currentSegment(lineTo); + pi.next(); + int nextType = pi.currentSegment(dummy); + + if (type == PathIterator.SEG_LINETO + && nextType == PathIterator.SEG_MOVETO) { + // this is the end of one line + if (endArrowType == ArrowType.Fill) { + double dx = (lineTo[0] - prevLine[0]); + double dy = (lineTo[1] - prevLine[1]); + double x2 = dx * dx; + double y2 = dy * dy; + double len = Math.sqrt(x2 + y2); + if (len > endArrowSize) { + double scale = endArrowSize / len; + lineTo[0] -= dx * scale; + lineTo[1] -= dy * scale; + } + } + } + + if (type == 0) { + result.moveTo(lineTo[0], lineTo[1]); + } else if (type == 1) { + result.lineTo(lineTo[0], lineTo[1]); + } else { + throw new UnsupportedOperationException( + "invalid path segment type: " + type); + } + prevLine[0] = lineTo[0]; + prevLine[1] = lineTo[1]; + + } + + result.setWindingRule(line.getWindingRule()); + return result; + } + + private void updateRoute(final IElement e) { + + final List segments = new ArrayList(); + segments.add(e); + + IRouter2 router = ElementUtils.getHintOrDefault(e, + DiagramHints.ROUTE_ALGORITHM, new FlowRouter()); + + router.route(new SysdynConnection() { + + IDiagram diagram = ElementUtils.peekDiagram(e); + + final Topology topology = diagram.getDiagramClass() + .getSingleItem(Topology.class); + + @Override + public Connector getBegin(Object seg) { + IElement e = (IElement) seg; + Connection begin = topology.getConnection(e, EdgeEnd.Begin); + return getConnector(begin); + } + + @Override + public Connector getEnd(Object seg) { + IElement e = (IElement) seg; + Connection begin = topology.getConnection(e, EdgeEnd.End); + return getConnector(begin); + } + + private Connector getConnector(Connection connection) { + Connector c = new Connector(); + + AffineTransform at = TerminalUtil.getTerminalPosOnDiagram( + connection.node, connection.terminal); + c.x = at.getTranslateX(); + c.y = at.getTranslateY(); + + if (connection.node.getElementClass().containsClass( + ValveSceneGraph.class)) { + Rectangle2D bounds = ElementUtils + .getElementBoundsOnDiagram(connection.node) + .getBounds2D(); + c.parentObstacle = new Rectangle2D.Double(bounds + .getCenterX() - FlowRouter.OFFSET, bounds + .getCenterY() - FlowRouter.OFFSET, + FlowRouter.OFFSET * 2, FlowRouter.OFFSET * 2); + } else { + c.parentObstacle = ElementUtils + .getElementBoundsOnDiagram(connection.node) + .getBounds2D(); + } + ConnectionDirectionUtil.determineAllowedDirections(c); + + return c; + } + + private int toAllowedDirections(BranchPoint.Direction direction) { + switch (direction) { + case Any: + return 0xf; + case Horizontal: + return Constants.EAST_FLAG | Constants.WEST_FLAG; + case Vertical: + return Constants.NORTH_FLAG | Constants.SOUTH_FLAG; + default: + throw new IllegalArgumentException( + "unrecognized direction: " + direction); + } + } + + @Override + public Collection getSegments() { + return segments; + } + + @Override + public void setPath(Object seg, Path2D path) { + IElement e = (IElement) seg; + BendsHandler bends = e.getElementClass() + .getAtMostOneItemOfClass(BendsHandler.class); + AffineTransform elementTransform = ElementUtils + .getInvTransform(e); + path = (Path2D) path.clone(); + path.transform(elementTransform); + bends.setPath(e, path); + } + }); + + } + + public void update(final IElement e) { + EdgeNode node = e.getHint(KEY_SG_NODE); + if (node == null) + return; + + EdgeVisuals vh = e.getElementClass().getSingleItem( + EdgeVisuals.class); + ArrowType at1 = vh.getArrowType(e, EdgeEnd.Begin); + ArrowType at2 = vh.getArrowType(e, EdgeEnd.End); + Stroke stroke = vh.getStroke(e); + // StrokeType strokeType = vh.getStrokeType(e); + double as1 = vh.getArrowSize(e, EdgeEnd.Begin); + double as2 = vh.getArrowSize(e, EdgeEnd.End); + + Color c = ElementUtils.getFillColor(e, Color.BLACK); + + // Get terminal shape for clipping the painted edge to its bounds. + IDiagram diagram = ElementUtils.peekDiagram(e); + if (diagram != null) { + Topology topology = diagram.getDiagramClass() + .getAtMostOneItemOfClass(Topology.class); + if (topology != null) { + Connection beginConnection = topology.getConnection(e, + EdgeEnd.Begin); + Connection endConnection = topology.getConnection(e, + EdgeEnd.End); + int beginBranchDegree = getBranchPointDegree( + beginConnection, topology); + int endBranchDegree = getBranchPointDegree(endConnection, + topology); + if (beginBranchDegree > 0 && beginBranchDegree < 3) { + at1 = ArrowType.None; + } + if (endBranchDegree > 0 && endBranchDegree < 3) { + at2 = ArrowType.None; + } + } + } + + // Read bends + BendsHandler bh = e.getElementClass().getSingleItem( + BendsHandler.class); + Path2D line = bh.getPath(e); + + boolean drawArrows = at1 != ArrowType.None || at2 != ArrowType.None; + // line = clipLineEnds(line, beginTerminalShape, endTerminalShape); + + Point2D first = new Point2D.Double(); + Point2D dir1 = new Point2D.Double(); + Point2D last = new Point2D.Double(); + Point2D dir2 = new Point2D.Double(); + PathIterator pi = line.getPathIterator(null); + drawArrows &= getPathArrows(pi, first, dir1, last, dir2); + + if (drawArrows) { + line = trimLineToArrows(line, at2, as2); + } + + EdgeNode.ArrowType pat1 = convert(at1); + EdgeNode.ArrowType pat2 = convert(at2); + + node.init(new GeneralPath(line), stroke, c, dir1, dir2, first, + last, as1, as2, pat1, pat2, null, null); + } + + private boolean getPathArrows(PathIterator pi, Point2D begin, + Point2D beginDirection, Point2D end, Point2D endDirection) { + + Iterator i = PathUtils.toLineIterator(pi); + + double first1[] = null, last1[] = null; + double first2[] = null, last2[] = null; + + // double current[] = new double[2]; + + double[] previous = null; + + while (i.hasNext()) { + double[] current = i.next(); + + // Start of the first path + if (first1 == null) { + first1 = current; + last1 = current; + } + + // Command was moveTo => start of the second path + else if (previous != null + && (previous[2] != current[0] || previous[3] != current[1])) { + first2 = current; + last2 = current; + } + + // first2 == null => still in the first line + else if (first2 == null) { + last1 = current; + } + + // second path + else if (!i.hasNext()) { + last2 = current; + } + + previous = current; + } + + if (first1 == null || last1 == null || first2 == null + || last2 == null) + return false; + + double[] first = { mean(first1[0], first2[0]), + mean(first1[1], first2[1]), mean(first1[2], first2[2]), + mean(first1[3], first2[3]) }; + double[] last = { mean(last1[0], last2[0]), + mean(last1[1], last2[1]), mean(last1[2], last2[2]), + mean(last1[3], last2[3]) }; + + begin.setLocation(PathUtils.getLinePos(first, 0)); + beginDirection.setLocation(PathUtils.getLineTangent(first, 0)); + end.setLocation(PathUtils.getLinePos(last, 1)); + Point2D endTangent = PathUtils.getLineTangent(last, 1); + endDirection.setLocation(-endTangent.getX(), -endTangent.getY()); + + return true; + } + + private double mean(double c1, double c2) { + return c1 + (c2 - c1) / 2; + } + + private static EdgeNode.ArrowType convert(ArrowType at) { + switch (at) { + case None: + return EdgeNode.ArrowType.None; + case Stroke: + return EdgeNode.ArrowType.Stroke; + case Fill: + return EdgeNode.ArrowType.Fill; + default: + throw new IllegalArgumentException("unsupported arrow type: " + + at); + } + } + + private final Collection connectionsTemp = new ArrayList(); + + private int getBranchPointDegree(Connection connection, + Topology topology) { + if (connection != null && connection.node != null) { + if (connection.node.getElementClass().containsClass( + BranchPoint.class)) { + connectionsTemp.clear(); + topology.getConnections(connection.node, + connection.terminal, connectionsTemp); + int degree = connectionsTemp.size(); + connectionsTemp.clear(); + return degree; + } + } + return -1; + } + + } } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowEdgeNode.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowEdgeNode.java new file mode 100644 index 00000000..e6640f17 --- /dev/null +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowEdgeNode.java @@ -0,0 +1,78 @@ +package org.simantics.sysdyn.ui.elements2.connections; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; + +import org.simantics.scenegraph.ISelectionPainterNode; +import org.simantics.scenegraph.g2d.nodes.EdgeNode; +import org.simantics.scenegraph.utils.NodeUtil; + +public class FlowEdgeNode extends EdgeNode implements ISelectionPainterNode { + + private static final long serialVersionUID = -6774653631527343539L; + + private static final BasicStroke SELECTION_STROKE = new BasicStroke(1.0f); + + @Override + public void render(Graphics2D g) { + if(color != null) g.setColor(color); + if(stroke == null || shape == null) return; + + if(alphaComposite != null) { + g.setComposite(alphaComposite); + } + + Stroke effectiveStroke = stroke; + if(dynamicStroke != null) { + effectiveStroke = dynamicStroke; + } + + Color effectiveColor = color; + if(dynamicColor != null) { + effectiveColor = dynamicColor; + } + + g.setStroke(effectiveStroke); + + // Draw line + boolean selected = NodeUtil.isSelected(this, 2); + if(selected) { + g.setColor(Color.PINK); + g.setStroke(SELECTION_STROKE); + g.draw(shape); + } + g.setColor(effectiveColor); + g.setStroke(effectiveStroke); + g.draw(shape); + + // Draw the ending arrow if necessary + if(last_at == ArrowType.Fill) { + AffineTransform at = g.getTransform(); + + g.setStroke(ARROW_STROKE); + + double theta = Math.atan2(lastdir.getY(), lastdir.getX()) - Math.PI/2; + g.translate(last.getX(), last.getY()); + g.rotate(theta); + g.scale(lastsize, lastsize); + g.fill(FLOW_ARROW); + g.setTransform(at); + } + + } + + public transient final static GeneralPath FLOW_ARROW; + + static { + FLOW_ARROW = new GeneralPath(); + FLOW_ARROW.moveTo(-1f, 1.6f); + FLOW_ARROW.lineTo( 0f, 0f); + FLOW_ARROW.lineTo( 1f, 1.6f); + FLOW_ARROW.closePath(); + } + +} diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowNode.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowNode.java index 3c5a08f4..25cec640 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowNode.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/elements2/connections/FlowNode.java @@ -32,9 +32,8 @@ public class FlowNode extends G2DNode implements ISelectionPainterNode { private Stroke stroke; private Rectangle2D beginBounds; private Rectangle2D endBounds; - private Boolean toValve; - private transient Path2D lines; - private transient Path2D arrow; + private Path2D lines; + private Path2D arrow; @PropertySetter("color") @SyncField("color") @@ -59,11 +58,17 @@ public class FlowNode extends G2DNode implements ISelectionPainterNode { public void setEndBounds(Rectangle2D endBounds) { this.endBounds = endBounds; } - - @PropertySetter("toValve") - @SyncField("toValve") - public void setToValve(Boolean toValve) { - this.toValve = toValve; + + @PropertySetter("lines") + @SyncField("lines") + public void setLines(Path2D lines) { + this.lines = lines; + } + + @PropertySetter("arrow") + @SyncField("arrow") + public void setArrow(Path2D arrow) { + this.arrow = arrow; } public Color getColor() { @@ -82,10 +87,14 @@ public class FlowNode extends G2DNode implements ISelectionPainterNode { return endBounds; } - public Boolean getBoolean() { - return toValve; + public Path2D getLines() { + return lines; } - + + public Path2D getArrow() { + return arrow; + } + @Override public void render(Graphics2D g) { // Removed to let the global control handle rendering quality issues. @@ -97,15 +106,6 @@ public class FlowNode extends G2DNode implements ISelectionPainterNode { * -In the second case there is an arrow and valve is beginBounds */ -// System.out.println("FlowNode.render toValve = " + toValve); - - if(toValve) { - lines = Flows.createLines(lines, false, endBounds, beginBounds); - } else { - lines = Flows.createLines(lines, true, beginBounds, endBounds); - arrow = Flows.createArrow(arrow, beginBounds, endBounds); - } - boolean selected = NodeUtil.isSelected(this, 2); if(selected) { g.setColor(Color.PINK); @@ -113,12 +113,12 @@ public class FlowNode extends G2DNode implements ISelectionPainterNode { g.draw(lines); if(color != null) g.setColor(color); g.setStroke(stroke); - g.draw(lines); + if(lines!= null) g.draw(lines); if(arrow != null) g.fill(arrow); } else { if(color != null) g.setColor(color); if(stroke != null) g.setStroke(stroke); - g.draw(lines); + if(lines!= null) g.draw(lines); if(arrow != null) g.fill(arrow); } -- 2.47.1