]> gerrit.simantics Code Review - simantics/platform.git/blob
2367805caed2dcbc9c44a5fb72e9c897d759cd7e
[simantics/platform.git] /
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                                 pos3 *= len1;
233                                 double x = l.x1 + pos3 * dx;
234                                 double y = l.y1 + pos3 * dy;
235                                 prevGapEnd = new Point2D.Double(x, y);
236                             } else {
237                                 finish = false;
238                             }
239                             pos = pos3;
240                         }
241
242                         if (finish) {
243                             handleGap(path2, prevGapEnd);
244                             path2.lineTo(l.x2, l.y2);
245                         } else {
246                             prevGapEnd = new Point2D.Double(l.x2, l.y2);
247                             handleGap(path2, prevGapEnd);
248                         }
249                     }
250                     segments.add(l);
251
252                     break;
253                 case PathIterator.SEG_QUADTO:
254                     // TODO: implement gaps
255                     path2.quadTo(c[0], c[1], c[2], c[3]);
256                     break;
257                 case PathIterator.SEG_CUBICTO:
258                     // TODO: implement gaps
259                     path2.curveTo(c[0], c[1], c[2], c[3], c[4], c[5]);
260                     break;
261                 case PathIterator.SEG_CLOSE:
262                     // TODO: implement gaps
263                     path2.closePath();
264                     break;
265                 default:
266                     throw new RuntimeException("Unexpected segment type " + i);
267                 }
268                 iter.next();
269             }
270         }
271         return path2;
272     }
273
274     private void handleGap(Path2D path, Point2D prevGapEnd) {
275         if (prevGapEnd != null) {
276             switch (type) {
277             case ARC:
278                 arcTo(path, prevGapEnd.getX(), prevGapEnd.getY());
279                 break;
280             case SQUARE:
281                 squareTo(path, prevGapEnd.getX(), prevGapEnd.getY(), width);
282                 break;
283             case GAP:
284                 path.moveTo(prevGapEnd.getX(), prevGapEnd.getY());
285                 break;
286             case NONE:
287                 break;
288             }
289         }
290     }
291
292     private static void arcTo(Path2D path, double x2, double y2) {
293         Arc2D arc = new Arc2D.Double();
294         double x1 = path.getCurrentPoint().getX();
295         double y1 = path.getCurrentPoint().getY();
296         double dx = x2 - x1;
297         double dy = y2 - y1;
298         double r = Math.sqrt(dx * dx + dy * dy) / 2;
299         double angle = Math.atan2(dx,  dy) * 180 / Math.PI + 90;
300         double span = (angle > 225 || angle < 45) ? 180 : -180;
301         arc.setArcByCenter((x1 + x2) / 2, (y1 + y2) / 2, r, angle, span, Arc2D.OPEN);
302         path.append(arc, true);
303     }
304     
305     private static void squareTo(Path2D path, double x2, double y2, double width) {
306         double x1 = path.getCurrentPoint().getX();
307         double y1 = path.getCurrentPoint().getY();
308         double dx = x2 - x1;
309         double dy = y2 - y1;
310         double l = Math.sqrt(dx * dx + dy* dy);
311         if (l > 0) {
312             double nx = -dy / l;
313             double ny = dx / l;
314             if (nx - ny < 0) {
315                 nx = -nx;
316                 ny = -ny;
317             }
318             path.lineTo(x1 + nx * width / 2, y1 + ny * width / 2);
319             path.lineTo(x2 + nx * width / 2, y2 + ny * width / 2);
320             path.lineTo(x2, y2);
321         }
322     }
323
324 }