1 /* Dia -- a diagram creation/manipulation program -*- c -*-
2 * Copyright (C) 2002 Alexander Larsson
4 * autoroute.c -- Automatic layout of connections.
5 * Copyright (C) 2003 Lars Clausen
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 #include "connectionpoint.h"
26 #include "orth_conn.h"
27 #include "autoroute.h"
29 #define MAX_BADNESS 10000.0
30 /** Add badness if a line is shorter than this distance. */
32 /** The maximum badness that can be given for a line being too short. */
33 #define MAX_SMALL_BADNESS 10.0
34 /** The badness given for having extra segments. */
35 #define EXTRA_SEGMENT_BADNESS 10.0
37 static real
calculate_badness(Point
*ps
, guint num_points
);
39 static real
autoroute_layout_parallel(Point
*to
,
40 guint
*num_points
, Point
**points
);
41 static real
autoroute_layout_orthogonal(Point
*to
,
43 guint
*num_points
, Point
**points
);
44 static real
autoroute_layout_opposite(Point
*to
,
45 guint
*num_points
, Point
**points
);
46 static Point
autolayout_adjust_for_gap(Point
*pos
, int dir
, ConnectionPoint
*cp
);
47 static guint
autolayout_normalize_points(guint startdir
, guint enddir
,
48 Point start
, Point end
,
50 static Point
*autolayout_unnormalize_points(guint dir
,
55 /** Calculate a 'pleasing' route between two connection points.
56 * If a good route is found, updates the given OrthConn with the values
58 * Otherwise, the OrthConn is untouched, and the function returns FALSE.
59 * Handles are not updated by this operation.
60 * @param conn The orthconn object to autoroute for.
61 * @param startconn The connectionpoint at the start of the orthconn,
62 * or null if it is not connected there at the moment.
63 * @param endconn The connectionpoint at the end (target) of the orthconn,
64 * or null if it is not connected there at the moment.
65 * @returns TRUE if the orthconn could be laid out reasonably, FALSE otherwise.
68 autoroute_layout_orthconn(OrthConn
*conn
,
69 ConnectionPoint
*startconn
, ConnectionPoint
*endconn
)
71 real min_badness
= MAX_BADNESS
;
72 Point
*best_layout
= NULL
;
73 guint best_num_points
= 0;
79 frompos
= conn
->points
[0];
80 topos
= conn
->points
[conn
->numpoints
-1];
81 if (startconn
!= NULL
) {
82 fromdir
= startconn
->directions
;
83 frompos
= startconn
->pos
;
85 else fromdir
= DIR_NORTH
|DIR_EAST
|DIR_SOUTH
|DIR_WEST
;
86 if (endconn
!= NULL
) {
87 todir
= endconn
->directions
;
90 else todir
= DIR_NORTH
|DIR_EAST
|DIR_SOUTH
|DIR_WEST
;
92 for (startdir
= DIR_NORTH
; startdir
<= DIR_WEST
; startdir
*= 2) {
93 for (enddir
= DIR_NORTH
; enddir
<= DIR_WEST
; enddir
*= 2) {
94 if ((fromdir
& startdir
) &&
97 Point
*this_layout
= NULL
;
98 guint this_num_points
;
100 Point startpoint
, endpoint
;
102 startpoint
= autolayout_adjust_for_gap(&frompos
, startdir
, startconn
);
103 endpoint
= autolayout_adjust_for_gap(&topos
, enddir
, endconn
);
105 printf("Startdir %d enddir %d orgstart %.2f, %.2f orgend %.2f, %.2f start %.2f, %.2f end %.2f, %.2f\n",
107 frompos.x, frompos.y,
109 startpoint.x, startpoint.y,
110 endpoint.x, endpoint.y);
112 normal_enddir
= autolayout_normalize_points(startdir
, enddir
,
113 startpoint
, endpoint
,
115 if (normal_enddir
== DIR_NORTH
) {
116 this_badness
= autoroute_layout_parallel(&otherpoint
,
119 } else if (normal_enddir
== DIR_SOUTH
) {
120 this_badness
= autoroute_layout_opposite(&otherpoint
,
124 this_badness
= autoroute_layout_orthogonal(&otherpoint
,
129 if (this_layout
!= NULL
) {
130 if (this_badness
-min_badness
< -0.00001) {
132 printf("Dir %d to %d badness %f < %f\n", startdir, enddir,
133 this_badness, min_badness);
135 min_badness
= this_badness
;
136 if (best_layout
!= NULL
) g_free(best_layout
);
137 best_layout
= autolayout_unnormalize_points(startdir
, startpoint
,
140 best_num_points
= this_num_points
;
149 if (min_badness
< MAX_BADNESS
) {
150 orthconn_set_points(conn
, best_num_points
, best_layout
);
158 /** Returns the basic badness of a length. The best length is MIN_DIST,
159 * anything shorter quickly becomes messy, longer segments are linearly worse.
160 * @param len The length of an orthconn segment.
161 * @returns How bad this segment would be to have in the autorouting.
164 length_badness(real len
)
166 if (len
< MIN_DIST
) {
167 /* This should be zero at MIN_DIST and MAX_SMALL_BADNESS at 0 */
168 return 2*MAX_SMALL_BADNESS
/(1.0+len
/MIN_DIST
) - MAX_SMALL_BADNESS
;
174 /** Returns the accumulated badness of a layout. At the moment, this is
175 * calculated as the sum of the badnesses of the segments plus a badness for
176 * each bend in the line.
177 * @param ps An array of points.
178 * @param num_points How many points in the array.
179 * @returns How bad the points would look as an orthconn layout.
182 calculate_badness(Point
*ps
, guint num_points
)
186 badness
= (num_points
-1)*EXTRA_SEGMENT_BADNESS
;
187 for (i
= 0; i
< num_points
-1; i
++) {
189 real len
= distance_point_point_manhattan(&ps
[i
], &ps
[i
+1]);
190 this_badness
= length_badness(len
);
191 badness
+= this_badness
;
196 /** Adjust one end of an orthconn for gaps, if autogap is on for the connpoint.
197 * @param pos Point of the end of the line.
198 * @param dir Which of the four cardinal directions the line goes from pos.
199 * @param cp The connectionpoint the line is connected to.
200 * @returns Where the line should end to be on the correct edge of the
201 * object, if cp has autogap on.
204 autolayout_adjust_for_gap(Point
*pos
, int dir
, ConnectionPoint
*cp
)
208 /* Do absolute gaps here, once it's defined */
210 if (!connpoint_is_autogap(cp
)) {
216 dir_other
.x
= pos
->x
;
217 dir_other
.y
= pos
->y
;
220 dir_other
.y
+= 2 * (object
->bounding_box
.top
- pos
->y
);
223 dir_other
.y
+= 2 * (object
->bounding_box
.bottom
- pos
->y
);
226 dir_other
.x
+= 2 * (object
->bounding_box
.right
- pos
->x
);
229 dir_other
.x
+= 2 * (object
->bounding_box
.left
- pos
->x
);
232 g_warning("Impossible direction %d\n", dir
);
234 return calculate_object_edge(pos
, &dir_other
, object
);
237 /** Lay out autorouting where start and end lines are parallel pointing the
238 * same direction. This can either a simple up-right-down layout, or if the
239 * to point is too close to origo, it will go up-right-down-left-down.
240 * @param to Where to lay out to, coming from origo.
241 * @param num_points Return value of how many points in the points array.
242 * @param points The points in the layout. Free the array after use. The
243 * passed in is ignored and overwritten, so should be NULL.
244 * @returns The badness of this layout.
247 autoroute_layout_parallel(Point
*to
, guint
*num_points
, Point
**points
)
250 if (fabs(to
->x
) > MIN_DIST
) {
251 real top
= MIN(-MIN_DIST
, to
->y
-MIN_DIST
);
253 printf("Doing parallel layout: Wide\n");
256 ps
= g_new0(Point
, *num_points
);
257 /* points[0] is 0,0 */
262 } else if (to
->y
> 0) { /* Close together, end below */
263 real top
= -MIN_DIST
;
264 real off
= to
->x
+MIN_DIST
*(to
->x
>0?1.0:-1.0);
265 real bottom
= to
->y
-MIN_DIST
;
267 printf("Doing parallel layout: Narrow\n");
270 ps
= g_new0(Point
, *num_points
);
271 /* points[0] is 0,0 */
281 real top
= to
->y
-MIN_DIST
;
282 real off
= MIN_DIST
*(to
->x
>0?-1.0:1.0);
283 real bottom
= -MIN_DIST
;
285 printf("Doing parallel layout: Narrow\n");
288 ps
= g_new0(Point
, *num_points
);
289 /* points[0] is 0,0 */
300 return calculate_badness(ps
, *num_points
);
303 /** Do layout for the case where the directions are orthogonal to each other.
304 * If both x and y of to are far enough from origo, this will be a simple
305 * bend, otherwise it will be a question-mark style line.
306 * @param to Where to lay out to, coming from origo.
307 * @param enddir What direction the endpoint goes, either east or west.
308 * @param num_points Return value of how many points in the points array.
309 * @param points The points in the layout. Free the array after use. The
310 * passed in is ignored and overwritten, so should be NULL.
311 * @returns The badness of this layout.
314 autoroute_layout_orthogonal(Point
*to
, int enddir
,
315 guint
*num_points
, Point
**points
)
317 /* This one doesn't consider enddir yet, not more complex layouts. */
319 real dirmult
= (enddir
==DIR_WEST
?1.0:-1.0);
320 if (to
->y
< -MIN_DIST
) {
321 if (dirmult
*to
->x
> MIN_DIST
) {
323 printf("Doing orthogonal layout: Three-way\n");
326 ps
= g_new0(Point
, *num_points
);
327 /* points[0] is 0,0 */
332 if (dirmult
*to
->x
> 0) off
= -dirmult
*MIN_DIST
;
333 else off
= -dirmult
*(MIN_DIST
+fabs(to
->x
));
335 ps
= g_new0(Point
, *num_points
);
344 if (dirmult
*to
->x
> 2*MIN_DIST
) {
347 ps
= g_new0(Point
, *num_points
);
356 if (dirmult
*to
->x
> 0) off
= -dirmult
*MIN_DIST
;
357 else off
= -dirmult
*(MIN_DIST
+fabs(to
->x
));
359 ps
= g_new0(Point
, *num_points
);
369 printf("Doing orthogonal layout\n");
372 return calculate_badness(ps
, *num_points
);
375 /** Do layout for the case where the end directions are opposite.
376 * This can be either a straight line, a zig-zag, a rotated s-shape or
378 * @param to Where to lay out to, coming from origo.
379 * @param num_points Return value of how many points in the points array.
380 * @param points The points in the layout. Free the array after use. The
381 * passed in is ignored and overwritten, so should be NULL.
382 * @returns The badness of this layout.
385 autoroute_layout_opposite(Point
*to
, guint
*num_points
, Point
**points
)
388 if (to
->y
< -MIN_DIST
) {
390 ps
= g_new0(Point
, *num_points
);
391 if (fabs(to
->x
) < 0.00000001) {
394 return length_badness(fabs(to
->y
))+2*EXTRA_SEGMENT_BADNESS
;
398 printf("Doing opposite layout: Three-way\n");
400 /* points[0] is 0,0 */
406 return 2*length_badness(fabs(mid
))+2*EXTRA_SEGMENT_BADNESS
;
408 } else if (fabs(to
->x
) > 2*MIN_DIST
) {
411 printf("Doing opposite layout: Doorhanger\n");
414 ps
= g_new0(Point
, *num_points
);
415 /* points[0] is 0,0 */
420 ps
[3].y
= to
->y
+MIN_DIST
;
422 ps
[4].y
= to
->y
+MIN_DIST
;
425 real off
= MIN_DIST
*(to
->x
>0?-1.0:1.0);
427 printf("Doing opposite layout: Overlap\n");
430 ps
= g_new0(Point
, *num_points
);
435 ps
[3].y
= to
->y
+MIN_DIST
;
437 ps
[4].y
= to
->y
+MIN_DIST
;
441 return calculate_badness(ps
, *num_points
);
444 /** Rotate a point clockwise.
445 * @param p The point to rotate.
448 point_rotate_cw(Point
*p
)
455 /** Rotate a point counterclockwise.
456 * @param p The point to rotate.
459 point_rotate_ccw(Point
*p
)
466 /** Rotate a point 180 degrees.
467 * @param p The point to rotate.
470 point_rotate_180(Point
*p
)
476 /** Normalizes the directions and points to make startdir be north and
477 * the starting point be 0,0.
478 * @param startdir The original startdir.
479 * @param enddir The original enddir.
480 * @param start The original start point.
481 * @param end The original end point.
482 * @param newend Return address for the normalized end point.
483 * @returns The normalized end direction.
486 autolayout_normalize_points(guint startdir
, guint enddir
,
487 Point start
, Point end
, Point
*newend
)
489 newend
->x
= end
.x
-start
.x
;
490 newend
->y
= end
.y
-start
.y
;
491 if (startdir
== DIR_NORTH
) {
493 } else if (startdir
== DIR_EAST
) {
494 point_rotate_ccw(newend
);
495 if (enddir
== DIR_NORTH
) return DIR_WEST
;
497 } else if (startdir
== DIR_WEST
) {
498 point_rotate_cw(newend
);
499 if (enddir
== DIR_WEST
) return DIR_NORTH
;
501 } else { /* startdir == DIR_SOUTH */
502 point_rotate_180(newend
);
503 if (enddir
< DIR_SOUTH
) return enddir
*4;
504 else return enddir
/4;
506 /* Insert handling of other stuff here */
510 /** Reverses the normalizing process of autolayout_normalize_points by
511 * moving and rotating the points to start at `start' with the start direction
512 * `startdir', instead of from origo going north.
513 * Returns the new array of points, freeing the old one if necessary.
514 * @param startdir The direction to use as a starting direction.
515 * @param start The point to start at.
516 * @param points A set of points laid out from origo northbound. This array
517 * will be freed by calling this function.
518 * @param num_points The number of points in the `points' array.
519 * @returns A newly allocated array of points starting at `start'.
522 autolayout_unnormalize_points(guint startdir
,
527 Point
*newpoints
= g_new(Point
, num_points
);
529 if (startdir
== DIR_NORTH
) {
530 for (i
= 0; i
< num_points
; i
++) {
531 newpoints
[i
] = points
[i
];
532 point_add(&newpoints
[i
], &start
);
534 } else if (startdir
== DIR_WEST
) {
535 for (i
= 0; i
< num_points
; i
++) {
536 newpoints
[i
] = points
[i
];
537 point_rotate_ccw(&newpoints
[i
]);
538 point_add(&newpoints
[i
], &start
);
540 } else if (startdir
== DIR_SOUTH
) {
541 for (i
= 0; i
< num_points
; i
++) {
542 newpoints
[i
] = points
[i
];
543 point_rotate_180(&newpoints
[i
]);
544 point_add(&newpoints
[i
], &start
);
546 } else if (startdir
== DIR_EAST
) {
547 for (i
= 0; i
< num_points
; i
++) {
548 newpoints
[i
] = points
[i
];
549 point_rotate_cw(&newpoints
[i
]);
550 point_add(&newpoints
[i
], &start
);