/* This software may only be used by you under license from AT&T Corp. ("AT&T"). A copy of AT&T's Source Code Agreement is available at AT&T's Internet website having the URL: If you received this software without first entering into a license with AT&T, you have an infringing copy of this software and cannot use it without violating AT&T's intellectual property rights. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "neato.h" #include "pathplan.h" #include "vispath.h" #ifndef HAVE_DRAND48 extern double drand48(void); #endif #define P2PF(p, pf) (pf.x = p.x, pf.y = p.y) #define PF2P(pf, p) (p.x = ROUND (pf.x), p.y = ROUND (pf.y)) extern void printvis (vconfig_t *cp); static void place_portlabel (edge_t *e, boolean head_p); static splines *getsplinepoints (edge_t* e); extern int in_poly(Ppoly_t argpoly, Ppoint_t q); static boolean spline_merge(node_t *n) { return FALSE; } static boolean swap_ends_p (edge_t *e) { return FALSE; } static splineInfo sinfo = {swap_ends_p, spline_merge}; void neato_compute_bb(graph_t *g) { node_t *n; edge_t *e; box b,bb; point pt,s2; int i,j; bb.LL = pointof(MAXINT,MAXINT); bb.UR = pointof(-MAXINT,-MAXINT); for (n = agfstnode(g); n; n = agnxtnode(g,n)) { pt = coord(n); s2.x = ND_xsize(n)/2+1; s2.y = ND_ysize(n)/2+1; b.LL = sub_points(pt,s2); b.UR = add_points(pt,s2); bb.LL.x = MIN(bb.LL.x,b.LL.x); bb.LL.y = MIN(bb.LL.y,b.LL.y); bb.UR.x = MAX(bb.UR.x,b.UR.x); bb.UR.y = MAX(bb.UR.y,b.UR.y); for (e = agfstout(g,n); e; e = agnxtout(g,e)) { if (ED_spl(e) == 0) continue; for (i = 0; i < ED_spl(e)->size; i++) { for (j = 0; j < ED_spl(e)->list[i].size; j++) { pt = ED_spl(e)->list[i].list[j]; if (bb.LL.x > pt.x) bb.LL.x = pt.x; if (bb.LL.y > pt.y) bb.LL.y = pt.y; if (bb.UR.x < pt.x) bb.UR.x = pt.x; if (bb.UR.y < pt.y) bb.UR.y = pt.y; } } } } for (i = 1; i <= GD_n_cluster(g); i++) { bb.LL.x = MIN(bb.LL.x,GD_clust(g)[i]->u.bb.LL.x); bb.LL.y = MIN(bb.LL.y,GD_clust(g)[i]->u.bb.LL.y); bb.UR.x = MAX(bb.UR.x,GD_clust(g)[i]->u.bb.UR.x); bb.UR.y = MAX(bb.UR.y,GD_clust(g)[i]->u.bb.UR.y); } GD_bb(g) = bb; } static Ppoint_t mkPoint(point p) {Ppoint_t rv; rv.x = p.x; rv.y = p.y; return rv;} static void make_barriers(Ppoly_t **poly, int npoly, int pp, int qp, Pedge_t **barriers, int *n_barriers){ int i, j, k, n, b; Pedge_t *bar; n = 0; for (i = 0; i < npoly; i++) { if (i == pp) continue; if (i == qp) continue; n = n + poly[i]->pn; } bar = N_GNEW(n, Pedge_t); b = 0; for (i = 0; i < npoly; i++) { if (i == pp) continue; if (i == qp) continue; for (j = 0; j < poly[i]->pn; j++) { k = j + 1; if (k >= poly[i]->pn) k = 0; bar[b].a = poly[i]->ps[j]; bar[b].b = poly[i]->ps[k]; b++; } } assert(b == n); *barriers = bar; *n_barriers = n; } extern int Plegal_arrangement( Ppoly_t **polys, int n_polys); /* recPt: */ static Ppoint_t recPt (double x, double y, point c, double sep) { Ppoint_t p; p.x = (x * sep) + c.x; p.y = (y * sep) + c.y; return p; } /* spline_edges0: * Main body for constructing edges. * Assumes u.bb for has been computed for g and all clusters * (not just top-level clusters), and * that GD_bb(g).LL is at the origin. * * This last criterion is, I believe, mainly to simplify the code * in neato_set_aspect. It would be good to remove this constraint, * as this would allow nodes pinned on input to have the same coordinates * when output in dot or plain format. * * As a side-effect, this function sets the u.coord attribute of nodes * from the u.pos attribute. This is needed for output. In addition, * it guarantees that all bounding * boxes are current; in particular, the bounding box of g reflects the * addition of edges. NOTE: intra-cluster edges are not constrained to * remain in the cluster's bounding box and, conversely, a cluster's box * is not altered to reflect intra-cluster edges. * * Note that, currently, edge labels are not used in bounding box * calculations. */ void spline_edges0(graph_t *g) { node_t *n; edge_t *e; point dumb[4],d,ld; pointf polyp; Ppoly_t **obs; polygon_t *poly; int i=0, j, npoly, sides; extern void poly_init(node_t *); Ppoint_t p,q; vconfig_t *vconfig; Pedge_t *barriers; double adj=0.0, SEP; char *str; if ((str = agget(g,"sep"))) {SEP = 1.0 + atof(str);} else SEP = 1.01; neato_set_aspect(g); /* build configuration */ if (mapbool(agget(g,"splines"))) { obs = N_NEW (agnnodes(g), Ppoly_t*); for (n = agfstnode(g); n; n = agnxtnode(g,n)) { ND_coord_i(n).x = POINTS(ND_pos(n)[0]); ND_coord_i(n).y = POINTS(ND_pos(n)[1]); if (ND_shape(n)->initfn == poly_init) { obs[i] = NEW(Ppoly_t); poly = (polygon_t*) ND_shape_info(n); if (poly->sides >= 3) { sides = poly->sides; } else { /* ellipse */ sides = 8; adj = drand48() * .01; } obs[i]->pn = sides; obs[i]->ps = N_NEW(sides, Ppoint_t); /* assuming polys are in CCW order, and pathplan needs CW */ for (j = 0; j < sides; j++) { if (poly->sides >= 3) { polyp.x = POINTS(poly->vertices[j].x) * SEP; polyp.y = POINTS(poly->vertices[j].y) * SEP; } else { double c, s; c = cos(2.0 * PI * j / sides + adj); s = sin(2.0 * PI * j / sides + adj); polyp.x = SEP * c * (ND_lw_i(n) + ND_rw_i(n)) / 2.0; polyp.y = SEP * s * ND_ht_i(n) / 2.0; } obs[i]->ps[sides - j - 1].x = polyp.x + ND_coord_i(n).x; obs[i]->ps[sides - j - 1].y = polyp.y + ND_coord_i(n).y; } i++; } else if (ND_shape(n)->initfn == record_init) { box b; point pt; field_t *fld = (field_t*) ND_shape_info(n); b = fld->b; obs[i] = NEW(Ppoly_t); obs[i]->pn = 4; obs[i]->ps = N_NEW(4, Ppoint_t); /* CW order */ pt = ND_coord_i(n); obs[i]->ps[0] = recPt (b.LL.x,b.LL.y, pt, SEP); obs[i]->ps[1] = recPt (b.LL.x,b.UR.y, pt, SEP); obs[i]->ps[2] = recPt (b.UR.x,b.UR.y, pt, SEP); obs[i]->ps[3] = recPt (b.UR.x,b.LL.y, pt, SEP); i++; } } } else { obs = 0; for (n = agfstnode(g); n; n = agnxtnode(g,n)) { ND_coord_i(n).x = POINTS(ND_pos(n)[0]); ND_coord_i(n).y = POINTS(ND_pos(n)[1]); } } npoly = i; if (obs && NOT(Plegal_arrangement(obs,npoly))) { if (Verbose) fprintf(stderr,"nodes touch - falling back to straight line edges\n"); vconfig = 0; } else { if (obs) vconfig = Pobsopen(obs,npoly); else vconfig = 0; } /* route edges */ if (Verbose) fprintf(stderr,"Creating edges using %s\n", (vconfig ? "splines" : "line segments")); if (vconfig) { /* path-finding pass */ for (n = agfstnode(g); n; n = agnxtnode(g,n)) { for (e = agfstout(g,n); e; e = agnxtout(g,e)) { Ppolyline_t line; int pp, qp; p = mkPoint(add_points(ND_coord_i(n), ED_tail_port(e).p)); q = mkPoint(add_points(ND_coord_i(e->head), ED_head_port(e).p)); /* determine the polygons (if any) that contain the endpoints */ pp = qp = POLYID_NONE; for (i = 0; i < npoly; i++) { if ((pp == POLYID_NONE) && in_poly(*obs[i], p)) pp = i; if ((qp == POLYID_NONE) && in_poly(*obs[i], q)) qp = i; } /*Pobspath(vconfig, p, POLYID_UNKNOWN, q, POLYID_UNKNOWN, &line);*/ Pobspath(vconfig, p, pp, q, qp, &line); ED_path(e) = line; } } } /* spline-drawing pass */ for (n = agfstnode(g); n; n = agnxtnode(g,n)) { for (e = agfstout(g,n); e; e = agnxtout(g,e)) { node_t* head = e->head; if ((Nop > 1) && ED_spl(e)) { p = mkPoint(add_points(ND_coord_i(n), ED_tail_port(e).p)); q = mkPoint(add_points(ND_coord_i(head), ED_head_port(e).p)); } else if (vconfig && (n != head)) { Ppolyline_t line, spline; Pvector_t slopes[2]; int n_barriers; int pp, qp; point *ispline; line = ED_path(e); p = line.ps[0]; q = line.ps[line.pn-1]; /* determine the polygons (if any) that contain the endpoints */ pp = qp = POLYID_NONE; for (i = 0; i < npoly; i++) { if ((pp == POLYID_NONE) && in_poly(*obs[i], p)) pp = i; if ((qp == POLYID_NONE) && in_poly(*obs[i], q)) qp = i; } make_barriers(obs, npoly, pp, qp, &barriers, &n_barriers); slopes[0].x = slopes[0].y = 0.0; slopes[1].x = slopes[1].y = 0.0; Proutespline (barriers, n_barriers, line, slopes, &spline); /* north why did you ever use int coords */ ispline = N_GNEW(spline.pn,point); for (i = 0; i < spline.pn; i++) { ispline[i].x = ROUND(spline.ps[i].x); ispline[i].y = ROUND(spline.ps[i].y); } if (Verbose > 1) fprintf(stderr,"spline %s %s\n",n->name,head->name); clip_and_install(e,e,ispline,spline.pn,&sinfo); free(ispline); free(barriers); } else { dumb[0] = add_points(ND_coord_i(n), ED_tail_port(e).p); dumb[3] = add_points(ND_coord_i(head), ED_head_port(e).p); p = mkPoint(dumb[0]); q = mkPoint(dumb[3]); if (n != head) { d = sub_points(dumb[3],dumb[0]); d.x = d.x / 3; d.y = d.y / 3; dumb[1] = add_points(dumb[0],d); dumb[2] = sub_points(dumb[3],d); } else { /* self arc */ pointf del; del.x = dumb[0].x - dumb[3].x; del.y = dumb[0].y - dumb[3].y; if ((del.x == 0) && (del.y == 0)) { d.x = ND_rw_i(head) + POINTS(.66 * SEP); d.y = 0; dumb[1] = dumb[2] = add_points(dumb[0],d); dumb[1].y += d.x; dumb[2].y -= d.x; } else { pointf perp, base; double l_del, l_perp, sz, l, L; perp.x = -del.y; perp.y = del.x; l_del = sqrt(del.x*del.x + del.y*del.y); l_perp = sqrt(perp.x*perp.x + perp.y*perp.y); if (abs(del.y) <= abs(del.x)) { /* horizontal */ sz = ND_ht_i(head)/2.0; if (del.y >= ND_coord_i(n).y) { if (perp.y < 0) { perp.y *= -1; perp.x *= -1; } } else { if (perp.y > 0) { perp.y *= -1; perp.x *= -1; } } } else { /* vertical */ sz = ND_rw_i(head); if (del.x >= ND_coord_i(n).x) { if (perp.x < 0) { perp.x *= -1; perp.y *= -1; } } else { if (perp.x > 0) { perp.y *= -1; perp.x *= -1; } } } l = sz + POINTS(.66 * SEP); L = l/l_del + 0.5; base.x = dumb[3].x + (del.x/2) + (l*perp.x)/l_perp; base.y = dumb[3].y + (del.y/2) + (l*perp.y)/l_perp; dumb[1].x = base.x + L*del.x; dumb[1].y = base.y + L*del.y; dumb[2].x = base.x - L*del.x; dumb[2].y = base.y - L*del.y; } } clip_and_install(e,e,dumb,4,&sinfo); } /* This can only hope to work for straight edges. * FIX for loops and curves; also if nop > 1, use * label position if provided */ if (ED_label(e) && !ED_label(e)->set) { d.x = (q.x + p.x)/ 2; d.y = (p.y + q.y)/ 2; if (abs(p.x - q.x) > abs(p.y - q.y)) { ld.x = 0; ld.y = POINTS(ED_label(e)->dimen.y)/2 + 2; } else { ld.x = POINTS(ED_label(e)->dimen.y)/2+ED_label(e)->fontsize; ld.y = 0; } d = add_points(d,ld); ED_label(e)->p = d; ED_label(e)->set = TRUE; } } } /* GD_bb(g).UR = sub_points(GD_bb(g).UR,GD_bb(g).LL); */ /* GD_bb(g).LL = pointof(0,0); */ /* We do not need to recompute the bounding box here because * it was computed before entry, and a side-effect of adding edges * with clip_and_install is to update the bounding box. */ /* neato_compute_bb(g); */ /* vladimir: place port labels */ if (E_headlabel || E_taillabel) for (n = agfstnode(g); n; n = agnxtnode(g,n)) { if (E_headlabel) for (e = agfstin(g,n); e; e = agnxtin(g,e)) if (ED_head_label(e)) place_portlabel (e, TRUE); if (E_taillabel) for (e = agfstout(g,n); e; e = agnxtout(g,e)) if (ED_tail_label(e)) place_portlabel (e, FALSE); } /* end vladimir */ State = GVSPLINES; } /* spline_edges: * Construct all edges. We assume the graph * has no clusters, and only nodes have been * positioned. */ void spline_edges(graph_t *g) { node_t *n; pointf offset; neato_compute_bb(g); offset = cvt2ptf(GD_bb(g).LL); for (n = agfstnode(g); n; n = agnxtnode(g,n)) { ND_pos(n)[0] -= offset.x; ND_pos(n)[1] -= offset.y; } GD_bb(g).UR.x -= GD_bb(g).LL.x; GD_bb(g).UR.y -= GD_bb(g).LL.y; GD_bb(g).LL.x = 0; GD_bb(g).LL.y = 0; spline_edges0(g); } /* scaleEdge: * Scale edge by given factor. * Assume ED_spl != NULL. */ static void scaleEdge(edge_t *e, double xf, double yf) { int i, j; point* pt; bezier* bez; bez = ED_spl(e)->list; for (i = 0; i < ED_spl(e)->size; i++) { pt = bez->list; for (j = 0; j < bez->size; j++) { pt->x *= xf; pt->y *= yf; pt++; } if (bez->sflag) { bez->sp.x *= xf; bez->sp.y *= yf; } if (bez->eflag) { bez->ep.x *= xf; bez->ep.y *= yf; } bez++; } if (ED_label(e) && ED_label(e)->set) { ED_label(e)->p.x *= xf; ED_label(e)->p.y *= yf; } if (ED_head_label(e) && ED_head_label(e)->set) { ED_head_label(e)->p.x *= xf; ED_head_label(e)->p.y *= yf; } if (ED_tail_label(e) && ED_tail_label(e)->set) { ED_tail_label(e)->p.x *= xf; ED_tail_label(e)->p.y *= yf; } } /* scaleBB: * Scale bounding box of clusters of g by given factors. */ static void scaleBB(graph_t *g, double xf, double yf) { int i; GD_bb(g).UR.x *= xf; GD_bb(g).UR.y *= yf; GD_bb(g).LL.x *= xf; GD_bb(g).LL.y *= yf; if (GD_label(g) && GD_label(g)->set) { GD_label(g)->p.x *= xf; GD_label(g)->p.y *= yf; } for (i = 1; i <= GD_n_cluster(g); i++) scaleBB(GD_clust(g)[i],xf,yf); } /* neato_set_aspect; * Assume all bounding boxes are correct and * that GD_bb(g).LL is at origin. */ void neato_set_aspect(graph_t *g) { /* int i; */ double xf,yf,actual,desired; char *str; node_t *n; /* neato_compute_bb(g); */ if (/* (GD_maxrank(g) > 0) && */(str = agget(g,"ratio"))) { /* normalize */ assert (GD_bb(g).LL.x == 0); assert (GD_bb(g).LL.y == 0); /* GD_bb(g).UR.x -= GD_bb(g).LL.x; */ /* GD_bb(g).UR.y -= GD_bb(g).LL.y; */ if (GD_left_to_right(g)) {int t = GD_bb(g).UR.x; GD_bb(g).UR.x = GD_bb(g).UR.y; GD_bb(g).UR.y = t;} if (strcmp(str,"fill") == 0) { /* fill is weird because both X and Y can stretch */ if (GD_drawing(g)->size.x <= 0) return; xf = (double)GD_drawing(g)->size.x / (double)GD_bb(g).UR.x; yf = (double)GD_drawing(g)->size.y / (double)GD_bb(g).UR.y; if ((xf < 1.0) || (yf < 1.0)) { if (xf < yf) {yf = yf / xf; xf = 1.0;} else {xf = xf / yf; yf = 1.0;} } } else { desired = atof(str); if (desired == 0.0) return; actual = ((double)GD_bb(g).UR.y)/((double)GD_bb(g).UR.x); if (actual < desired) {yf = desired/actual; xf = 1.0;} else {xf = actual/desired; yf = 1.0;} } if (GD_left_to_right(g)) {double t = xf; xf = yf; yf = t;} /* Not relying on neato_nlist here allows us not to have to * allocate it in the root graph and the connected components. */ for (n = agfstnode(g); n; n = agnxtnode(g,n)) { /* for (i = 0; (n = GD_neato_nlist(g)[i]); i++) { */ ND_pos(n)[0] = ND_pos(n)[0] * xf; ND_pos(n)[1] = ND_pos(n)[1] * yf; } scaleBB (g, xf, yf); if (Nop > 1) { edge_t* e; for (n = agfstnode(g); n; n = agnxtnode(g,n)) { for (e = agfstout(g,n); e; e = agnxtout(g,e)) if (ED_spl(e)) scaleEdge (e, xf, yf); } } } } /* vladimir */ static void place_portlabel (edge_t *e, boolean head_p) /* place the {head,tail}label (depending on HEAD_P) of edge E */ { textlabel_t *l; splines *spl; bezier *bez; double dist, angle; point p; pointf c[4], pf; int i; l = head_p ? ED_head_label(e) : ED_tail_label(e); if (swap_ends_p(e)) head_p = !head_p; spl = getsplinepoints(e); if (!head_p) { bez = &spl->list[0]; if (bez->sflag != ARR_NONE && bez->sflag != ARR_OPEN && bez->sflag != ARR_HALFOPEN) { p = bez->sp; P2PF(bez->list[0],pf); } else { p = bez->list[0]; for (i=0; i<4; i++) P2PF(bez->list[i], c[i]); pf = Bezier (c, 3, 0.1, NULL, NULL); } } else { bez = &spl->list[spl->size-1]; if (bez->eflag != ARR_NONE && bez->eflag != ARR_OPEN && bez->eflag != ARR_HALFOPEN) { p = bez->ep; P2PF(bez->list[bez->size-1],pf); } else { p = bez->list[bez->size-1]; for (i=0; i<4; i++) P2PF(bez->list[bez->size-4+i], c[i]); pf = Bezier (c, 3, 0.9, NULL, NULL); } } angle = atan2 (pf.y-p.y, pf.x-p.x) + RADIANS(late_double(e,E_labelangle,PORT_LABEL_ANGLE,-180.0)); dist = PORT_LABEL_DISTANCE * late_double(e,E_labeldistance,1.0,0.0); l->p.x = p.x + ROUND(dist * cos(angle)); l->p.y = p.y + ROUND(dist * sin(angle)); l->set = TRUE; } static splines *getsplinepoints (edge_t* e) { splines *sp; sp = ED_spl(e); if (sp == NULL) abort (); return sp; }