]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/ConnectionCrossings.java
Configurable connection crossing styles
[simantics/platform.git] / bundles / org.simantics.diagram.connection / src / org / simantics / diagram / connection / rendering / ConnectionCrossings.java
diff --git a/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/ConnectionCrossings.java b/bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/ConnectionCrossings.java
new file mode 100644 (file)
index 0000000..3ccb492
--- /dev/null
@@ -0,0 +1,214 @@
+/*******************************************************************************
+ * Copyright (c) 2020 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:
+ *     Semantum Oy - initial API and implementation
+ *******************************************************************************/
+package org.simantics.diagram.connection.rendering;
+
+import java.awt.geom.Arc2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ConnectionCrossings implements PathModifier {
+    public enum Type {
+        NONE,
+        GAP,
+        ARC,
+        SQUARE
+    }
+
+    private List<Segment> segments = new ArrayList<>();
+    private double width;
+    private Type type;
+
+    public void setWidth(double gapWidth) {
+        this.width = gapWidth;
+    }
+
+    public double getWidth() {
+        return width;
+    }
+
+    public void setType(Type type) {
+        this.type = type;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    static class Segment {
+        public double x1, y1, x2, y2;
+
+        public Segment(double x1, double y1, double x2, double y2) {
+            this.x1 = x1;
+            this.y1 = y1;
+            this.x2 = x2;
+            this.y2 = y2;
+        }
+    };
+
+    public void reset() {
+        segments.clear();
+    }
+
+    static Double lineLineIntersection(Segment l1, Segment l2) { 
+        double epsilon = 0.001;
+
+        double d = (l1.x1 - l1.x2) * (l2.y1 - l2.y2) - (l1.y1 - l1.y2) * (l2.x1 - l2.x2);
+        if (d == 0.0) return null;
+        double s = ((l1.x1 - l2.x1) * (l2.y1 - l2.y2) - (l1.y1 - l2.y1) * (l2.x1 - l2.x2)) / d;
+        if ((s > epsilon) && (s < 1 - epsilon)) {
+            double t = -((l1.x1 - l1.x2) * (l1.y1 - l2.y1) - (l1.y1 - l1.y2) * (l1.x1 - l2.x1)) / d;
+            if ((t > epsilon) && (t < 1 - epsilon)) {
+                return t;
+            }
+        }
+        return null; 
+    }
+
+    public Path2D modify(Path2D path) {
+        Path2D.Double path2 = new Path2D.Double();
+        PathIterator iter = path.getPathIterator(null);
+
+        while (!iter.isDone()) {
+
+            double c[] = new double[6];
+            int i = iter.currentSegment(c);
+            switch (i) {
+            case PathIterator.SEG_MOVETO:
+                path2.moveTo(c[0], c[1]);
+                break;
+            case PathIterator.SEG_LINETO:
+                Segment l = new Segment(path2.getCurrentPoint().getX(), path2.getCurrentPoint().getY(), c[0], c[1]);
+                
+                List<Double> gaps = new ArrayList<>();
+                for (Segment old : segments) {
+                    Double t = lineLineIntersection(old, l);
+                    if (t != null) {
+                        gaps.add(t);
+                    }
+                }
+
+                if (gaps.isEmpty()) {
+                    path2.lineTo(c[0], c[1]);
+                } else {
+                    Collections.sort(gaps);
+                    double dx = l.x2 - l.x1;
+                    double dy = l.y2 - l.y1;
+
+                    double pos = 0.0;
+                    double len = Math.sqrt(dx*dx + dy*dy);
+
+                    boolean finish = true;
+                    Point2D prevGapEnd = null;
+                    for (Double gapCenter : gaps) {
+                        double pos2 = gapCenter - width / 2 / len;
+                        double pos3 = gapCenter + width / 2 / len;
+                        if (pos2 > pos) {
+                            handleGap(path2, prevGapEnd);
+                            prevGapEnd = null;
+                            path2.lineTo(l.x1 + pos2 * dx, l.y1 + pos2 * dy);
+                        }
+                        if (pos3 < 1.0) {
+                            double x = l.x1 + pos3 * dx;
+                            double y = l.y1 + pos3 * dy;
+                            prevGapEnd = new Point2D.Double(x, y);
+                        } else {
+                            finish = false;
+                        }
+                        pos = pos3;
+                    }
+                    
+                    if (finish) {
+                        handleGap(path2, prevGapEnd);
+                        path2.lineTo(l.x2, l.y2);
+                    } else {
+                        prevGapEnd = new Point2D.Double(l.x2, l.y2);
+                        handleGap(path2, prevGapEnd);
+                    }
+                }
+                segments.add(l);
+
+                break;
+            case PathIterator.SEG_QUADTO:
+                // TODO: implement gaps
+                path2.quadTo(c[0], c[1], c[2], c[3]);
+                break;
+            case PathIterator.SEG_CUBICTO:
+                // TODO: implement gaps
+                path2.curveTo(c[0], c[1], c[2], c[3], c[4], c[5]);
+                break;
+            case PathIterator.SEG_CLOSE:
+                // TODO: implement gaps
+                path2.closePath();
+                break;
+            default:
+                throw new RuntimeException("Unexpected segment type " + i);
+            }
+            iter.next();
+        }
+        return path2;
+    }
+
+    private void handleGap(Path2D path, Point2D prevGapEnd) {
+        if (prevGapEnd != null) {
+            switch (type) {
+            case ARC:
+                arcTo(path, prevGapEnd.getX(), prevGapEnd.getY());
+                break;
+            case SQUARE:
+                squareTo(path, prevGapEnd.getX(), prevGapEnd.getY(), width);
+                break;
+            case GAP:
+                path.moveTo(prevGapEnd.getX(), prevGapEnd.getY());
+                break;
+            case NONE:
+                break;
+            }
+        }
+    }
+
+    private static void arcTo(Path2D path, double x2, double y2) {
+        Arc2D arc = new Arc2D.Double();
+        double x1 = path.getCurrentPoint().getX();
+        double y1 = path.getCurrentPoint().getY();
+        double dx = x2 - x1;
+        double dy = y2 - y1;
+        double r = Math.sqrt(dx * dx + dy * dy) / 2;
+        double angle = Math.atan2(dx,  dy) * 180 / Math.PI + 90;
+        double span = (angle > 225 || angle < 45) ? 180 : -180;
+        arc.setArcByCenter((x1 + x2) / 2, (y1 + y2) / 2, r, angle, span, Arc2D.OPEN);
+        path.append(arc, true);
+    }
+    
+    private static void squareTo(Path2D path, double x2, double y2, double width) {
+        double x1 = path.getCurrentPoint().getX();
+        double y1 = path.getCurrentPoint().getY();
+        double dx = x2 - x1;
+        double dy = y2 - y1;
+        double l = Math.sqrt(dx * dx + dy* dy);
+        if (l > 0) {
+            double nx = -dy / l;
+            double ny = dx / l;
+            if (nx - ny < 0) {
+                nx = -nx;
+                ny = -ny;
+            }
+            path.lineTo(x1 + nx * width / 2, y1 + ny * width / 2);
+            path.lineTo(x2 + nx * width / 2, y2 + ny * width / 2);
+            path.lineTo(x2, y2);
+        }
+    }
+
+}