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.
62 autoroute_layout_orthconn(OrthConn
*conn
,
63 ConnectionPoint
*startconn
, ConnectionPoint
*endconn
)
65 real min_badness
= MAX_BADNESS
;
66 Point
*best_layout
= NULL
;
67 guint best_num_points
= 0;
73 frompos
= conn
->points
[0];
74 topos
= conn
->points
[conn
->numpoints
-1];
75 if (startconn
!= NULL
) {
76 fromdir
= startconn
->directions
;
77 frompos
= startconn
->pos
;
79 else fromdir
= DIR_NORTH
|DIR_EAST
|DIR_SOUTH
|DIR_WEST
;
80 if (endconn
!= NULL
) {
81 todir
= endconn
->directions
;
84 else todir
= DIR_NORTH
|DIR_EAST
|DIR_SOUTH
|DIR_WEST
;
86 for (startdir
= DIR_NORTH
; startdir
<= DIR_WEST
; startdir
*= 2) {
87 for (enddir
= DIR_NORTH
; enddir
<= DIR_WEST
; enddir
*= 2) {
88 if ((fromdir
& startdir
) &&
91 Point
*this_layout
= NULL
;
92 guint this_num_points
;
94 Point startpoint
, endpoint
;
96 startpoint
= autolayout_adjust_for_gap(&frompos
, startdir
, startconn
);
97 endpoint
= autolayout_adjust_for_gap(&topos
, enddir
, endconn
);
99 printf("Startdir %d enddir %d orgstart %.2f, %.2f orgend %.2f, %.2f start %.2f, %.2f end %.2f, %.2f\n",
101 frompos.x, frompos.y,
103 startpoint.x, startpoint.y,
104 endpoint.x, endpoint.y);
106 normal_enddir
= autolayout_normalize_points(startdir
, enddir
,
107 startpoint
, endpoint
,
109 if (normal_enddir
== DIR_NORTH
) {
110 this_badness
= autoroute_layout_parallel(&otherpoint
,
113 } else if (normal_enddir
== DIR_SOUTH
) {
114 this_badness
= autoroute_layout_opposite(&otherpoint
,
118 this_badness
= autoroute_layout_orthogonal(&otherpoint
,
123 if (this_layout
!= NULL
) {
124 if (this_badness
-min_badness
< -0.00001) {
126 printf("Dir %d to %d badness %f < %f\n", startdir, enddir,
127 this_badness, min_badness);
129 min_badness
= this_badness
;
130 if (best_layout
!= NULL
) g_free(best_layout
);
131 best_layout
= autolayout_unnormalize_points(startdir
, startpoint
,
134 best_num_points
= this_num_points
;
143 if (min_badness
< MAX_BADNESS
) {
144 orthconn_set_points(conn
, best_num_points
, best_layout
);
152 /** Returns the basic badness of a length */
154 length_badness(real len
)
156 if (len
< MIN_DIST
) {
157 /* This should be zero at MIN_DIST and MAX_SMALL_BADNESS at 0 */
158 return 2*MAX_SMALL_BADNESS
/(1.0+len
/MIN_DIST
) - MAX_SMALL_BADNESS
;
164 /** Returns the accumulated badness of a layout */
166 calculate_badness(Point
*ps
, guint num_points
)
170 badness
= (num_points
-1)*EXTRA_SEGMENT_BADNESS
;
171 for (i
= 0; i
< num_points
-1; i
++) {
173 real len
= distance_point_point_manhattan(&ps
[i
], &ps
[i
+1]);
174 this_badness
= length_badness(len
);
175 badness
+= this_badness
;
180 /** Adjust one end of an orthconn for gaps */
182 autolayout_adjust_for_gap(Point
*pos
, int dir
, ConnectionPoint
*cp
)
186 /* Do absolute gaps here, once it's defined */
188 if (!connpoint_is_autogap(cp
)) {
194 dir_other
.x
= pos
->x
;
195 dir_other
.y
= pos
->y
;
198 dir_other
.y
+= 2 * (object
->bounding_box
.top
- pos
->y
);
201 dir_other
.y
+= 2 * (object
->bounding_box
.bottom
- pos
->y
);
204 dir_other
.x
+= 2 * (object
->bounding_box
.right
- pos
->x
);
207 dir_other
.x
+= 2 * (object
->bounding_box
.left
- pos
->x
);
210 g_warning("Impossible direction %d\n", dir
);
212 return calculate_object_edge(pos
, &dir_other
, object
);
216 autoroute_layout_parallel(Point
*to
, guint
*num_points
, Point
**points
)
219 if (fabs(to
->x
) > MIN_DIST
) {
220 real top
= MIN(-MIN_DIST
, to
->y
-MIN_DIST
);
222 printf("Doing parallel layout: Wide\n");
225 ps
= g_new0(Point
, *num_points
);
226 /* points[0] is 0,0 */
231 } else if (to
->y
> 0) { /* Close together, end below */
232 real top
= -MIN_DIST
;
233 real off
= to
->x
+MIN_DIST
*(to
->x
>0?1.0:-1.0);
234 real bottom
= to
->y
-MIN_DIST
;
236 printf("Doing parallel layout: Narrow\n");
239 ps
= g_new0(Point
, *num_points
);
240 /* points[0] is 0,0 */
250 real top
= to
->y
-MIN_DIST
;
251 real off
= MIN_DIST
*(to
->x
>0?-1.0:1.0);
252 real bottom
= -MIN_DIST
;
254 printf("Doing parallel layout: Narrow\n");
257 ps
= g_new0(Point
, *num_points
);
258 /* points[0] is 0,0 */
269 return calculate_badness(ps
, *num_points
);
273 autoroute_layout_orthogonal(Point
*to
, int enddir
,
274 guint
*num_points
, Point
**points
)
276 /* This one doesn't consider enddir yet, not more complex layouts. */
278 real dirmult
= (enddir
==DIR_WEST
?1.0:-1.0);
279 if (to
->y
< -MIN_DIST
) {
280 if (dirmult
*to
->x
> MIN_DIST
) {
282 printf("Doing orthogonal layout: Three-way\n");
285 ps
= g_new0(Point
, *num_points
);
286 /* points[0] is 0,0 */
291 if (dirmult
*to
->x
> 0) off
= -dirmult
*MIN_DIST
;
292 else off
= -dirmult
*(MIN_DIST
+fabs(to
->x
));
294 ps
= g_new0(Point
, *num_points
);
303 if (dirmult
*to
->x
> 2*MIN_DIST
) {
306 ps
= g_new0(Point
, *num_points
);
315 if (dirmult
*to
->x
> 0) off
= -dirmult
*MIN_DIST
;
316 else off
= -dirmult
*(MIN_DIST
+fabs(to
->x
));
318 ps
= g_new0(Point
, *num_points
);
328 printf("Doing orthogonal layout\n");
331 return calculate_badness(ps
, *num_points
);
335 autoroute_layout_opposite(Point
*to
, guint
*num_points
, Point
**points
)
338 if (to
->y
< -MIN_DIST
) {
340 ps
= g_new0(Point
, *num_points
);
341 if (fabs(to
->x
) < 0.00000001) {
344 return length_badness(fabs(to
->y
))+2*EXTRA_SEGMENT_BADNESS
;
348 printf("Doing opposite layout: Three-way\n");
350 /* points[0] is 0,0 */
356 return 2*length_badness(fabs(mid
))+2*EXTRA_SEGMENT_BADNESS
;
358 } else if (fabs(to
->x
) > 2*MIN_DIST
) {
361 printf("Doing opposite layout: Doorhanger\n");
364 ps
= g_new0(Point
, *num_points
);
365 /* points[0] is 0,0 */
370 ps
[3].y
= to
->y
+MIN_DIST
;
372 ps
[4].y
= to
->y
+MIN_DIST
;
375 real off
= MIN_DIST
*(to
->x
>0?-1.0:1.0);
377 printf("Doing opposite layout: Overlap\n");
380 ps
= g_new0(Point
, *num_points
);
385 ps
[3].y
= to
->y
+MIN_DIST
;
387 ps
[4].y
= to
->y
+MIN_DIST
;
391 return calculate_badness(ps
, *num_points
);
395 point_rotate_cw(Point
*p
) {
402 point_rotate_ccw(Point
*p
) {
409 point_rotate_180(Point
*p
) {
414 /** Normalizes the directions and points to make startdir be north and
415 * the starting point be 0,0.
416 * Normalized points are put in newstart and newend.
417 * Returns the new enddir.
420 autolayout_normalize_points(guint startdir
, guint enddir
,
421 Point start
, Point end
, Point
*newend
)
423 newend
->x
= end
.x
-start
.x
;
424 newend
->y
= end
.y
-start
.y
;
425 if (startdir
== DIR_NORTH
) {
427 } else if (startdir
== DIR_EAST
) {
428 point_rotate_ccw(newend
);
429 if (enddir
== DIR_NORTH
) return DIR_WEST
;
431 } else if (startdir
== DIR_WEST
) {
432 point_rotate_cw(newend
);
433 if (enddir
== DIR_WEST
) return DIR_NORTH
;
435 } else { /* startdir == DIR_SOUTH */
436 point_rotate_180(newend
);
437 if (enddir
< DIR_SOUTH
) return enddir
*4;
438 else return enddir
/4;
440 /* Insert handling of other stuff here */
444 /** Reverses the normalizing process of autolayout_normalize_points.
445 * Returns the new array of points, freeing the old one if necessary.
448 autolayout_unnormalize_points(guint startdir
,
453 Point
*newpoints
= g_new(Point
, num_points
);
455 if (startdir
== DIR_NORTH
) {
456 for (i
= 0; i
< num_points
; i
++) {
457 newpoints
[i
] = points
[i
];
458 point_add(&newpoints
[i
], &start
);
460 } else if (startdir
== DIR_WEST
) {
461 for (i
= 0; i
< num_points
; i
++) {
462 newpoints
[i
] = points
[i
];
463 point_rotate_ccw(&newpoints
[i
]);
464 point_add(&newpoints
[i
], &start
);
466 } else if (startdir
== DIR_SOUTH
) {
467 for (i
= 0; i
< num_points
; i
++) {
468 newpoints
[i
] = points
[i
];
469 point_rotate_180(&newpoints
[i
]);
470 point_add(&newpoints
[i
], &start
);
472 } else if (startdir
== DIR_EAST
) {
473 for (i
= 0; i
< num_points
; i
++) {
474 newpoints
[i
] = points
[i
];
475 point_rotate_cw(&newpoints
[i
]);
476 point_add(&newpoints
[i
], &start
);