]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/connection/EdgeSceneGraph.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / elementclass / connection / EdgeSceneGraph.java
diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/connection/EdgeSceneGraph.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/connection/EdgeSceneGraph.java
new file mode 100644 (file)
index 0000000..b12ad68
--- /dev/null
@@ -0,0 +1,394 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.g2d.elementclass.connection;\r
+\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Shape;\r
+import java.awt.Stroke;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.GeneralPath;\r
+import java.awt.geom.Line2D;\r
+import java.awt.geom.Path2D;\r
+import java.awt.geom.PathIterator;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Iterator;\r
+\r
+import org.simantics.g2d.diagram.IDiagram;\r
+import org.simantics.g2d.diagram.handler.Topology;\r
+import org.simantics.g2d.diagram.handler.Topology.Connection;\r
+import org.simantics.g2d.element.ElementUtils;\r
+import org.simantics.g2d.element.IElement;\r
+import org.simantics.g2d.element.SceneGraphNodeKey;\r
+import org.simantics.g2d.element.handler.BendsHandler;\r
+import org.simantics.g2d.element.handler.EdgeVisuals;\r
+import org.simantics.g2d.element.handler.EdgeVisuals.ArrowType;\r
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
+import org.simantics.g2d.element.handler.Rotate;\r
+import org.simantics.g2d.element.handler.SceneGraph;\r
+import org.simantics.g2d.element.handler.TerminalLayout;\r
+import org.simantics.g2d.elementclass.BranchPoint;\r
+import org.simantics.g2d.utils.PathUtils;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.nodes.EdgeNode;\r
+import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
+\r
+/**\r
+ * Generic edge painter\r
+ *\r
+ * @author J-P Laine\r
+ */\r
+public class EdgeSceneGraph implements SceneGraph {\r
+\r
+    private static final long serialVersionUID = 2914383071126238996L;\r
+\r
+    public static final EdgeSceneGraph INSTANCE = new EdgeSceneGraph();\r
+\r
+    public static final Stroke ARROW_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);\r
+\r
+    public static final Key KEY_SG_NODE = new SceneGraphNodeKey(EdgeNode.class, "EDGE_SG_NODE");\r
+\r
+    @Override\r
+    public void init(IElement e, G2DParentNode parent) {\r
+        ElementUtils.getOrCreateNode(e, parent, KEY_SG_NODE, "edge_" + e.hashCode(), EdgeNode.class);\r
+        update(e);\r
+    }\r
+\r
+    @Override\r
+    public void cleanup(IElement e) {\r
+        ElementUtils.removePossibleNode(e, KEY_SG_NODE);\r
+    }\r
+\r
+    public void update(final IElement e) {\r
+        EdgeNode node = e.getHint(KEY_SG_NODE);\r
+        if(node == null) return;\r
+\r
+        EdgeVisuals vh = e.getElementClass().getSingleItem(EdgeVisuals.class);\r
+        ArrowType at1 = vh.getArrowType(e, EdgeEnd.Begin);\r
+        ArrowType at2 = vh.getArrowType(e, EdgeEnd.End);\r
+        Stroke stroke = vh.getStroke(e);\r
+        //StrokeType strokeType = vh.getStrokeType(e);\r
+        double as1 = vh.getArrowSize(e, EdgeEnd.Begin);\r
+        double as2 = vh.getArrowSize(e, EdgeEnd.End);\r
+\r
+        Color c = ElementUtils.getFillColor(e, Color.BLACK);\r
+\r
+        // Get terminal shape for clipping the painted edge to its bounds.\r
+        IDiagram diagram = ElementUtils.peekDiagram(e);\r
+        Shape beginTerminalShape = null;\r
+        Shape endTerminalShape = null;\r
+        if (diagram != null) {\r
+            Topology topology = diagram.getDiagramClass().getAtMostOneItemOfClass(Topology.class);\r
+            if (topology != null) {\r
+                Connection beginConnection = topology.getConnection(e, EdgeEnd.Begin);\r
+                Connection endConnection = topology.getConnection(e, EdgeEnd.End);\r
+                beginTerminalShape = getTerminalShape(beginConnection);\r
+                endTerminalShape = getTerminalShape(endConnection);\r
+                int beginBranchDegree = getBranchPointDegree(beginConnection, topology);\r
+                int endBranchDegree = getBranchPointDegree(endConnection, topology);\r
+                if (beginBranchDegree > 0 && beginBranchDegree < 3) {\r
+                    at1 = ArrowType.None;\r
+                }\r
+                if (endBranchDegree > 0 && endBranchDegree < 3) {\r
+                    at2 = ArrowType.None;\r
+                }\r
+            }\r
+        }\r
+\r
+        // Read bends\r
+        BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);\r
+        Path2D line = bh.getPath(e);\r
+\r
+        boolean drawArrows = at1 != ArrowType.None || at2 != ArrowType.None;\r
+        //line = clipLineEnds(line, beginTerminalShape, endTerminalShape);\r
+\r
+        Point2D first       = new Point2D.Double();\r
+        Point2D dir1        = new Point2D.Double();\r
+        Point2D last        = new Point2D.Double();\r
+        Point2D dir2        = new Point2D.Double();\r
+        PathIterator pi     = line.getPathIterator(null);\r
+        drawArrows &= PathUtils.getPathArrows(pi, first, dir1, last, dir2);\r
+\r
+        if (drawArrows) {\r
+            line = trimLineToArrows(line, at1, as1, at2, as2);\r
+        }\r
+\r
+        EdgeNode.ArrowType pat1 = convert(at1);\r
+        EdgeNode.ArrowType pat2 = convert(at2);\r
+\r
+        node.init(new GeneralPath(line), stroke, c, dir1, dir2, first, last, as1, as2, pat1, pat2, null, null);\r
+    }\r
+\r
+    private static EdgeNode.ArrowType convert(ArrowType at) {\r
+        switch (at) {\r
+            case None: return EdgeNode.ArrowType.None;\r
+            case Stroke: return EdgeNode.ArrowType.Stroke;\r
+            case Fill: return EdgeNode.ArrowType.Fill;\r
+            default:\r
+                throw new IllegalArgumentException("unsupported arrow type: " + at);\r
+        }\r
+    }\r
+\r
+    private static final Rectangle2D EMPTY = new Rectangle2D.Double();\r
+\r
+    private static Shape getTerminalShape(Connection connection) {\r
+        if (connection != null && connection.node != null && connection.terminal != null) {\r
+            TerminalLayout layout = connection.node.getElementClass().getAtMostOneItemOfClass(TerminalLayout.class);\r
+            if (layout != null) {\r
+                //return layout.getTerminalShape(connection.node, connection.terminal);\r
+                Shape shp = layout.getTerminalShape(connection.node, connection.terminal);\r
+                Rotate rotate = connection.node.getElementClass().getAtMostOneItemOfClass(Rotate.class);\r
+                if (rotate == null)\r
+                    return shp;\r
+\r
+                double theta = rotate.getAngle(connection.node);\r
+                return AffineTransform.getRotateInstance(theta).createTransformedShape(shp);\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+    private final Collection<Connection> connectionsTemp = new ArrayList<Connection>();\r
+    private int getBranchPointDegree(Connection connection, Topology topology) {\r
+        if (connection != null && connection.node != null) {\r
+            if (connection.node.getElementClass().containsClass(BranchPoint.class)) {\r
+                connectionsTemp.clear();\r
+                topology.getConnections(connection.node, connection.terminal, connectionsTemp);\r
+                int degree = connectionsTemp.size();\r
+                connectionsTemp.clear();\r
+                return degree;\r
+            }\r
+        }\r
+        return -1;\r
+    }\r
+\r
+    private static Path2D clipLineEnds(Path2D line, Shape beginTerminalShape, Shape endTerminalShape) {\r
+        if (beginTerminalShape == null && endTerminalShape == null)\r
+            return line;\r
+\r
+        Rectangle2D bb = beginTerminalShape != null ? beginTerminalShape.getBounds2D() : EMPTY;\r
+        Rectangle2D eb = endTerminalShape != null ? endTerminalShape.getBounds2D() : EMPTY;\r
+        // If the terminal shape doesn't contain its own coordinate system\r
+        // origin, just ignore the terminal shape.\r
+        if (bb != EMPTY && !bb.contains(0, 0))\r
+            bb = EMPTY;\r
+        if (eb != EMPTY && !eb.contains(0, 0))\r
+            eb = EMPTY;\r
+        if (bb.isEmpty() && eb.isEmpty())\r
+            return line;\r
+\r
+        Path2D result = new Path2D.Double();\r
+\r
+        PathIterator pi = line.getPathIterator(null);\r
+        Iterator<double[]> it = PathUtils.toLineIterator(pi);\r
+        boolean first = true;\r
+        while (it.hasNext()) {\r
+            double[] seg = it.next();\r
+            int degree = PathUtils.getLineDegree(seg);\r
+            //System.out.println("SEG: " + Arrays.toString(seg));\r
+\r
+            if (first) {\r
+                first = false;\r
+                Point2D start = PathUtils.getLinePos(seg, 0);\r
+                Point2D sp = clipToRectangle(bb, PathUtils.getLinePos(seg, 1), start);\r
+                if (sp != null) {\r
+                    result.moveTo(sp.getX(), sp.getY());\r
+                } else {\r
+                    result.moveTo(start.getX(), start.getY());\r
+                }\r
+            }\r
+            if (!it.hasNext()) {\r
+                // this is the last segment\r
+                Point2D ep = clipToRectangle(eb, PathUtils.getLinePos(seg, 0), PathUtils.getLinePos(seg, 1));\r
+                //System.out.println("EP: " + ep + ", " + PathUtils.getLinePos(seg, 0) + " -> " + PathUtils.getLinePos(seg, 1));\r
+                if (ep != null) {\r
+                    seg[degree * 2] = ep.getX();\r
+                    seg[degree * 2 + 1] = ep.getY();\r
+                }\r
+            }\r
+\r
+            if (degree == 1) {\r
+                result.lineTo(seg[2], seg[3]);\r
+            } else if (degree == 2) {\r
+                result.quadTo(seg[2], seg[3], seg[4], seg[5]);\r
+            } else if (degree == 3) {\r
+                result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]);\r
+            } else {\r
+                throw new UnsupportedOperationException("invalid path segment degree: " + degree);\r
+            }\r
+        }\r
+\r
+        result.setWindingRule(line.getWindingRule());\r
+        return result;\r
+    }\r
+\r
+    private static Path2D trimLineToArrows(Path2D line, ArrowType beginArrowType, double beginArrowSize, ArrowType endArrowType, double endArrowSize) {\r
+        Path2D result = new Path2D.Double();\r
+        PathIterator pi = line.getPathIterator(null);\r
+        Iterator<double[]> it = PathUtils.toLineIterator(pi);\r
+        boolean first = true;\r
+\r
+        while (it.hasNext()) {\r
+            double[] seg = it.next();\r
+            int degree = PathUtils.getLineDegree(seg);\r
+\r
+            if (first) {\r
+                first = false;\r
+\r
+                if (beginArrowType == ArrowType.Fill) {\r
+                    Point2D t = PathUtils.getLineTangent(seg, 0);\r
+                    double len = Math.sqrt(lensq(t, null));\r
+                    if (len > beginArrowSize) {\r
+                        double scale = beginArrowSize / len;\r
+                        seg[0] += t.getX() * scale;\r
+                        seg[1] += t.getY() * scale;\r
+                    } else {\r
+                        // Remove the first segment completely if the segment is too\r
+                        // small to be noticed from under the arrow.\r
+                        result.moveTo(seg[degree * 2], seg[degree * 2 + 1]);\r
+                        continue;\r
+                    }\r
+                }\r
+\r
+                result.moveTo(seg[0], seg[1]);\r
+            }\r
+            if (!it.hasNext()) {\r
+                // this is the last segment\r
+                if (endArrowType == ArrowType.Fill) {\r
+                    Point2D t = PathUtils.getLineTangent(seg, 1);\r
+                    double len = Math.sqrt(lensq(t, null));\r
+                    if (len > endArrowSize) {\r
+                        double scale = endArrowSize / len;\r
+                        seg[degree * 2] -= t.getX() * scale;\r
+                        seg[degree * 2 + 1] -= t.getY() * scale;\r
+                    }\r
+                }\r
+            }\r
+\r
+            if (degree == 1) {\r
+                result.lineTo(seg[2], seg[3]);\r
+            } else if (degree == 2) {\r
+                result.quadTo(seg[2], seg[3], seg[4], seg[5]);\r
+            } else if (degree == 3) {\r
+                result.curveTo(seg[2], seg[3], seg[4], seg[5], seg[6], seg[7]);\r
+            } else {\r
+                throw new UnsupportedOperationException("invalid path segment degree: " + degree);\r
+            }\r
+        }\r
+\r
+        result.setWindingRule(line.getWindingRule());\r
+        return result;\r
+    }\r
+\r
+    private static Point2D clipToRectangle(Rectangle2D bounds, Point2D p1, Point2D p2) {\r
+        if (bounds.isEmpty())\r
+            return p2;\r
+\r
+        Line2D line = new Line2D.Double(p1, p2);\r
+        Point2D vi1 = intersectWithHorizontalLine(line, bounds.getMinY() + p2.getY());\r
+        Point2D vi2 = intersectWithHorizontalLine(line, bounds.getMaxY() + p2.getY());\r
+        Point2D hi1 = intersectWithVerticalLine(line, bounds.getMinX() + p2.getX());\r
+        Point2D hi2 = intersectWithVerticalLine(line, bounds.getMaxX() + p2.getX());\r
+\r
+        int i = 0;\r
+        Point2D[] intersections = { null, null, null, null };\r
+        if (vi1 != null)\r
+            intersections[i++] = vi1;\r
+        if (vi2 != null)\r
+            intersections[i++] = vi2;\r
+        if (hi1 != null)\r
+            intersections[i++] = hi1;\r
+        if (hi2 != null)\r
+            intersections[i++] = hi2;\r
+\r
+        //System.out.println(bounds + ": P1(" + p1 + ") - P2(" + p2 +"): " + Arrays.toString(intersections));\r
+\r
+        if (i == 0)\r
+            return p2;\r
+        if (i == 1)\r
+            return intersections[0];\r
+\r
+        // Find the intersection i for which applies\r
+        // lensq(p1, p2) >= lensq(p1, i) &\r
+        // for all intersections j != i: lensq(p1, i) > lensq(p1, j)\r
+        double len = lensq(p1, p2);\r
+        //System.out.println("original line lensq: " + len);\r
+        Point2D nearestIntersection = null;\r
+        double nearestLen = -1;\r
+        for (int j = 0; j < i; ++j) {\r
+            double l = lensq(p1, intersections[j]);\r
+            //System.out.println("intersected line lensq: " + l);\r
+            if (l <= len && l > nearestLen) {\r
+                nearestIntersection = intersections[j];\r
+                nearestLen = l;\r
+                //System.out.println("nearest");\r
+            }\r
+        }\r
+        return nearestIntersection;\r
+    }\r
+\r
+    private static double lensq(Point2D p1, Point2D p2) {\r
+        double dx = p1.getX();\r
+        double dy = p1.getY();\r
+        if (p2 != null) {\r
+            dx = p2.getX() - dx;\r
+            dy = p2.getY() - dy;\r
+        }\r
+        return dx*dx + dy*dy;\r
+    }\r
+\r
+    private static Point2D intersectWithHorizontalLine(Line2D l, double y) {\r
+        double dx = l.getX2() - l.getX1();\r
+        double dy = l.getY2() - l.getY1();\r
+\r
+        if (Math.abs(dy) < 1e-5) {\r
+            // input line as horizontal, no intersection.\r
+            return null;\r
+        }\r
+        double a = dx / dy;\r
+        return new Point2D.Double((y - l.getY1()) * a + l.getX1(), y);\r
+    }\r
+\r
+    private static Point2D intersectWithVerticalLine(Line2D l, double x) {\r
+        double dx = l.getX2() - l.getX1();\r
+        double dy = l.getY2() - l.getY1();\r
+\r
+        if (Math.abs(dx) < 1e-5) {\r
+            // input line as vertical, no intersection.\r
+            return null;\r
+        }\r
+        double a = dy / dx;\r
+        return new Point2D.Double(x, a * (x - l.getX1()) + l.getY1());\r
+    }\r
+\r
+\r
+    public final static Path2D NORMAL_ARROW;\r
+    public final static Path2D FILLED_ARROW;\r
+\r
+    static {\r
+        FILLED_ARROW = new Path2D.Double();\r
+        FILLED_ARROW.moveTo(-0.5, 1);\r
+        FILLED_ARROW.lineTo(   0, 0);\r
+        FILLED_ARROW.lineTo( 0.5, 1);\r
+        FILLED_ARROW.closePath();\r
+\r
+        NORMAL_ARROW = new Path2D.Double();\r
+        NORMAL_ARROW.moveTo(-0.5, 1);\r
+        NORMAL_ARROW.lineTo(   0, 0);\r
+        NORMAL_ARROW.lineTo( 0.5, 1);\r
+    }\r
+\r
+}\r