123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020 |
- /// @file
- /// @ingroup common_render
- /*************************************************************************
- * Copyright (c) 2011 AT&T Intellectual Property
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors: Details at https://graphviz.org
- *************************************************************************/
- #include "config.h"
- #include <assert.h>
- #include <cgraph/gv_math.h>
- #include <cgraph/list.h>
- #include <common/geomprocs.h>
- #include <common/render.h>
- #include <float.h>
- #include <limits.h>
- #include <math.h>
- #include <pathplan/pathplan.h>
- #include <stdbool.h>
- #include <stdint.h>
- #include <stdlib.h>
- #include <string.h>
- #include <util/agxbuf.h>
- #include <util/alloc.h>
- #include <util/prisize_t.h>
- static int nedges; ///< total no. of edges used in routing
- static size_t nboxes; ///< total no. of boxes used in routing
- static int routeinit;
- static int checkpath(size_t, boxf *, path *);
- static void printpath(path * pp);
- #ifdef DEBUG
- static void printboxes(size_t boxn, boxf *boxes) {
- pointf ll, ur;
- for (size_t bi = 0; bi < boxn; bi++) {
- ll = boxes[bi].LL, ur = boxes[bi].UR;
- agxbuf buf = {0};
- agxbprint(&buf, "%.0f %.0f %.0f %.0f pathbox", ll.x, ll.y, ur.x, ur.y);
- show_boxes_append(&Show_boxes, agxbdisown(&buf));
- }
- }
- #if DEBUG > 1
- static void psprintpolypts(Ppoint_t * p, int sz)
- {
- int i;
- fprintf(stderr, "%%!\n");
- fprintf(stderr, "%% constraint poly\n");
- fprintf(stderr, "newpath\n");
- for (i = 0; i < sz; i++)
- fprintf(stderr, "%f %f %s\n", p[i].x, p[i].y, i == 0 ? "moveto" : "lineto");
- fprintf(stderr, "closepath stroke\n");
- }
- static void psprintpoint(point p)
- {
- fprintf(stderr, "gsave\n");
- fprintf(stderr,
- "newpath %d %d moveto %d %d 2 0 360 arc closepath fill stroke\n",
- p.x, p.y, p.x, p.y);
- fprintf(stderr, "/Times-Roman findfont 4 scalefont setfont\n");
- fprintf(stderr, "%d %d moveto (\\(%d,%d\\)) show\n", p.x + 5, p.y + 5,
- p.x, p.y);
- fprintf(stderr, "grestore\n");
- }
- static void psprintpointf(pointf p)
- {
- fprintf(stderr, "gsave\n");
- fprintf(stderr,
- "newpath %.5g %.5g moveto %.5g %.5g 2 0 360 arc closepath fill stroke\n",
- p.x, p.y, p.x, p.y);
- fprintf(stderr, "/Times-Roman findfont 4 scalefont setfont\n");
- fprintf(stderr, "%.5g %.5g moveto (\\(%.5g,%.5g\\)) show\n", p.x + 5, p.y + 5,
- p.x, p.y);
- fprintf(stderr, "grestore\n");
- }
- #endif
- static void psprintspline(Ppolyline_t spl)
- {
- show_boxes_append(&Show_boxes, gv_strdup("%%!"));
- show_boxes_append(&Show_boxes, gv_strdup("%% spline"));
- show_boxes_append(&Show_boxes, gv_strdup("gsave 1 0 0 setrgbcolor newpath"));
- for (size_t i = 0; i < spl.pn; i++) {
- agxbuf buf = {0};
- agxbprint(&buf, "%f %f %s", spl.ps[i].x, spl.ps[i].y,
- i == 0 ? "moveto" : (i % 3 == 0 ? "curveto" : ""));
- show_boxes_append(&Show_boxes, agxbdisown(&buf));
- }
- show_boxes_append(&Show_boxes, gv_strdup("stroke grestore"));
- }
- static void psprintline(Ppolyline_t pl)
- {
- show_boxes_append(&Show_boxes, gv_strdup("%%!"));
- show_boxes_append(&Show_boxes, gv_strdup("%% line"));
- show_boxes_append(&Show_boxes, gv_strdup("gsave 0 0 1 setrgbcolor newpath"));
- for (size_t i = 0; i < pl.pn; i++) {
- agxbuf buf = {0};
- agxbprint(&buf, "%f %f %s", pl.ps[i].x, pl.ps[i].y,
- i == 0 ? "moveto" : "lineto");
- show_boxes_append(&Show_boxes, agxbdisown(&buf));
- }
- show_boxes_append(&Show_boxes, gv_strdup("stroke grestore"));
- }
- static void psprintpoly(Ppoly_t p)
- {
- char* pfx;
- show_boxes_append(&Show_boxes, gv_strdup("%% poly list"));
- show_boxes_append(&Show_boxes, gv_strdup("gsave 0 1 0 setrgbcolor"));
- for (size_t bi = 0; bi < p.pn; bi++) {
- const pointf tail = p.ps[bi];
- const pointf head = p.ps[(bi + 1) % p.pn];
- if (fabs(tail.x - head.x) < 1 && fabs(tail.y - head.y) < 1) pfx = "%%";
- else pfx ="";
- agxbuf buf = {0};
- agxbprint(&buf, "%s%.0f %.0f %.0f %.0f makevec", pfx, tail.x, tail.y, head.x,
- head.y);
- show_boxes_append(&Show_boxes, agxbdisown(&buf));
- }
- show_boxes_append(&Show_boxes, gv_strdup("grestore"));
- }
- static void psprintboxes(size_t boxn, boxf *boxes) {
- pointf ll, ur;
- show_boxes_append(&Show_boxes, gv_strdup("%% box list"));
- show_boxes_append(&Show_boxes, gv_strdup("gsave 0 1 0 setrgbcolor"));
- for (size_t bi = 0; bi < boxn; bi++) {
- ll = boxes[bi].LL, ur = boxes[bi].UR;
- agxbuf buf = {0};
- agxbprint(&buf, "newpath\n%.0f %.0f moveto", ll.x, ll.y);
- show_boxes_append(&Show_boxes, agxbdisown(&buf));
- agxbprint(&buf, "%.0f %.0f lineto", ll.x, ur.y);
- show_boxes_append(&Show_boxes, agxbdisown(&buf));
- agxbprint(&buf, "%.0f %.0f lineto", ur.x, ur.y);
- show_boxes_append(&Show_boxes, agxbdisown(&buf));
- agxbprint(&buf, "%.0f %.0f lineto", ur.x, ll.y);
- show_boxes_append(&Show_boxes, agxbdisown(&buf));
- show_boxes_append(&Show_boxes, gv_strdup("closepath stroke"));
- }
- show_boxes_append(&Show_boxes, gv_strdup("grestore"));
- }
- static void psprintinit (int begin)
- {
- if (begin)
- show_boxes_append(&Show_boxes, gv_strdup("dbgstart"));
- else
- show_boxes_append(&Show_boxes, gv_strdup("grestore"));
- }
- static bool debugleveln(edge_t* realedge, int i)
- {
- return GD_showboxes(agraphof(aghead(realedge))) == i ||
- GD_showboxes(agraphof(agtail(realedge))) == i ||
- ED_showboxes(realedge) == i ||
- ND_showboxes(aghead(realedge)) == i ||
- ND_showboxes(agtail(realedge)) == i;
- }
- #endif /* DEBUG */
- /// Given a simple (ccw) polygon, route an edge from tp to hp.
- pointf *simpleSplineRoute(pointf tp, pointf hp, Ppoly_t poly, size_t *n_spl_pts,
- int polyline) {
- Ppolyline_t pl, spl;
- Ppoint_t eps[2];
- Pvector_t evs[2];
- eps[0].x = tp.x;
- eps[0].y = tp.y;
- eps[1].x = hp.x;
- eps[1].y = hp.y;
- if (Pshortestpath(&poly, eps, &pl) < 0)
- return NULL;
- if (polyline)
- make_polyline (pl, &spl);
- else {
- // polygon edges passed to Proutespline
- Pedge_t *edges = gv_calloc(poly.pn, sizeof(Pedge_t));
- for (size_t i = 0; i < poly.pn; i++) {
- edges[i].a = poly.ps[i];
- edges[i].b = poly.ps[(i + 1) % poly.pn];
- }
- evs[0].x = evs[0].y = 0;
- evs[1].x = evs[1].y = 0;
- if (Proutespline(edges, poly.pn, pl, evs, &spl) < 0) {
- free(edges);
- return NULL;
- }
- free(edges);
- }
- pointf *ps = calloc(spl.pn, sizeof(ps[0]));
- if (ps == NULL) {
- agerrorf("cannot allocate ps\n");
- return NULL;
- }
- for (size_t i = 0; i < spl.pn; i++) {
- ps[i] = spl.ps[i];
- }
- *n_spl_pts = spl.pn;
- return ps;
- }
- /** Data initialized once until matching call to routeplineterm
- * Allows recursive calls to dot
- */
- int
- routesplinesinit(void)
- {
- if (++routeinit > 1) return 0;
- #ifdef DEBUG
- show_boxes_free(&Show_boxes);
- #endif
- nedges = 0;
- nboxes = 0;
- if (Verbose)
- start_timer();
- return 0;
- }
- void routesplinesterm(void)
- {
- if (--routeinit > 0) return;
- if (Verbose)
- fprintf(stderr,
- "routesplines: %d edges, %" PRISIZE_T " boxes %.2f sec\n",
- nedges, nboxes, elapsed_sec());
- }
- static void limitBoxes(boxf *boxes, size_t boxn, const pointf *pps, size_t pn,
- double delta) {
- double t;
- pointf sp[4];
- const double num_div = delta * (double)boxn;
- for (size_t splinepi = 0; splinepi + 3 < pn; splinepi += 3) {
- for (double si = 0; si <= num_div; si++) {
- t = si / num_div;
- sp[0] = pps[splinepi];
- sp[1] = pps[splinepi + 1];
- sp[2] = pps[splinepi + 2];
- sp[3] = pps[splinepi + 3];
- sp[0].x += t * (sp[1].x - sp[0].x);
- sp[0].y += t * (sp[1].y - sp[0].y);
- sp[1].x += t * (sp[2].x - sp[1].x);
- sp[1].y += t * (sp[2].y - sp[1].y);
- sp[2].x += t * (sp[3].x - sp[2].x);
- sp[2].y += t * (sp[3].y - sp[2].y);
- sp[0].x += t * (sp[1].x - sp[0].x);
- sp[0].y += t * (sp[1].y - sp[0].y);
- sp[1].x += t * (sp[2].x - sp[1].x);
- sp[1].y += t * (sp[2].y - sp[1].y);
- sp[0].x += t * (sp[1].x - sp[0].x);
- sp[0].y += t * (sp[1].y - sp[0].y);
- for (size_t bi = 0; bi < boxn; bi++) {
- /* this tested ok on 64bit machines, but on 32bit we need this FUDGE
- * or graphs/directed/records.gv fails */
- #define FUDGE .0001
- if (sp[0].y <= boxes[bi].UR.y+FUDGE && sp[0].y >= boxes[bi].LL.y-FUDGE) {
- boxes[bi].LL.x = fmin(boxes[bi].LL.x, sp[0].x);
- boxes[bi].UR.x = fmax(boxes[bi].UR.x, sp[0].x);
- }
- }
- }
- }
- }
- #define INIT_DELTA 10
- #define LOOP_TRIES 15 /* number of times to try to limiting boxes to regain space, using smaller divisions */
- /** Route a path using the path info in pp. This includes start and end points
- * plus a collection of contiguous boxes contain the terminal points. The boxes
- * are converted into a containing polygon. A shortest path is constructed within
- * the polygon from between the terminal points. If polyline is true, this path
- * is converted to a spline representation. Otherwise, we call the path planner to
- * convert the polyline into a smooth spline staying within the polygon. In both
- * cases, the function returns an array of the computed control points. The number
- * of these points is given in npoints.
- *
- * Note that the returned points are stored in a single array, so the points must be
- * used before another call to this function.
- *
- * During cleanup, the function determines the x-extent of the spline in the box, so
- * the box can be shrunk to the minimum width. The extra space can then be used by other
- * edges.
- *
- * If a catastrophic error, return NULL and npoints is 0.
- */
- static pointf *routesplines_(path *pp, size_t *npoints, int polyline) {
- Ppoly_t poly;
- Ppolyline_t pl, spl;
- Ppoint_t eps[2];
- Pvector_t evs[2];
- int prev, next;
- boxf *boxes;
- edge_t* realedge;
- bool flip;
- int loopcnt;
- bool unbounded;
- *npoints = 0;
- nedges++;
- nboxes += pp->nbox;
- for (realedge = pp->data;
- realedge && ED_edge_type(realedge) != NORMAL;
- realedge = ED_to_orig(realedge));
- if (!realedge) {
- agerrorf("in routesplines, cannot find NORMAL edge\n");
- return NULL;
- }
- boxes = pp->boxes;
- const size_t boxn = pp->nbox;
- if (checkpath(boxn, boxes, pp))
- return NULL;
- #ifdef DEBUG
- if (debugleveln(realedge, 1))
- printboxes(boxn, boxes);
- if (debugleveln(realedge, 3)) {
- psprintinit(1);
- psprintboxes(boxn, boxes);
- }
- #endif
- // vertices of polygon defined by boxes
- Ppoint_t *polypoints = gv_calloc(boxn * 8, sizeof(Ppoint_t));
- if (boxn > 1 && boxes[0].LL.y > boxes[1].LL.y) {
- flip = true;
- for (size_t bi = 0; bi < boxn; bi++) {
- double v = boxes[bi].UR.y;
- boxes[bi].UR.y = -1*boxes[bi].LL.y;
- boxes[bi].LL.y = -v;
- }
- }
- else flip = false;
- size_t pi;
- if (agtail(realedge) != aghead(realedge)) {
- /* I assume that the path goes either down only or
- up - right - down */
- size_t bi;
- for (bi = 0, pi = 0; bi < boxn; bi++) {
- next = prev = 0;
- if (bi > 0)
- prev = boxes[bi].LL.y > boxes[bi - 1].LL.y ? -1 : 1;
- if (bi + 1 < boxn)
- next = boxes[bi + 1].LL.y > boxes[bi].LL.y ? 1 : -1;
- if (prev != next) {
- if (next == -1 || prev == 1) {
- polypoints[pi].x = boxes[bi].LL.x;
- polypoints[pi++].y = boxes[bi].UR.y;
- polypoints[pi].x = boxes[bi].LL.x;
- polypoints[pi++].y = boxes[bi].LL.y;
- } else {
- polypoints[pi].x = boxes[bi].UR.x;
- polypoints[pi++].y = boxes[bi].LL.y;
- polypoints[pi].x = boxes[bi].UR.x;
- polypoints[pi++].y = boxes[bi].UR.y;
- }
- }
- else if (prev == 0) { /* single box */
- polypoints[pi].x = boxes[bi].LL.x;
- polypoints[pi++].y = boxes[bi].UR.y;
- polypoints[pi].x = boxes[bi].LL.x;
- polypoints[pi++].y = boxes[bi].LL.y;
- }
- else {
- if (!(prev == -1 && next == -1)) {
- free(polypoints);
- agerrorf("in routesplines, illegal values of prev %d and next %d, line %d\n", prev, next, __LINE__);
- return NULL;
- }
- }
- }
- for (bi = boxn - 1; bi != SIZE_MAX; bi--) {
- next = prev = 0;
- if (bi + 1 < boxn)
- prev = boxes[bi].LL.y > boxes[bi + 1].LL.y ? -1 : 1;
- if (bi > 0)
- next = boxes[bi - 1].LL.y > boxes[bi].LL.y ? 1 : -1;
- if (prev != next) {
- if (next == -1 || prev == 1 ) {
- polypoints[pi].x = boxes[bi].LL.x;
- polypoints[pi++].y = boxes[bi].UR.y;
- polypoints[pi].x = boxes[bi].LL.x;
- polypoints[pi++].y = boxes[bi].LL.y;
- } else {
- polypoints[pi].x = boxes[bi].UR.x;
- polypoints[pi++].y = boxes[bi].LL.y;
- polypoints[pi].x = boxes[bi].UR.x;
- polypoints[pi++].y = boxes[bi].UR.y;
- }
- }
- else if (prev == 0) { /* single box */
- polypoints[pi].x = boxes[bi].UR.x;
- polypoints[pi++].y = boxes[bi].LL.y;
- polypoints[pi].x = boxes[bi].UR.x;
- polypoints[pi++].y = boxes[bi].UR.y;
- }
- else {
- if (!(prev == -1 && next == -1)) {
- /* it went badly, e.g. degenerate box in boxlist */
- free(polypoints);
- agerrorf("in routesplines, illegal values of prev %d and next %d, line %d\n", prev, next, __LINE__);
- return NULL; /* for correctness sake, it's best to just stop */
- }
- polypoints[pi].x = boxes[bi].UR.x;
- polypoints[pi++].y = boxes[bi].LL.y;
- polypoints[pi].x = boxes[bi].UR.x;
- polypoints[pi++].y = boxes[bi].UR.y;
- polypoints[pi].x = boxes[bi].LL.x;
- polypoints[pi++].y = boxes[bi].UR.y;
- polypoints[pi].x = boxes[bi].LL.x;
- polypoints[pi++].y = boxes[bi].LL.y;
- }
- }
- }
- else {
- free(polypoints);
- agerrorf("in routesplines, edge is a loop at %s\n", agnameof(aghead(realedge)));
- return NULL;
- }
- if (flip) {
- for (size_t bi = 0; bi < boxn; bi++) {
- double v = boxes[bi].UR.y;
- boxes[bi].UR.y = -1*boxes[bi].LL.y;
- boxes[bi].LL.y = -v;
- }
- for (size_t i = 0; i < pi; i++)
- polypoints[i].y *= -1;
- }
- static const double INITIAL_LLX = DBL_MAX;
- static const double INITIAL_URX = -DBL_MAX;
- for (size_t bi = 0; bi < boxn; bi++) {
- boxes[bi].LL.x = INITIAL_LLX;
- boxes[bi].UR.x = INITIAL_URX;
- }
- poly.ps = polypoints, poly.pn = pi;
- eps[0].x = pp->start.p.x, eps[0].y = pp->start.p.y;
- eps[1].x = pp->end.p.x, eps[1].y = pp->end.p.y;
- if (Pshortestpath(&poly, eps, &pl) < 0) {
- free(polypoints);
- agerrorf("in routesplines, Pshortestpath failed\n");
- return NULL;
- }
- #ifdef DEBUG
- if (debugleveln(realedge, 3)) {
- psprintpoly(poly);
- psprintline(pl);
- }
- #endif
- if (polyline) {
- make_polyline (pl, &spl);
- }
- else {
- Pedge_t *edges = gv_calloc(poly.pn, sizeof(Pedge_t));
- for (size_t edgei = 0; edgei < poly.pn; edgei++) {
- edges[edgei].a = polypoints[edgei];
- edges[edgei].b = polypoints[(edgei + 1) % poly.pn];
- }
- if (pp->start.constrained) {
- evs[0].x = cos(pp->start.theta);
- evs[0].y = sin(pp->start.theta);
- } else
- evs[0].x = evs[0].y = 0;
- if (pp->end.constrained) {
- evs[1].x = -cos(pp->end.theta);
- evs[1].y = -sin(pp->end.theta);
- } else
- evs[1].x = evs[1].y = 0;
- if (Proutespline(edges, poly.pn, pl, evs, &spl) < 0) {
- free(edges);
- free(polypoints);
- agerrorf("in routesplines, Proutespline failed\n");
- return NULL;
- }
- free(edges);
- #ifdef DEBUG
- if (debugleveln(realedge, 3)) {
- psprintspline(spl);
- psprintinit(0);
- }
- #endif
- }
- pointf *ps = calloc(spl.pn, sizeof(ps[0]));
- if (ps == NULL) {
- free(polypoints);
- agerrorf("cannot allocate ps\n");
- return NULL; /* Bailout if no memory left */
- }
- unbounded = true;
- for (size_t splinepi = 0; splinepi < spl.pn; splinepi++) {
- ps[splinepi] = spl.ps[splinepi];
- }
- double delta = INIT_DELTA;
- for (loopcnt = 0; unbounded && loopcnt < LOOP_TRIES; loopcnt++) {
- limitBoxes(boxes, boxn, ps, spl.pn, delta);
- /* The following check is necessary because if a box is not very
- * high, it is possible that the sampling above might miss it.
- * Therefore, we make the sample finer until all boxes have
- * valid values. cf. bug 456.
- */
- size_t bi;
- for (bi = 0; bi < boxn; bi++) {
- /* these fp equality tests are used only to detect if the
- * values have been changed since initialization - ok */
- if (is_exactly_equal(boxes[bi].LL.x, INITIAL_LLX) ||
- is_exactly_equal(boxes[bi].UR.x, INITIAL_URX)) {
- delta *= 2; /* try again with a finer interval */
- break;
- }
- }
- if (bi == boxn)
- unbounded = false;
- }
- if (unbounded) {
- /* Either an extremely short, even degenerate, box, or some failure with the path
- * planner causing the spline to miss some boxes. In any case, use the shortest path
- * to bound the boxes. This will probably mean a bad edge, but we avoid an infinite
- * loop and we can see the bad edge, and even use the showboxes scaffolding.
- */
- Ppolyline_t polyspl;
- agwarningf("Unable to reclaim box space in spline routing for edge \"%s\" -> \"%s\". Something is probably seriously wrong.\n", agnameof(agtail(realedge)), agnameof(aghead(realedge)));
- make_polyline (pl, &polyspl);
- limitBoxes(boxes, boxn, polyspl.ps, polyspl.pn, INIT_DELTA);
- }
- *npoints = spl.pn;
- #ifdef DEBUG
- if (GD_showboxes(agraphof(aghead(realedge))) == 2 ||
- GD_showboxes(agraphof(agtail(realedge))) == 2 ||
- ED_showboxes(realedge) == 2 ||
- ND_showboxes(aghead(realedge)) == 2 ||
- ND_showboxes(agtail(realedge)) == 2)
- printboxes(boxn, boxes);
- #endif
- free(polypoints);
- return ps;
- }
- pointf *routesplines(path *pp, size_t *npoints) {
- return routesplines_(pp, npoints, 0);
- }
- pointf *routepolylines(path *pp, size_t *npoints) {
- return routesplines_(pp, npoints, 1);
- }
- static double overlap(double i0, double i1, double j0, double j1) {
- if (i1 <= j0)
- return 0;
- if (i0 >= j1)
- return 0;
- // does the first interval subsume the second?
- if (i0 <= j0 && i1 >= j1)
- return i1 - i0;
- // does the second interval subsume the first?
- if (j0 <= i0 && j1 >= i1)
- return j1 - j0;
- if (j0 <= i0 && i0 <= j1)
- return j1 - i0;
- assert(j0 <= i1 && i1 <= j1);
- return i1 - j0;
- }
- /*
- * repairs minor errors in the boxpath, such as boxes not joining
- * or slightly intersecting. it's sort of the equivalent of the
- * audit process in the 5E control program - if you've given up on
- * fixing all the bugs, at least try to engineer around them!
- * in postmodern CS, we could call this "self-healing code."
- *
- * Return 1 on failure; 0 on success.
- */
- static int checkpath(size_t boxn, boxf *boxes, path *thepath) {
- boxf *ba, *bb;
- int errs, l, r, d, u;
- /* remove degenerate boxes. */
- size_t i = 0;
- for (size_t bi = 0; bi < boxn; bi++) {
- if (fabs(boxes[bi].LL.y - boxes[bi].UR.y) < .01)
- continue;
- if (fabs(boxes[bi].LL.x - boxes[bi].UR.x) < .01)
- continue;
- boxes[i] = boxes[bi];
- i++;
- }
- boxn = i;
- ba = &boxes[0];
- if (ba->LL.x > ba->UR.x || ba->LL.y > ba->UR.y) {
- agerrorf("in checkpath, box 0 has LL coord > UR coord\n");
- printpath(thepath);
- return 1;
- }
- for (size_t bi = 0; bi + 1 < boxn; bi++) {
- ba = &boxes[bi], bb = &boxes[bi + 1];
- if (bb->LL.x > bb->UR.x || bb->LL.y > bb->UR.y) {
- agerrorf("in checkpath, box %" PRISIZE_T " has LL coord > UR coord\n",
- bi + 1);
- printpath(thepath);
- return 1;
- }
- l = ba->UR.x < bb->LL.x ? 1 : 0;
- r = ba->LL.x > bb->UR.x ? 1 : 0;
- d = ba->UR.y < bb->LL.y ? 1 : 0;
- u = ba->LL.y > bb->UR.y ? 1 : 0;
- errs = l + r + d + u;
- if (errs > 0 && Verbose) {
- fprintf(stderr, "in checkpath, boxes %" PRISIZE_T " and %" PRISIZE_T
- " don't touch\n", bi, bi + 1);
- printpath(thepath);
- }
- if (errs > 0) {
- double xy;
- if (l == 1)
- xy = ba->UR.x, ba->UR.x = bb->LL.x, bb->LL.x = xy, l = 0;
- else if (r == 1)
- xy = ba->LL.x, ba->LL.x = bb->UR.x, bb->UR.x = xy, r = 0;
- else if (d == 1)
- xy = ba->UR.y, ba->UR.y = bb->LL.y, bb->LL.y = xy, d = 0;
- else if (u == 1)
- xy = ba->LL.y, ba->LL.y = bb->UR.y, bb->UR.y = xy, u = 0;
- for (int j = 0; j < errs - 1; j++) {
- if (l == 1)
- xy = (ba->UR.x + bb->LL.x) / 2.0 + 0.5, ba->UR.x =
- bb->LL.x = xy, l = 0;
- else if (r == 1)
- xy = (ba->LL.x + bb->UR.x) / 2.0 + 0.5, ba->LL.x =
- bb->UR.x = xy, r = 0;
- else if (d == 1)
- xy = (ba->UR.y + bb->LL.y) / 2.0 + 0.5, ba->UR.y =
- bb->LL.y = xy, d = 0;
- else if (u == 1)
- xy = (ba->LL.y + bb->UR.y) / 2.0 + 0.5, ba->LL.y =
- bb->UR.y = xy, u = 0;
- }
- }
- /* check for overlapping boxes */
- double xoverlap = overlap(ba->LL.x, ba->UR.x, bb->LL.x, bb->UR.x);
- double yoverlap = overlap(ba->LL.y, ba->UR.y, bb->LL.y, bb->UR.y);
- if (xoverlap > 0 && yoverlap > 0) {
- if (xoverlap < yoverlap) {
- if (ba->UR.x - ba->LL.x > bb->UR.x - bb->LL.x) {
- /* take space from ba */
- if (ba->UR.x < bb->UR.x)
- ba->UR.x = bb->LL.x;
- else
- ba->LL.x = bb->UR.x;
- } else {
- /* take space from bb */
- if (ba->UR.x < bb->UR.x)
- bb->LL.x = ba->UR.x;
- else
- bb->UR.x = ba->LL.x;
- }
- } else { /* symmetric for y coords */
- if (ba->UR.y - ba->LL.y > bb->UR.y - bb->LL.y) {
- /* take space from ba */
- if (ba->UR.y < bb->UR.y)
- ba->UR.y = bb->LL.y;
- else
- ba->LL.y = bb->UR.y;
- } else {
- /* take space from bb */
- if (ba->UR.y < bb->UR.y)
- bb->LL.y = ba->UR.y;
- else
- bb->UR.y = ba->LL.y;
- }
- }
- }
- }
- if (thepath->start.p.x < boxes[0].LL.x
- || thepath->start.p.x > boxes[0].UR.x
- || thepath->start.p.y < boxes[0].LL.y
- || thepath->start.p.y > boxes[0].UR.y) {
- if (Verbose) {
- fprintf(stderr, "in checkpath, start port not in first box\n");
- printpath(thepath);
- }
- thepath->start.p.x = fmax(thepath->start.p.x, boxes[0].LL.x);
- thepath->start.p.x = fmin(thepath->start.p.x, boxes[0].UR.x);
- thepath->start.p.y = fmax(thepath->start.p.y, boxes[0].LL.y);
- thepath->start.p.y = fmin(thepath->start.p.y, boxes[0].UR.y);
- }
- if (thepath->end.p.x < boxes[boxn - 1].LL.x
- || thepath->end.p.x > boxes[boxn - 1].UR.x
- || thepath->end.p.y < boxes[boxn - 1].LL.y
- || thepath->end.p.y > boxes[boxn - 1].UR.y) {
- if (Verbose) {
- fprintf(stderr, "in checkpath, end port not in last box\n");
- printpath(thepath);
- }
- thepath->end.p.x = fmax(thepath->end.p.x, boxes[boxn - 1].LL.x);
- thepath->end.p.x = fmin(thepath->end.p.x, boxes[boxn - 1].UR.x);
- thepath->end.p.y = fmax(thepath->end.p.y, boxes[boxn - 1].LL.y);
- thepath->end.p.y = fmin(thepath->end.p.y, boxes[boxn - 1].UR.y);
- }
- return 0;
- }
- static void printpath(path * pp)
- {
- fprintf(stderr, "%" PRISIZE_T " boxes:\n", pp->nbox);
- for (size_t bi = 0; bi < pp->nbox; bi++)
- fprintf(stderr, "%" PRISIZE_T " (%.5g, %.5g), (%.5g, %.5g)\n", bi,
- pp->boxes[bi].LL.x, pp->boxes[bi].LL.y,
- pp->boxes[bi].UR.x, pp->boxes[bi].UR.y);
- fprintf(stderr, "start port: (%.5g, %.5g), tangent angle: %.5g, %s\n",
- pp->start.p.x, pp->start.p.y, pp->start.theta,
- pp->start.constrained ? "constrained" : "not constrained");
- fprintf(stderr, "end port: (%.5g, %.5g), tangent angle: %.5g, %s\n",
- pp->end.p.x, pp->end.p.y, pp->end.theta,
- pp->end.constrained ? "constrained" : "not constrained");
- }
- static pointf get_centroid(Agraph_t *g)
- {
- pointf sum = {0.0, 0.0};
- sum.x = (GD_bb(g).LL.x + GD_bb(g).UR.x) / 2.0;
- sum.y = (GD_bb(g).LL.y + GD_bb(g).UR.y) / 2.0;
- return sum;
- }
- DEFINE_LIST(nodes, node_t *)
- static void nodes_delete(nodes_t *pvec) {
- if (pvec != NULL) {
- nodes_free(pvec);
- }
- free(pvec);
- }
- DEFINE_LIST_WITH_DTOR(cycles, nodes_t *, nodes_delete)
- static bool cycle_contains_edge(nodes_t *cycle, edge_t *edge) {
- node_t* start = agtail(edge);
- node_t* end = aghead(edge);
- const size_t cycle_len = nodes_size(cycle);
- for (size_t i=0; i < cycle_len; ++i) {
- const node_t *c_start = nodes_get(cycle, i == 0 ? cycle_len - 1 : i - 1);
- const node_t *c_end = nodes_get(cycle, i);
- if (c_start == start && c_end == end)
- return true;
- }
- return false;
- }
- static bool eq(const node_t *a, const node_t *b) { return a == b; }
- static bool is_cycle_unique(cycles_t *cycles, nodes_t *cycle) {
- const size_t cycle_len = nodes_size(cycle);
- size_t i; //node counter
- bool all_items_match;
- for (size_t c = 0; c < cycles_size(cycles); ++c) {
- nodes_t *cur_cycle = cycles_get(cycles, c);
- const size_t cur_cycle_len = nodes_size(cur_cycle);
- //if all the items match in equal length cycles then we're not unique
- if (cur_cycle_len == cycle_len) {
- all_items_match = true;
- for (i=0; i < cur_cycle_len; ++i) {
- node_t *cur_cycle_item = nodes_get(cur_cycle, i);
- if (!nodes_contains(cycle, cur_cycle_item, eq)) {
- all_items_match = false;
- break;
- }
- }
- if (all_items_match)
- return false;
- }
- }
- return true;
- }
- static void dfs(graph_t *g, node_t *search, nodes_t *visited, node_t *end,
- cycles_t *cycles) {
- edge_t* e;
- node_t* n;
- if (nodes_contains(visited, search, eq)) {
- if (search == end) {
- if (is_cycle_unique(cycles, visited)) {
- nodes_t *cycle = gv_alloc(sizeof(nodes_t));
- *cycle = nodes_copy(visited);
- cycles_append(cycles, cycle);
- }
- }
- } else {
- nodes_append(visited, search);
- for (e = agfstout(g, search); e; e = agnxtout(g, e)) {
- n = aghead(e);
- dfs(g, n, visited, end, cycles);
- }
- if (!nodes_is_empty(visited)) {
- (void)nodes_pop_back(visited);
- }
- }
- }
- // Returns a vec of vec of nodes (aka a vector of cycles)
- static cycles_t find_all_cycles(graph_t *g) {
- node_t *n;
- // vector of vectors of nodes -- AKA cycles to delete
- cycles_t alloced_cycles = {0};
- cycles_t cycles = {0}; // vector of vectors of nodes AKA a vector of cycles
- for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
- nodes_t *cycle = gv_alloc(sizeof(nodes_t));
- // keep track of all items we allocate to clean up at the end of this function
- cycles_append(&alloced_cycles, cycle);
-
- dfs(g, n, cycle, n, &cycles);
- }
-
- cycles_free(&alloced_cycles); // cycles contains copied vecs
- return cycles;
- }
- static nodes_t *find_shortest_cycle_with_edge(cycles_t *cycles, edge_t *edge,
- size_t min_size) {
- nodes_t *shortest = NULL;
- for (size_t c = 0; c < cycles_size(cycles); ++c) {
- nodes_t *cycle = cycles_get(cycles, c);
- size_t cycle_len = nodes_size(cycle);
- if (cycle_len < min_size)
- continue;
- if (shortest == NULL || nodes_size(shortest) > cycle_len) {
- if (cycle_contains_edge(cycle, edge)) {
- shortest = cycle;
- }
- }
- }
- return shortest;
- }
- static pointf get_cycle_centroid(graph_t *g, edge_t* edge)
- {
- cycles_t cycles = find_all_cycles(g);
- //find the center of the shortest cycle containing this edge
- //cycles of length 2 do their own thing, we want 3 or
- nodes_t *cycle = find_shortest_cycle_with_edge(&cycles, edge, 3);
- pointf sum = {0.0, 0.0};
- if (cycle == NULL) {
- cycles_free(&cycles);
- return get_centroid(g);
- }
- double cnt = 0;
- for (size_t idx = 0; idx < nodes_size(cycle); ++idx) {
- node_t *n = nodes_get(cycle, idx);
- sum.x += ND_coord(n).x;
- sum.y += ND_coord(n).y;
- cnt++;
- }
- cycles_free(&cycles);
- sum.x /= cnt;
- sum.y /= cnt;
- return sum;
- }
- static void bend(pointf spl[4], pointf centroid)
- {
- pointf a;
- double r;
- pointf midpt = mid_pointf(spl[0], spl[3]);
- double dist = DIST(spl[3], spl[0]);
- r = dist/5.0;
- {
- double vX = centroid.x - midpt.x;
- double vY = centroid.y - midpt.y;
- double magV = hypot(vX, vY);
- if (magV == 0) return; /* if midpoint == centroid, don't divide by zero */
- a.x = midpt.x - vX / magV * r; /* + would be closest point */
- a.y = midpt.y - vY / magV * r;
- }
- /* this can be improved */
- spl[1].x = spl[2].x = a.x;
- spl[1].y = spl[2].y = a.y;
- }
- // FIX: handle ports on boundary?
- void
- makeStraightEdge(graph_t * g, edge_t * e, int et, splineInfo* sinfo)
- {
- edge_t *e0;
- size_t e_cnt = 1;
- e0 = e;
- while (e0 != ED_to_virt(e0) && (e0 = ED_to_virt(e0))) e_cnt++;
- edge_t **edge_list = gv_calloc(e_cnt, sizeof(edge_t *));
- e0 = e;
- for (size_t i = 0; i < e_cnt; i++) {
- edge_list[i] = e0;
- e0 = ED_to_virt(e0);
- }
- assert(e_cnt <= INT_MAX);
- makeStraightEdges(g, edge_list, e_cnt, et, sinfo);
- free(edge_list);
- }
- void makeStraightEdges(graph_t *g, edge_t **edge_list, size_t e_cnt, int et,
- splineInfo *sinfo) {
- pointf dumb[4];
- bool curved = et == EDGETYPE_CURVED;
- pointf del;
- edge_t *e = edge_list[0];
- node_t *n = agtail(e);
- node_t *head = aghead(e);
- dumb[1] = dumb[0] = add_pointf(ND_coord(n), ED_tail_port(e).p);
- dumb[2] = dumb[3] = add_pointf(ND_coord(head), ED_head_port(e).p);
- if (e_cnt == 1 || Concentrate) {
- if (curved) bend(dumb,get_cycle_centroid(g, edge_list[0]));
- clip_and_install(e, aghead(e), dumb, 4, sinfo);
- addEdgeLabels(e);
- return;
- }
- if (APPROXEQPT(dumb[0], dumb[3], MILLIPOINT)) {
- /* degenerate case */
- dumb[1] = dumb[0];
- dumb[2] = dumb[3];
- del.x = 0;
- del.y = 0;
- }
- else {
- pointf perp = {
- .x = dumb[0].y - dumb[3].y,
- .y = dumb[3].x - dumb[0].x
- };
- double l_perp = hypot(perp.x, perp.y);
- int xstep = GD_nodesep(g->root);
- assert(e_cnt - 1 <= INT_MAX);
- int dx = xstep * (int)(e_cnt - 1) / 2;
- dumb[1].x = dumb[0].x + dx * perp.x / l_perp;
- dumb[1].y = dumb[0].y + dx * perp.y / l_perp;
- dumb[2].x = dumb[3].x + dx * perp.x / l_perp;
- dumb[2].y = dumb[3].y + dx * perp.y / l_perp;
- del.x = -xstep * perp.x / l_perp;
- del.y = -xstep * perp.y / l_perp;
- }
- for (size_t i = 0; i < e_cnt; i++) {
- edge_t *e0 = edge_list[i];
- pointf dumber[4];
- if (aghead(e0) == head) {
- for (size_t j = 0; j < 4; j++) {
- dumber[j] = dumb[j];
- }
- } else {
- for (size_t j = 0; j < 4; j++) {
- dumber[3 - j] = dumb[j];
- }
- }
- if (et == EDGETYPE_PLINE) {
- Ppoint_t pts[] = {dumber[0], dumber[1], dumber[2], dumber[3]};
- Ppolyline_t spl, line = {.pn = 4, .ps = pts};
- make_polyline (line, &spl);
- clip_and_install(e0, aghead(e0), spl.ps, (size_t)spl.pn, sinfo);
- }
- else
- clip_and_install(e0, aghead(e0), dumber, 4, sinfo);
- addEdgeLabels(e0);
- dumb[1] = add_pointf(dumb[1], del);
- dumb[2] = add_pointf(dumb[2], del);
- }
- }
|