]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/SimpleConnectionUtility.java
Round corners between non-axis-aligned connection lines properly
[simantics/platform.git] / bundles / org.simantics.diagram.connection / src / org / simantics / diagram / connection / SimpleConnectionUtility.java
1 /*******************************************************************************
2  * Copyright (c) 2011 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  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.connection;
13
14 /**
15  * An internal utility class for routing simple connections 
16  * (a connection with two terminals without any route lines).
17  * 
18  * @author Hannu Niemistö
19  */
20 public class SimpleConnectionUtility {
21
22     public static boolean allowsDirection(RouteTerminal a, int dir) {
23         return Directions.isAllowed(a.getAllowedDirections(), dir);
24     }
25     
26     public static final int DIRECT_HORIZONTAL_CONNECTION = 0;
27     public static final int DIRECT_VERTICAL_CONNECTION = 1;
28     
29     public static final int ONE_BEND_HORIZONTAL_VERTICAL = 2;
30     public static final int ONE_BEND_VERTICAL_HORIZONTAL = 3;
31
32     public static final int MORE_BENDS_BBS_DONT_INTERSECT = 4;
33     public static final int MORE_BENDS_BBS_INTERSECT = 5;
34     
35     public static final int COMPLEX_CONNECTION = 6;
36     
37     public static int simpleConnectionCase(RouteTerminal a, RouteTerminal b) {
38         if(a.isRouteToBounds() && b.isRouteToBounds())
39             return simpleConnectionCaseRouteToBounds(a, b);
40                     
41         // Can connect terminals by one straight line?
42         if(a.y == b.y) {
43             if(a.x < b.x) {
44                 if(allowsDirection(a, 0) && allowsDirection(b, 2))
45                     return DIRECT_HORIZONTAL_CONNECTION;
46             }
47             else {
48                 if(allowsDirection(a, 2) && allowsDirection(b, 0))
49                     return DIRECT_HORIZONTAL_CONNECTION;
50             }
51         }
52         else if(a.x == b.x) {
53             if(a.y < b.y) {
54                 if(allowsDirection(a, 1) && allowsDirection(b, 3))
55                     return DIRECT_VERTICAL_CONNECTION;
56             }
57             else {
58                 if(allowsDirection(a, 3) && allowsDirection(b, 1))
59                     return DIRECT_VERTICAL_CONNECTION;
60             }
61         }
62         
63         // Can connect terminals by two lines?
64         if(a.x < b.x) {
65             if(a.y < b.y) {
66                 if(allowsDirection(a, 0) && allowsDirection(b, 3)
67                         /*&& b.x >= a.getMaxX() && a.y <= b.getMinY()*/)
68                     return ONE_BEND_HORIZONTAL_VERTICAL;
69                 else if(allowsDirection(a, 1) && allowsDirection(b, 2)
70                         /*&& b.y >= a.getMaxY() && a.x <= b.getMinX()*/)
71                     return ONE_BEND_VERTICAL_HORIZONTAL;
72             }
73             else {
74                 if(allowsDirection(a, 0) && allowsDirection(b, 1)
75                         /*&& b.x >= a.getMaxX() && a.y >= b.getMaxY()*/)
76                     return ONE_BEND_HORIZONTAL_VERTICAL;
77                 else if(allowsDirection(a, 3) && allowsDirection(b, 2)
78                         /*&& b.y <= a.getMinY() && a.x <= b.getMinX()*/)
79                     return ONE_BEND_VERTICAL_HORIZONTAL;
80             }
81         }
82         else {
83             if(a.y < b.y) {
84                 if(allowsDirection(a, 2) && allowsDirection(b, 3)
85                         /*&& b.x <= a.getMinX() && a.y <= b.getMinY()*/)
86                     return ONE_BEND_HORIZONTAL_VERTICAL;
87                 else if(allowsDirection(a, 1) && allowsDirection(b, 0)
88                         /*&& b.y >= a.getMaxY() && a.x >= b.getMaxX()*/)
89                     return ONE_BEND_VERTICAL_HORIZONTAL;
90             }
91             else {
92                 if(allowsDirection(a, 2) && allowsDirection(b, 1)
93                         /*&& b.x <= a.getMinX() && a.y >= b.getMaxY()*/)
94                     return ONE_BEND_HORIZONTAL_VERTICAL;
95                 else if(allowsDirection(a, 3) && allowsDirection(b, 0)
96                         /*&& b.y <= a.getMinY() && a.x >= b.getMaxX()*/)
97                     return ONE_BEND_VERTICAL_HORIZONTAL;
98             }
99         }
100         
101         // Do bounding boxes intersect each other?
102         boolean boundingBoxesIntersect = !(
103                 a.getMaxX() < b.getMinX() ||
104                 a.getMinX() > b.getMaxX() ||
105                 a.getMaxY() < b.getMinY() ||
106                 a.getMinY() > b.getMaxY() 
107                 );
108         
109         if(boundingBoxesIntersect) {
110             // Can connect terminals by two lines if we ignore bounding boxes?
111             if(a.x < b.x) {
112                 if(a.y < b.y) {
113                     if(allowsDirection(a, 0) && allowsDirection(b, 3))
114                         return ONE_BEND_HORIZONTAL_VERTICAL;
115                     else if(allowsDirection(a, 1) && allowsDirection(b, 2))
116                         return ONE_BEND_VERTICAL_HORIZONTAL;
117                 }
118                 else {
119                     if(allowsDirection(a, 0) && allowsDirection(b, 1))
120                         return ONE_BEND_HORIZONTAL_VERTICAL;
121                     else if(allowsDirection(a, 3) && allowsDirection(b, 2))
122                         return ONE_BEND_VERTICAL_HORIZONTAL;
123                 }
124             }
125             else {
126                 if(a.y < b.y) {
127                     if(allowsDirection(a, 2) && allowsDirection(b, 3))
128                         return ONE_BEND_HORIZONTAL_VERTICAL;
129                     else if(allowsDirection(a, 1) && allowsDirection(b, 0))
130                         return ONE_BEND_VERTICAL_HORIZONTAL;
131                 }
132                 else {
133                     if(allowsDirection(a, 2) && allowsDirection(b, 1))
134                         return ONE_BEND_HORIZONTAL_VERTICAL;
135                     else if(allowsDirection(a, 3) && allowsDirection(b, 0))
136                         return ONE_BEND_VERTICAL_HORIZONTAL;
137                 }
138             }
139             
140             // Otherwise
141             return MORE_BENDS_BBS_INTERSECT;
142         }        
143         
144         // Otherwise
145         return MORE_BENDS_BBS_DONT_INTERSECT;
146     }
147     
148     public static int simpleConnectionCaseRouteToBounds(RouteTerminal a,
149             RouteTerminal b) {
150         double aX = 0.5*(a.getMinX() + a.getMaxX());
151         double aY = 0.5*(a.getMinY() + a.getMaxY());
152         double bX = 0.5*(b.getMinX() + b.getMaxX());
153         double bY = 0.5*(b.getMinY() + b.getMaxY());
154         
155         double minY = Math.max(a.getMinY(), b.getMinY());
156         double maxY = Math.min(a.getMaxY(), b.getMaxY());
157         
158         if(minY <= maxY) {
159             double cY = 0.5*(minY+maxY);
160             a.setY(cY);
161             b.setY(cY);
162             if(aX < bX) {
163                 a.setX(a.getMaxX());
164                 b.setX(b.getMinX());
165             }
166             else {
167                 a.setX(a.getMinX());
168                 b.setX(b.getMaxX());
169             }
170             return DIRECT_HORIZONTAL_CONNECTION;
171         }
172         
173         double minX = Math.max(a.getMinX(), b.getMinX());
174         double maxX = Math.min(a.getMaxX(), b.getMaxX());
175         
176         if(minX <= maxX) {
177             double cX = 0.5*(minX+maxX);
178             a.setX(cX);
179             b.setX(cX);
180             if(aY < bY) {
181                 a.setY(a.getMaxY());
182                 b.setY(b.getMinY());
183             }
184             else {
185                 a.setY(a.getMinY());
186                 b.setY(b.getMaxY());
187             }
188             return DIRECT_VERTICAL_CONNECTION;
189         }
190         
191         {
192             a.setY(aY);
193             b.setX(bX);
194             if(aX < bX) {
195                 a.setX(a.getMaxX());
196             }
197             else {
198                 a.setX(a.getMinX());
199             }
200             if(aY < bY) {
201                 b.setY(b.getMinY());
202             }
203             else {
204                 b.setY(b.getMaxY());
205             }
206             return ONE_BEND_HORIZONTAL_VERTICAL;
207         }
208     }
209
210     /**
211      * Finds a route line for two route terminals.
212      */
213     public static RouteLine findRouteLine(RouteTerminal a, RouteTerminal b, boolean terminalsIntersect) {
214         if(terminalsIntersect) {
215             if(a.x < b.x) {
216                 if((a.getAllowedDirections() & RouteTerminal.DIR_RIGHT) != 0 
217                         && (b.getAllowedDirections() & RouteTerminal.DIR_LEFT) != 0) {
218                     return new RouteLine(false, 0.5 * (a.x + b.x));
219                 }
220             }
221             else {
222                 if((a.getAllowedDirections() & RouteTerminal.DIR_LEFT) != 0 
223                         && (b.getAllowedDirections() & RouteTerminal.DIR_RIGHT) != 0) {
224                     return new RouteLine(false, 0.5 * (a.x + b.x));
225                 }
226             }
227             if(a.y < b.y) {
228                 if((a.getAllowedDirections() & RouteTerminal.DIR_DOWN) != 0 
229                         && (b.getAllowedDirections() & RouteTerminal.DIR_UP) != 0) {
230                     return new RouteLine(true, 0.5 * (a.y + b.y));
231                 }
232             }
233             else {
234                 if((a.getAllowedDirections() & RouteTerminal.DIR_UP) != 0 
235                         && (b.getAllowedDirections() & RouteTerminal.DIR_DOWN) != 0) {
236                     return new RouteLine(true, 0.5 * (a.y + b.y));
237                 }
238             }
239         }
240         
241         //int aDir = Directions.firstAllowedDirection(a.getAllowedDirections());
242         //int bDir = Directions.firstAllowedDirection(b.getAllowedDirections());
243         
244         boolean isHorizontal = true;
245         double position = 0.0;
246         
247         loop:
248         for(int aDir=0;aDir<4;++aDir)
249             if(Directions.isAllowed(a.getAllowedDirections(), aDir))
250                 for(int bDir=0;bDir<4;++bDir)
251                     if(Directions.isAllowed(b.getAllowedDirections(), bDir)) {
252                         // Connection starts to the same direction from the both terminals
253                         if(aDir == bDir) {
254                             isHorizontal = !isHorizontal(aDir);
255                             if(!terminalsIntersect) {
256                                 if(dist(aDir, a, b) > 0 && isIn(aDir+1, a.x, a.y, b)) {
257                                     position = middle(aDir, a, b);
258                                     break loop;
259                                 }
260                                 else if(dist(aDir, b, a) > 0 && isIn(aDir+1, b.x, b.y, a)) {
261                                     position = middle(aDir, b, a);
262                                     break loop;
263                                 }
264                             }
265                             position = boundary(aDir, a, b);
266                         }
267                         // Connection starts horizontally from one terminal and
268                         // vertically from another terminal
269                         else if(((aDir ^ bDir)&1) == 1) {
270                             if(dist(aDir, a, b) >= 0) {
271                                 isHorizontal = !isHorizontal(aDir);
272                                 position = middle(aDir, a, b);
273                                 break loop;
274                             }
275                             else if(dist(bDir, b, a) >= 0) {
276                                 isHorizontal = isHorizontal(aDir);;
277                                 position = middle(bDir, b, a);
278                                 break loop;
279                             }
280                             else if(firstIsBoundary(bDir, a, b)) {
281                                 isHorizontal = isHorizontal(aDir);
282                                 position = boundary(bDir, b, a);
283                             }
284                             else {
285                                 isHorizontal = !isHorizontal(aDir);
286                                 position = boundary(aDir, a, b);
287                             }
288                         }
289                         // Connection starts to opposite directions from the terminals
290                         else {           
291                             if(dist(aDir, a, b) >= 0.0) {
292                                 isHorizontal = !isHorizontal(aDir);
293                                 position = middle(aDir, a, b);
294                                 break loop;
295                             }
296                             else if(dist(aDir+1, a, b) >= 0.0) {
297                                 isHorizontal = isHorizontal(aDir);
298                                 position = middle(aDir+1, a, b);
299                                 break loop;
300                             }
301                             else if(dist(aDir-1, a, b) >= 0.0) {
302                                 isHorizontal = isHorizontal(aDir);
303                                 position = middle(aDir+1, a, b);
304                                 break loop;
305                             }
306                             else {
307                                 isHorizontal = isHorizontal(aDir);
308                                 double b1 = boundary(aDir+1, a, b);
309                                 double b2 = boundary(aDir-1, a, b);
310                                 double cost1, cost2;
311                                 if(isHorizontal) {
312                                     double da1 = a.y - b1;
313                                     double db1 = b.y - b1;
314                                     cost1 = da1*da1 + db1*db1;
315                                     double da2 = a.y - b2;
316                                     double db2 = b.y - b2;
317                                     cost2 = da2*da2 + db1*db2;
318                                 }
319                                 else {
320                                     double da1 = a.x - b1;
321                                     double db1 = b.x - b1;
322                                     cost1 = da1*da1 + db1*db1;
323                                     double da2 = a.x - b2;
324                                     double db2 = b.x - b2;
325                                     cost2 = da2*da2 + db1*db2;
326                                 }
327                                 position = cost1 <= cost2 ? b1 : b2;
328                             }
329                         }
330                     }
331         return new RouteLine(isHorizontal, position);
332     }
333     
334     /**
335      * Computes the difference between two points to the given direction
336      */
337     public static double diff(int dir, double x1, double y1, double x2, double y2) {
338         switch(dir&3) {
339         case 0: return x1 - x2;
340         case 1: return y1 - y2;
341         case 2: return x2 - x1;
342         case 3: return y2 - y1;
343         default: throw new Error("Should not happen.");
344         }
345     }
346     
347     /**
348      * Computes the distance of the bounding boxes of the two route terminals
349      * to the given direction.
350      */
351     public static double dist(int dir, RouteTerminal a, RouteTerminal b) {
352         switch(dir&3) {
353         case 0: return b.getMinX() - a.getMaxX();
354         case 1: return b.getMinY() - a.getMaxY();
355         case 2: return a.getMinX() - b.getMaxX();
356         case 3: return a.getMinY() - b.getMaxY();
357         default: throw new Error("Should not happen.");
358         }
359     }
360     
361     /**
362      * Computes the middle point between two terminals in the given direction.
363      */
364     public static double middle(int dir, RouteTerminal a, RouteTerminal b) {
365         switch(dir&3) {
366         case 0: return 0.5*(b.getMinX() + a.getMaxX());
367         case 1: return 0.5*(b.getMinY() + a.getMaxY());
368         case 2: return 0.5*(a.getMinX() + b.getMaxX());
369         case 3: return 0.5*(a.getMinY() + b.getMaxY());
370         default: throw new Error("Should not happen.");
371         }
372     }
373     
374     /**
375      * Tests whether the point is inside the bounding box of the terminal
376      * in the given direction.
377      */
378     public static boolean isIn(int dir, double x, double y, RouteTerminal a) {
379         if((dir&1) == 0)
380             return a.getMinX() < x && x < a.getMaxX();
381         else
382             return a.getMinY() < y && y < a.getMaxY();
383     }
384     
385     public static boolean isHorizontal(int dir) {
386         return (dir&1) == 0;
387     }
388     
389     /**
390      * Gives the boundary of the route terminal in the given direction.
391      */
392     public static double boundary(int dir, RouteTerminal a) {
393         switch(dir&3) {
394         case 0: return a.getMaxX();
395         case 1: return a.getMaxY();
396         case 2: return a.getMinX();
397         case 3: return a.getMinY();
398         default: throw new Error("Should not happen.");
399         }
400     }
401     
402     /**
403      * Gives the boundary of two route terminals in the given direction.
404      */
405     public static double boundary(int dir, RouteTerminal a, RouteTerminal b) {
406         switch(dir&3) {
407         case 0: return Math.max(a.getMaxX(), b.getMaxX());
408         case 1: return Math.max(a.getMaxY(), b.getMaxY());
409         case 2: return Math.min(a.getMinX(), b.getMinX());
410         case 3: return Math.min(a.getMinY(), b.getMinY());
411         default: throw new Error("Should not happen.");
412         }
413     }
414     
415     /**
416      * Returns true if the first terminal is farther away in the given direction.
417      */
418     public static boolean firstIsBoundary(int dir, RouteTerminal a, RouteTerminal b) {
419         switch(dir&3) {
420         case 0: return a.getMaxX() >= b.getMaxX();
421         case 1: return a.getMaxY() >= b.getMaxY();
422         case 2: return a.getMinX() <= b.getMinX();
423         case 3: return a.getMinY() <= b.getMinY();
424         default: throw new Error("Should not happen.");
425         }
426     }
427         
428 }