]> gerrit.simantics Code Review - simantics/platform.git/blob - 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
1 /*******************************************************************************
2  * Copyright (c) 2020 Association for Decentralized Information Management in
3  * Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     Semantum Oy - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.connection.rendering;
13
14 import java.awt.geom.Arc2D;
15 import java.awt.geom.Path2D;
16 import java.awt.geom.PathIterator;
17 import java.awt.geom.Point2D;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.List;
21
22 public class ConnectionCrossings implements PathModifier {
23     public enum Type {
24         NONE,
25         GAP,
26         ARC,
27         SQUARE
28     }
29
30     private List<Segment> segments = new ArrayList<>();
31     private double width;
32     private Type type;
33
34     public void setWidth(double gapWidth) {
35         this.width = gapWidth;
36     }
37
38     public double getWidth() {
39         return width;
40     }
41
42     public void setType(Type type) {
43         this.type = type;
44     }
45
46     public Type getType() {
47         return type;
48     }
49
50     static class Segment {
51         public double x1, y1, x2, y2;
52
53         public Segment(double x1, double y1, double x2, double y2) {
54             this.x1 = x1;
55             this.y1 = y1;
56             this.x2 = x2;
57             this.y2 = y2;
58         }
59     };
60
61     public void reset() {
62         segments.clear();
63     }
64
65     static Double lineLineIntersection(Segment l1, Segment l2) { 
66         double epsilon = 0.001;
67
68         double d = (l1.x1 - l1.x2) * (l2.y1 - l2.y2) - (l1.y1 - l1.y2) * (l2.x1 - l2.x2);
69         if (d == 0.0) return null;
70         double s = ((l1.x1 - l2.x1) * (l2.y1 - l2.y2) - (l1.y1 - l2.y1) * (l2.x1 - l2.x2)) / d;
71         if ((s > epsilon) && (s < 1 - epsilon)) {
72             double t = -((l1.x1 - l1.x2) * (l1.y1 - l2.y1) - (l1.y1 - l1.y2) * (l1.x1 - l2.x1)) / d;
73             if ((t > epsilon) && (t < 1 - epsilon)) {
74                 return t;
75             }
76         }
77         return null; 
78     }
79
80     public Path2D modify(Path2D path) {
81         Path2D.Double path2 = new Path2D.Double();
82         PathIterator iter = path.getPathIterator(null);
83
84         while (!iter.isDone()) {
85
86             double c[] = new double[6];
87             int i = iter.currentSegment(c);
88             switch (i) {
89             case PathIterator.SEG_MOVETO:
90                 path2.moveTo(c[0], c[1]);
91                 break;
92             case PathIterator.SEG_LINETO:
93                 Segment l = new Segment(path2.getCurrentPoint().getX(), path2.getCurrentPoint().getY(), c[0], c[1]);
94                 
95                 List<Double> gaps = new ArrayList<>();
96                 for (Segment old : segments) {
97                     Double t = lineLineIntersection(old, l);
98                     if (t != null) {
99                         gaps.add(t);
100                     }
101                 }
102
103                 if (gaps.isEmpty()) {
104                     path2.lineTo(c[0], c[1]);
105                 } else {
106                     Collections.sort(gaps);
107                     double dx = l.x2 - l.x1;
108                     double dy = l.y2 - l.y1;
109
110                     double pos = 0.0;
111                     double len = Math.sqrt(dx*dx + dy*dy);
112
113                     boolean finish = true;
114                     Point2D prevGapEnd = null;
115                     for (Double gapCenter : gaps) {
116                         double pos2 = gapCenter - width / 2 / len;
117                         double pos3 = gapCenter + width / 2 / len;
118                         if (pos2 > pos) {
119                             handleGap(path2, prevGapEnd);
120                             prevGapEnd = null;
121                             path2.lineTo(l.x1 + pos2 * dx, l.y1 + pos2 * dy);
122                         }
123                         if (pos3 < 1.0) {
124                             double x = l.x1 + pos3 * dx;
125                             double y = l.y1 + pos3 * dy;
126                             prevGapEnd = new Point2D.Double(x, y);
127                         } else {
128                             finish = false;
129                         }
130                         pos = pos3;
131                     }
132                     
133                     if (finish) {
134                         handleGap(path2, prevGapEnd);
135                         path2.lineTo(l.x2, l.y2);
136                     } else {
137                         prevGapEnd = new Point2D.Double(l.x2, l.y2);
138                         handleGap(path2, prevGapEnd);
139                     }
140                 }
141                 segments.add(l);
142
143                 break;
144             case PathIterator.SEG_QUADTO:
145                 // TODO: implement gaps
146                 path2.quadTo(c[0], c[1], c[2], c[3]);
147                 break;
148             case PathIterator.SEG_CUBICTO:
149                 // TODO: implement gaps
150                 path2.curveTo(c[0], c[1], c[2], c[3], c[4], c[5]);
151                 break;
152             case PathIterator.SEG_CLOSE:
153                 // TODO: implement gaps
154                 path2.closePath();
155                 break;
156             default:
157                 throw new RuntimeException("Unexpected segment type " + i);
158             }
159             iter.next();
160         }
161         return path2;
162     }
163
164     private void handleGap(Path2D path, Point2D prevGapEnd) {
165         if (prevGapEnd != null) {
166             switch (type) {
167             case ARC:
168                 arcTo(path, prevGapEnd.getX(), prevGapEnd.getY());
169                 break;
170             case SQUARE:
171                 squareTo(path, prevGapEnd.getX(), prevGapEnd.getY(), width);
172                 break;
173             case GAP:
174                 path.moveTo(prevGapEnd.getX(), prevGapEnd.getY());
175                 break;
176             case NONE:
177                 break;
178             }
179         }
180     }
181
182     private static void arcTo(Path2D path, double x2, double y2) {
183         Arc2D arc = new Arc2D.Double();
184         double x1 = path.getCurrentPoint().getX();
185         double y1 = path.getCurrentPoint().getY();
186         double dx = x2 - x1;
187         double dy = y2 - y1;
188         double r = Math.sqrt(dx * dx + dy * dy) / 2;
189         double angle = Math.atan2(dx,  dy) * 180 / Math.PI + 90;
190         double span = (angle > 225 || angle < 45) ? 180 : -180;
191         arc.setArcByCenter((x1 + x2) / 2, (y1 + y2) / 2, r, angle, span, Arc2D.OPEN);
192         path.append(arc, true);
193     }
194     
195     private static void squareTo(Path2D path, double x2, double y2, double width) {
196         double x1 = path.getCurrentPoint().getX();
197         double y1 = path.getCurrentPoint().getY();
198         double dx = x2 - x1;
199         double dy = y2 - y1;
200         double l = Math.sqrt(dx * dx + dy* dy);
201         if (l > 0) {
202             double nx = -dy / l;
203             double ny = dx / l;
204             if (nx - ny < 0) {
205                 nx = -nx;
206                 ny = -ny;
207             }
208             path.lineTo(x1 + nx * width / 2, y1 + ny * width / 2);
209             path.lineTo(x2 + nx * width / 2, y2 + ny * width / 2);
210             path.lineTo(x2, y2);
211         }
212     }
213
214 }