]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/rendering/ConnectionCrossings.java
6349088842521c211d6a3d5ba8385290bf8de24b
[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 double widthd2;
33     private Type type;
34     private boolean keepLines = false;
35     
36
37     public void setWidth(double gapWidth) {
38         this.width = gapWidth;
39         this.widthd2 = width * 0.5;
40     }
41
42     public double getWidth() {
43         return width;
44     }
45
46     public void setType(Type type) {
47         this.type = type;
48     }
49
50     public Type getType() {
51         return type;
52     }
53     
54     /**
55      * When keep lines is enabled, we keep short lines in the start and end of a line segment, instead of removing them completely with gaps.
56      * @return
57      */
58     public boolean isKeepLines() {
59         return keepLines;
60     }
61     
62     public void setKeepLines(boolean keepLines) {
63         this.keepLines = keepLines;
64     }
65
66     static class Segment {
67         public double x1, y1, x2, y2;
68
69         public Segment(double x1, double y1, double x2, double y2) {
70             this.x1 = x1;
71             this.y1 = y1;
72             this.x2 = x2;
73             this.y2 = y2;
74         }
75     };
76
77     public void reset() {
78         segments.clear();
79     }
80
81     static Double lineLineIntersection(Segment l1, Segment l2) { 
82         double epsilon = 0.001;
83
84         double d = (l1.x1 - l1.x2) * (l2.y1 - l2.y2) - (l1.y1 - l1.y2) * (l2.x1 - l2.x2);
85         if (d == 0.0) return null;
86         double s = ((l1.x1 - l2.x1) * (l2.y1 - l2.y2) - (l1.y1 - l2.y1) * (l2.x1 - l2.x2)) / d;
87         if ((s > epsilon) && (s < 1 - epsilon)) {
88             double t = -((l1.x1 - l1.x2) * (l1.y1 - l2.y1) - (l1.y1 - l1.y2) * (l1.x1 - l2.x1)) / d;
89             if ((t > epsilon) && (t < 1 - epsilon)) {
90                 return t;
91             }
92         }
93         return null; 
94     }
95
96     public Path2D modify(Path2D path) {
97         Path2D.Double path2 = new Path2D.Double();
98         PathIterator iter = path.getPathIterator(null);
99         if (!isKeepLines()) {
100             while (!iter.isDone()) {
101     
102                 double c[] = new double[6];
103                 int i = iter.currentSegment(c);
104                 switch (i) {
105                 case PathIterator.SEG_MOVETO:
106                     path2.moveTo(c[0], c[1]);
107                     break;
108                 case PathIterator.SEG_LINETO:
109                     Segment l = new Segment(path2.getCurrentPoint().getX(), path2.getCurrentPoint().getY(), c[0], c[1]);
110
111                     List<Double> gaps = new ArrayList<>();
112                     for (Segment old : segments) {
113                         Double t = lineLineIntersection(old, l);
114                         if (t != null) {
115                             gaps.add(t);
116                         }
117                     }
118
119                     if (gaps.isEmpty()) {
120                         path2.lineTo(c[0], c[1]);
121                     } else {
122                         Collections.sort(gaps);
123                         double dx = l.x2 - l.x1;
124                         double dy = l.y2 - l.y1;
125
126                         double pos = 0.0;
127                         double len = Math.sqrt(dx * dx + dy * dy);
128                         double len1 = 1.0 / len;
129
130                         boolean finish = true;
131                         Point2D prevGapEnd = null;
132                         for (Double gapCenter : gaps) {
133                             double pos2 = gapCenter - widthd2 * len1;
134                             double pos3 = gapCenter + widthd2 * len1;
135                             if (pos2 > pos) {
136                                 handleGap(path2, prevGapEnd);
137                                 prevGapEnd = null;
138                                 path2.lineTo(l.x1 + pos2 * dx, l.y1 + pos2 * dy);
139                             }
140                             if (pos3 < 1.0) {
141                                 double x = l.x1 + pos3 * dx;
142                                 double y = l.y1 + pos3 * dy;
143                                 prevGapEnd = new Point2D.Double(x, y);
144                             } else {
145                                 finish = false;
146                             }
147                             pos = pos3;
148                         }
149
150                         if (finish) {
151                             handleGap(path2, prevGapEnd);
152                             path2.lineTo(l.x2, l.y2);
153                         } else {
154                             prevGapEnd = new Point2D.Double(l.x2, l.y2);
155                             handleGap(path2, prevGapEnd);
156                         }
157                     }
158                     segments.add(l);
159
160                     break;
161                 case PathIterator.SEG_QUADTO:
162                     // TODO: implement gaps
163                     path2.quadTo(c[0], c[1], c[2], c[3]);
164                     break;
165                 case PathIterator.SEG_CUBICTO:
166                     // TODO: implement gaps
167                     path2.curveTo(c[0], c[1], c[2], c[3], c[4], c[5]);
168                     break;
169                 case PathIterator.SEG_CLOSE:
170                     // TODO: implement gaps
171                     path2.closePath();
172                     break;
173                 default:
174                     throw new RuntimeException("Unexpected segment type " + i);
175                 }
176                 iter.next();
177             }
178         } else {
179             while (!iter.isDone()) {
180
181                 double c[] = new double[6];
182                 int i = iter.currentSegment(c);
183                 switch (i) {
184                 case PathIterator.SEG_MOVETO:
185                     path2.moveTo(c[0], c[1]);
186                     break;
187                 case PathIterator.SEG_LINETO:
188                     Segment l = new Segment(path2.getCurrentPoint().getX(), path2.getCurrentPoint().getY(), c[0], c[1]);
189
190                     List<Double> gaps = new ArrayList<>();
191                     for (Segment old : segments) {
192                         Double t = lineLineIntersection(old, l);
193                         if (t != null) {
194                             gaps.add(t);
195                         }
196                     }
197
198                     if (gaps.isEmpty()) {
199                         path2.lineTo(c[0], c[1]);
200                     } else {
201                         Collections.sort(gaps);
202                         double dx = l.x2 - l.x1;
203                         double dy = l.y2 - l.y1;
204
205                         double pos = 0.0;
206                         double len = Math.sqrt(dx * dx + dy * dy);
207                         double len1 = 1.0 / len;
208
209                         boolean finish = true;
210                         Point2D prevGapEnd = null;
211                         for (int j = 0; j < gaps.size(); j++) {
212                             Double gapCenter = gaps.get(j);
213                             double gc = gapCenter * len;
214                             double pos2 = gc - widthd2;
215                             double pos3 = gc + widthd2;
216                             if (j == 0) {
217                                 if (pos2 < widthd2)
218                                     pos2 += (widthd2 - pos2) * 0.5;
219                             }
220                             if (j == gaps.size() - 1) {
221                                 double d = len - pos3;
222                                 if (d < widthd2)
223                                     pos3 -= (widthd2 - d) * 0.5;
224                             }
225                             if (pos2 > pos) {
226                                 handleGap(path2, prevGapEnd);
227                                 prevGapEnd = null;
228                                 pos2 *= len1;
229                                 path2.lineTo(l.x1 + pos2 * dx, l.y1 + pos2 * dy);
230                             }
231                             if (pos3 < len) {
232                                 pos = pos3;
233                                 pos3 *= len1;
234                                 double x = l.x1 + pos3 * dx;
235                                 double y = l.y1 + pos3 * dy;
236                                 prevGapEnd = new Point2D.Double(x, y);
237                             } else {
238                                 pos = pos3;
239                                 finish = false;
240                             }
241                         }
242
243                         if (finish) {
244                             handleGap(path2, prevGapEnd);
245                             path2.lineTo(l.x2, l.y2);
246                         } else {
247                             prevGapEnd = new Point2D.Double(l.x2, l.y2);
248                             handleGap(path2, prevGapEnd);
249                         }
250                     }
251                     segments.add(l);
252
253                     break;
254                 case PathIterator.SEG_QUADTO:
255                     // TODO: implement gaps
256                     path2.quadTo(c[0], c[1], c[2], c[3]);
257                     break;
258                 case PathIterator.SEG_CUBICTO:
259                     // TODO: implement gaps
260                     path2.curveTo(c[0], c[1], c[2], c[3], c[4], c[5]);
261                     break;
262                 case PathIterator.SEG_CLOSE:
263                     // TODO: implement gaps
264                     path2.closePath();
265                     break;
266                 default:
267                     throw new RuntimeException("Unexpected segment type " + i);
268                 }
269                 iter.next();
270             }
271         }
272         return path2;
273     }
274
275     private void handleGap(Path2D path, Point2D prevGapEnd) {
276         if (prevGapEnd != null) {
277             switch (type) {
278             case ARC:
279                 arcTo(path, prevGapEnd.getX(), prevGapEnd.getY());
280                 break;
281             case SQUARE:
282                 squareTo(path, prevGapEnd.getX(), prevGapEnd.getY(), width);
283                 break;
284             case GAP:
285                 path.moveTo(prevGapEnd.getX(), prevGapEnd.getY());
286                 break;
287             case NONE:
288                 break;
289             }
290         }
291     }
292
293     private static void arcTo(Path2D path, double x2, double y2) {
294         Arc2D arc = new Arc2D.Double();
295         double x1 = path.getCurrentPoint().getX();
296         double y1 = path.getCurrentPoint().getY();
297         double dx = x2 - x1;
298         double dy = y2 - y1;
299         double r = Math.sqrt(dx * dx + dy * dy) / 2;
300         double angle = Math.atan2(dx,  dy) * 180 / Math.PI + 90;
301         double span = (angle > 225 || angle < 45) ? 180 : -180;
302         arc.setArcByCenter((x1 + x2) / 2, (y1 + y2) / 2, r, angle, span, Arc2D.OPEN);
303         path.append(arc, true);
304     }
305     
306     private static void squareTo(Path2D path, double x2, double y2, double width) {
307         double x1 = path.getCurrentPoint().getX();
308         double y1 = path.getCurrentPoint().getY();
309         double dx = x2 - x1;
310         double dy = y2 - y1;
311         double l = Math.sqrt(dx * dx + dy* dy);
312         if (l > 0) {
313             double nx = -dy / l;
314             double ny = dx / l;
315             if (nx - ny < 0) {
316                 nx = -nx;
317                 ny = -ny;
318             }
319             path.lineTo(x1 + nx * width / 2, y1 + ny * width / 2);
320             path.lineTo(x2 + nx * width / 2, y2 + ny * width / 2);
321             path.lineTo(x2, y2);
322         }
323     }
324
325 }