Easier adjusting of layouts
[dia.git] / lib / autoroute.c
blob437c076051fcbf54281994fc3a65d458010a0f72
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.
22 #include "config.h"
24 #include "object.h"
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. */
31 #define MIN_DIST 1.0
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,
42 int enddir,
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,
49 Point *newend);
50 static Point *autolayout_unnormalize_points(guint dir,
51 Point start,
52 Point *points,
53 guint num_points);
55 /** Calculate a 'pleasing' route between two connection points.
56 * If a good route is found, updates the given OrthConn with the values
57 * and returns TRUE.
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.
67 gboolean
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;
74 int startdir, enddir;
76 int fromdir, todir;
77 Point frompos, topos;
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;
88 topos = endconn->pos;
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) &&
95 (todir & enddir)) {
96 real this_badness;
97 Point *this_layout = NULL;
98 guint this_num_points;
99 guint normal_enddir;
100 Point startpoint, endpoint;
101 Point otherpoint;
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",
106 startdir, enddir,
107 frompos.x, frompos.y,
108 topos.x, topos.y,
109 startpoint.x, startpoint.y,
110 endpoint.x, endpoint.y);
112 normal_enddir = autolayout_normalize_points(startdir, enddir,
113 startpoint, endpoint,
114 &otherpoint);
115 if (normal_enddir == DIR_NORTH ) {
116 this_badness = autoroute_layout_parallel(&otherpoint,
117 &this_num_points,
118 &this_layout);
119 } else if (normal_enddir == DIR_SOUTH) {
120 this_badness = autoroute_layout_opposite(&otherpoint,
121 &this_num_points,
122 &this_layout);
123 } else {
124 this_badness = autoroute_layout_orthogonal(&otherpoint,
125 normal_enddir,
126 &this_num_points,
127 &this_layout);
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,
138 this_layout,
139 this_num_points);
140 best_num_points = this_num_points;
141 } else {
142 g_free(this_layout);
149 if (min_badness < MAX_BADNESS) {
150 orthconn_set_points(conn, best_num_points, best_layout);
151 return TRUE;
152 } else {
153 g_free(best_layout);
154 return FALSE;
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.
163 static real
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;
169 } else {
170 return len-MIN_DIST;
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.
181 static real
182 calculate_badness(Point *ps, guint num_points)
184 real badness;
185 guint i;
186 badness = (num_points-1)*EXTRA_SEGMENT_BADNESS;
187 for (i = 0; i < num_points-1; i++) {
188 real this_badness;
189 real len = distance_point_point_manhattan(&ps[i], &ps[i+1]);
190 this_badness = length_badness(len);
191 badness += this_badness;
193 return 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.
203 static Point
204 autolayout_adjust_for_gap(Point *pos, int dir, ConnectionPoint *cp)
206 DiaObject *object;
207 Point dir_other;
208 /* Do absolute gaps here, once it's defined */
210 if (!connpoint_is_autogap(cp)) {
211 return *pos;
214 object = cp->object;
216 dir_other.x = pos->x;
217 dir_other.y = pos->y;
218 switch (dir) {
219 case DIR_NORTH:
220 dir_other.y += 2 * (object->bounding_box.top - pos->y);
221 break;
222 case DIR_SOUTH:
223 dir_other.y += 2 * (object->bounding_box.bottom - pos->y);
224 break;
225 case DIR_EAST:
226 dir_other.x += 2 * (object->bounding_box.right - pos->x);
227 break;
228 case DIR_WEST:
229 dir_other.x += 2 * (object->bounding_box.left - pos->x);
230 break;
231 default:
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.
246 static real
247 autoroute_layout_parallel(Point *to, guint *num_points, Point **points)
249 Point *ps = NULL;
250 if (fabs(to->x) > MIN_DIST) {
251 real top = MIN(-MIN_DIST, to->y-MIN_DIST);
253 printf("Doing parallel layout: Wide\n");
255 *num_points = 4;
256 ps = g_new0(Point, *num_points);
257 /* points[0] is 0,0 */
258 ps[1].y = top;
259 ps[2].x = to->x;
260 ps[2].y = top;
261 ps[3] = *to;
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");
269 *num_points = 6;
270 ps = g_new0(Point, *num_points);
271 /* points[0] is 0,0 */
272 ps[1].y = top;
273 ps[2].x = off;
274 ps[2].y = top;
275 ps[3].x = off;
276 ps[3].y = bottom;
277 ps[4].x = to->x;
278 ps[4].y = bottom;
279 ps[5] = *to;
280 } else {
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");
287 *num_points = 6;
288 ps = g_new0(Point, *num_points);
289 /* points[0] is 0,0 */
290 ps[1].y = bottom;
291 ps[2].x = off;
292 ps[2].y = bottom;
293 ps[3].x = off;
294 ps[3].y = top;
295 ps[4].x = to->x;
296 ps[4].y = top;
297 ps[5] = *to;
299 *points = ps;
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.
313 static real
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. */
318 Point *ps = NULL;
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");
325 *num_points = 3;
326 ps = g_new0(Point, *num_points);
327 /* points[0] is 0,0 */
328 ps[1].y = to->y;
329 ps[2] = *to;
330 } else {
331 real off;
332 if (dirmult*to->x > 0) off = -dirmult*MIN_DIST;
333 else off = -dirmult*(MIN_DIST+fabs(to->x));
334 *num_points = 5;
335 ps = g_new0(Point, *num_points);
336 ps[1].y = -MIN_DIST;
337 ps[2].x = off;
338 ps[2].y = -MIN_DIST;
339 ps[3].x = off;
340 ps[3].y = to->y;
341 ps[4] = *to;
343 } else {
344 if (dirmult*to->x > 2*MIN_DIST) {
345 real mid = to->x/2;
346 *num_points = 5;
347 ps = g_new0(Point, *num_points);
348 ps[1].y = -MIN_DIST;
349 ps[2].x = mid;
350 ps[2].y = -MIN_DIST;
351 ps[3].x = mid;
352 ps[3].y = to->y;
353 ps[4] = *to;
354 } else {
355 real off;
356 if (dirmult*to->x > 0) off = -dirmult*MIN_DIST;
357 else off = -dirmult*(MIN_DIST+fabs(to->x));
358 *num_points = 5;
359 ps = g_new0(Point, *num_points);
360 ps[1].y = -MIN_DIST;
361 ps[2].x = off;
362 ps[2].y = -MIN_DIST;
363 ps[3].x = off;
364 ps[3].y = to->y;
365 ps[4] = *to;
369 printf("Doing orthogonal layout\n");
371 *points = ps;
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
377 * a spiral.
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.
384 static real
385 autoroute_layout_opposite(Point *to, guint *num_points, Point **points)
387 Point *ps = NULL;
388 if (to->y < -MIN_DIST) {
389 *num_points = 4;
390 ps = g_new0(Point, *num_points);
391 if (fabs(to->x) < 0.00000001) {
392 ps[2] = ps[3] = *to;
393 *points = ps;
394 return length_badness(fabs(to->y))+2*EXTRA_SEGMENT_BADNESS;
395 } else {
396 real mid = to->y/2;
398 printf("Doing opposite layout: Three-way\n");
400 /* points[0] is 0,0 */
401 ps[1].y = mid;
402 ps[2].x = to->x;
403 ps[2].y = mid;
404 ps[3] = *to;
405 *points = ps;
406 return 2*length_badness(fabs(mid))+2*EXTRA_SEGMENT_BADNESS;
408 } else if (fabs(to->x) > 2*MIN_DIST) {
409 real mid = to->x/2;
411 printf("Doing opposite layout: Doorhanger\n");
413 *num_points = 6;
414 ps = g_new0(Point, *num_points);
415 /* points[0] is 0,0 */
416 ps[1].y = -MIN_DIST;
417 ps[2].x = mid;
418 ps[2].y = -MIN_DIST;
419 ps[3].x = mid;
420 ps[3].y = to->y+MIN_DIST;
421 ps[4].x = to->x;
422 ps[4].y = to->y+MIN_DIST;
423 ps[5] = *to;
424 } else {
425 real off = MIN_DIST*(to->x>0?-1.0:1.0);
427 printf("Doing opposite layout: Overlap\n");
429 *num_points = 6;
430 ps = g_new0(Point, *num_points);
431 ps[1].y = -MIN_DIST;
432 ps[2].x = off;
433 ps[2].y = -MIN_DIST;
434 ps[3].x = off;
435 ps[3].y = to->y+MIN_DIST;
436 ps[4].x = to->x;
437 ps[4].y = to->y+MIN_DIST;
438 ps[5] = *to;
440 *points = ps;
441 return calculate_badness(ps, *num_points);
444 /** Rotate a point clockwise.
445 * @param p The point to rotate.
447 static void
448 point_rotate_cw(Point *p)
450 real tmp = p->x;
451 p->x = -p->y;
452 p->y = tmp;
455 /** Rotate a point counterclockwise.
456 * @param p The point to rotate.
458 static void
459 point_rotate_ccw(Point *p)
461 real tmp = p->x;
462 p->x = p->y;
463 p->y = -tmp;
466 /** Rotate a point 180 degrees.
467 * @param p The point to rotate.
469 static void
470 point_rotate_180(Point *p)
472 p->x = -p->x;
473 p->y = -p->y;
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.
485 static guint
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) {
492 return enddir;
493 } else if (startdir == DIR_EAST) {
494 point_rotate_ccw(newend);
495 if (enddir == DIR_NORTH) return DIR_WEST;
496 return enddir/2;
497 } else if (startdir == DIR_WEST) {
498 point_rotate_cw(newend);
499 if (enddir == DIR_WEST) return DIR_NORTH;
500 return enddir*2;
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 */
507 return enddir;
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'.
521 static Point *
522 autolayout_unnormalize_points(guint startdir,
523 Point start,
524 Point *points,
525 guint num_points)
527 Point *newpoints = g_new(Point, num_points);
528 guint i;
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);
553 g_free(points);
554 return newpoints;