|
Graphviz
2.29.20120523.0446
|
00001 /* $Id$ $Revision$ */ 00002 /* vim:set shiftwidth=4 ts=8: */ 00003 00004 /************************************************************************* 00005 * Copyright (c) 2011 AT&T Intellectual Property 00006 * All rights reserved. This program and the accompanying materials 00007 * are made available under the terms of the Eclipse Public License v1.0 00008 * which accompanies this distribution, and is available at 00009 * http://www.eclipse.org/legal/epl-v10.html 00010 * 00011 * Contributors: See CVS logs. Details at http://www.graphviz.org/ 00012 *************************************************************************/ 00013 00014 00015 /* Module for clipping splines to cluster boxes. 00016 */ 00017 00018 #include "dot.h" 00019 00020 /* pf2s: 00021 * Convert a pointf to its string representation. 00022 */ 00023 static char *pf2s(pointf p, char *buf) 00024 { 00025 sprintf(buf, "(%.5g,%.5g)", p.x, p.y); 00026 return buf; 00027 } 00028 00029 /* Return point where line segment [pp,cp] intersects 00030 * the box bp. Assume cp is outside the box, and pp is 00031 * on or in the box. 00032 */ 00033 static pointf boxIntersectf(pointf pp, pointf cp, boxf * bp) 00034 { 00035 pointf ipp; 00036 double ppx = pp.x; 00037 double ppy = pp.y; 00038 double cpx = cp.x; 00039 double cpy = cp.y; 00040 pointf ll; 00041 pointf ur; 00042 00043 ll = bp->LL; 00044 ur = bp->UR; 00045 if (cp.x < ll.x) { 00046 ipp.x = ll.x; 00047 ipp.y = pp.y + (int) ((ipp.x - ppx) * (ppy - cpy) / (ppx - cpx)); 00048 if (ipp.y >= ll.y && ipp.y <= ur.y) 00049 return ipp; 00050 } 00051 if (cp.x > ur.x) { 00052 ipp.x = ur.x; 00053 ipp.y = pp.y + (int) ((ipp.x - ppx) * (ppy - cpy) / (ppx - cpx)); 00054 if (ipp.y >= ll.y && ipp.y <= ur.y) 00055 return ipp; 00056 } 00057 if (cp.y < ll.y) { 00058 ipp.y = ll.y; 00059 ipp.x = pp.x + (int) ((ipp.y - ppy) * (ppx - cpx) / (ppy - cpy)); 00060 if (ipp.x >= ll.x && ipp.x <= ur.x) 00061 return ipp; 00062 } 00063 if (cp.y > ur.y) { 00064 ipp.y = ur.y; 00065 ipp.x = pp.x + (int) ((ipp.y - ppy) * (ppx - cpx) / (ppy - cpy)); 00066 if (ipp.x >= ll.x && ipp.x <= ur.x) 00067 return ipp; 00068 } 00069 00070 /* failure */ 00071 { 00072 char ppbuf[100], cpbuf[100], llbuf[100], urbuf[100]; 00073 00074 agerr(AGERR, 00075 "segment [%s,%s] does not intersect box ll=%s,ur=%s\n", 00076 pf2s(pp, ppbuf), pf2s(cp, cpbuf), 00077 pf2s(ll, llbuf), pf2s(ur, urbuf)); 00078 assert(0); 00079 } 00080 return ipp; 00081 } 00082 00083 /* inBoxf: 00084 * Returns true if p is on or in box bb 00085 */ 00086 static int inBoxf(pointf p, boxf * bb) 00087 { 00088 return INSIDE(p, *bb); 00089 } 00090 00091 /* getCluster: 00092 * Returns subgraph of g with given name. 00093 * Returns NULL if no name is given, or subgraph of 00094 * that name does not exist. 00095 */ 00096 #ifdef WITH_CGRAPH 00097 static graph_t *getCluster(graph_t * g, char *cluster_name, Dt_t* map) 00098 #else 00099 static graph_t *getCluster(graph_t * g, char *cluster_name) 00100 #endif 00101 { 00102 Agraph_t* sg; 00103 00104 if (!cluster_name || (*cluster_name == '\0')) 00105 return NULL; 00106 #ifdef WITH_CGRAPH 00107 sg = findCluster (map, cluster_name); 00108 #else 00109 sg = agfindsubg(g, cluster_name); 00110 #endif 00111 if (sg == NULL) { 00112 agerr(AGWARN, "cluster named %s not found\n", cluster_name); 00113 } 00114 return sg; 00115 } 00116 00117 /* The following functions are derived from pp. 411-415 (pp. 791-795) 00118 * of Graphics Gems. In the code there, they use a SGN function to 00119 * count crossings. This doesn't seem to handle certain special cases, 00120 * as when the last point is on the line. It certainly didn't work 00121 * for us when we used int values; see bug 145. We needed to use CMP instead. 00122 * 00123 * Possibly unnecessary with double values, but harmless. 00124 */ 00125 00126 /* countVertCross: 00127 * Return the number of times the Bezier control polygon crosses 00128 * the vertical line x = xcoord. 00129 */ 00130 static int countVertCross(pointf * pts, double xcoord) 00131 { 00132 int i; 00133 int sign, old_sign; 00134 int num_crossings = 0; 00135 00136 sign = CMP(pts[0].x, xcoord); 00137 if (sign == 0) 00138 num_crossings++; 00139 for (i = 1; i <= 3; i++) { 00140 old_sign = sign; 00141 sign = CMP(pts[i].x, xcoord); 00142 if ((sign != old_sign) && (old_sign != 0)) 00143 num_crossings++; 00144 } 00145 return num_crossings; 00146 } 00147 00148 /* countHorzCross: 00149 * Return the number of times the Bezier control polygon crosses 00150 * the horizontal line y = ycoord. 00151 */ 00152 static int countHorzCross(pointf * pts, double ycoord) 00153 { 00154 int i; 00155 int sign, old_sign; 00156 int num_crossings = 0; 00157 00158 sign = CMP(pts[0].y, ycoord); 00159 if (sign == 0) 00160 num_crossings++; 00161 for (i = 1; i <= 3; i++) { 00162 old_sign = sign; 00163 sign = CMP(pts[i].y, ycoord); 00164 if ((sign != old_sign) && (old_sign != 0)) 00165 num_crossings++; 00166 } 00167 return num_crossings; 00168 } 00169 00170 /* findVertical: 00171 * Given 4 Bezier control points pts, corresponding to the portion 00172 * of an initial spline with path parameter in the range 00173 * 0.0 <= tmin <= t <= tmax <= 1.0, return t where the spline 00174 * first crosses a vertical line segment 00175 * [(xcoord,ymin),(xcoord,ymax)]. Return -1 if not found. 00176 * This is done by binary subdivision. 00177 */ 00178 static double 00179 findVertical(pointf * pts, double tmin, double tmax, 00180 double xcoord, double ymin, double ymax) 00181 { 00182 pointf Left[4]; 00183 pointf Right[4]; 00184 double t; 00185 int no_cross = countVertCross(pts, xcoord); 00186 00187 if (no_cross == 0) 00188 return -1.0; 00189 00190 /* if 1 crossing and on the line x == xcoord (within 1 point) */ 00191 if ((no_cross == 1) && (ROUND(pts[3].x) == ROUND(xcoord))) { 00192 if ((ymin <= pts[3].y) && (pts[3].y <= ymax)) { 00193 return tmax; 00194 } else 00195 return -1.0; 00196 } 00197 00198 /* split the Bezier into halves, trying the first half first. */ 00199 Bezier(pts, 3, 0.5, Left, Right); 00200 t = findVertical(Left, tmin, (tmin + tmax) / 2.0, xcoord, ymin, ymax); 00201 if (t >= 0.0) 00202 return t; 00203 return findVertical(Right, (tmin + tmax) / 2.0, tmax, xcoord, ymin, 00204 ymax); 00205 00206 } 00207 00208 /* findHorizontal: 00209 * Given 4 Bezier control points pts, corresponding to the portion 00210 * of an initial spline with path parameter in the range 00211 * 0.0 <= tmin <= t <= tmax <= 1.0, return t where the spline 00212 * first crosses a horizontal line segment 00213 * [(xmin,ycoord),(xmax,ycoord)]. Return -1 if not found. 00214 * This is done by binary subdivision. 00215 */ 00216 static double 00217 findHorizontal(pointf * pts, double tmin, double tmax, 00218 double ycoord, double xmin, double xmax) 00219 { 00220 pointf Left[4]; 00221 pointf Right[4]; 00222 double t; 00223 int no_cross = countHorzCross(pts, ycoord); 00224 00225 if (no_cross == 0) 00226 return -1.0; 00227 00228 /* if 1 crossing and on the line y == ycoord (within 1 point) */ 00229 if ((no_cross == 1) && (ROUND(pts[3].y) == ROUND(ycoord))) { 00230 if ((xmin <= pts[3].x) && (pts[3].x <= xmax)) { 00231 return tmax; 00232 } else 00233 return -1.0; 00234 } 00235 00236 /* split the Bezier into halves, trying the first half first. */ 00237 Bezier(pts, 3, 0.5, Left, Right); 00238 t = findHorizontal(Left, tmin, (tmin + tmax) / 2.0, ycoord, xmin, 00239 xmax); 00240 if (t >= 0.0) 00241 return t; 00242 return findHorizontal(Right, (tmin + tmax) / 2.0, tmax, ycoord, xmin, 00243 xmax); 00244 } 00245 00246 /* splineIntersectf: 00247 * Given four spline control points and a box, 00248 * find the shortest portion of the spline from 00249 * pts[0] to the intersection with the box, if any. 00250 * If an intersection is found, the four points are stored in pts[0..3] 00251 * with pts[3] being on the box, and 1 is returned. Otherwise, pts 00252 * is left unchanged and 0 is returned. 00253 */ 00254 static int splineIntersectf(pointf * pts, boxf * bb) 00255 { 00256 double tmin = 2.0; 00257 double t; 00258 pointf origpts[4]; 00259 int i; 00260 00261 for (i = 0; i < 4; i++) { 00262 origpts[i] = pts[i]; 00263 } 00264 00265 t = findVertical(pts, 0.0, 1.0, bb->LL.x, bb->LL.y, bb->UR.y); 00266 if ((t >= 0) && (t < tmin)) { 00267 Bezier(origpts, 3, t, pts, NULL); 00268 tmin = t; 00269 } 00270 t = findVertical(pts, 0.0, MIN(1.0, tmin), bb->UR.x, bb->LL.y, 00271 bb->UR.y); 00272 if ((t >= 0) && (t < tmin)) { 00273 Bezier(origpts, 3, t, pts, NULL); 00274 tmin = t; 00275 } 00276 t = findHorizontal(pts, 0.0, MIN(1.0, tmin), bb->LL.y, bb->LL.x, 00277 bb->UR.x); 00278 if ((t >= 0) && (t < tmin)) { 00279 Bezier(origpts, 3, t, pts, NULL); 00280 tmin = t; 00281 } 00282 t = findHorizontal(pts, 0.0, MIN(1.0, tmin), bb->UR.y, bb->LL.x, 00283 bb->UR.x); 00284 if ((t >= 0) && (t < tmin)) { 00285 Bezier(origpts, 3, t, pts, NULL); 00286 tmin = t; 00287 } 00288 00289 if (tmin < 2.0) { 00290 return 1; 00291 } else 00292 return 0; 00293 } 00294 00295 /* makeCompoundEdge: 00296 * If edge e has a cluster head and/or cluster tail, 00297 * clip spline to outside of cluster. 00298 * Requirement: spline is composed of only one part, 00299 * with n control points where n >= 4 and n (mod 3) = 1. 00300 * If edge has arrowheads, reposition them. 00301 */ 00302 #ifdef WITH_CGRAPH 00303 static void makeCompoundEdge(graph_t * g, edge_t * e, Dt_t* clustMap) 00304 #else 00305 static void makeCompoundEdge(graph_t * g, edge_t * e) 00306 #endif 00307 { 00308 graph_t *lh; /* cluster containing head */ 00309 graph_t *lt; /* cluster containing tail */ 00310 bezier *bez; /* original Bezier for e */ 00311 bezier *nbez; /* new Bezier for e */ 00312 int starti = 0, endi = 0; /* index of first and last control point */ 00313 node_t *head; 00314 node_t *tail; 00315 boxf *bb; 00316 int i, j; 00317 int size; 00318 pointf pts[4]; 00319 pointf p; 00320 int fixed; 00321 00322 /* find head and tail target clusters, if defined */ 00323 #ifdef WITH_CGRAPH 00324 lh = getCluster(g, agget(e, "lhead"), clustMap); 00325 lt = getCluster(g, agget(e, "ltail"), clustMap); 00326 #else 00327 lh = getCluster(g, agget(e, "lhead")); 00328 lt = getCluster(g, agget(e, "ltail")); 00329 #endif 00330 if (!lt && !lh) 00331 return; 00332 if (!ED_spl(e)) return; 00333 00334 /* at present, we only handle single spline case */ 00335 if (ED_spl(e)->size > 1) { 00336 agerr(AGWARN, "%s -> %s: spline size > 1 not supported\n", 00337 agnameof(agtail(e)), agnameof(aghead(e))); 00338 return; 00339 } 00340 bez = ED_spl(e)->list; 00341 size = bez->size; 00342 00343 head = aghead(e); 00344 tail = agtail(e); 00345 00346 /* allocate new Bezier */ 00347 nbez = GNEW(bezier); 00348 nbez->eflag = bez->eflag; 00349 nbez->sflag = bez->sflag; 00350 00351 /* if Bezier has four points, almost collinear, 00352 * make line - unimplemented optimization? 00353 */ 00354 00355 /* If head cluster defined, find first Bezier 00356 * crossing head cluster, and truncate spline to 00357 * box edge. 00358 * Otherwise, leave end alone. 00359 */ 00360 fixed = 0; 00361 if (lh) { 00362 bb = &(GD_bb(lh)); 00363 if (!inBoxf(ND_coord(head), bb)) { 00364 agerr(AGWARN, "%s -> %s: head not inside head cluster %s\n", 00365 agnameof(agtail(e)), agnameof(aghead(e)), agget(e, "lhead")); 00366 } else { 00367 /* If first control point is in bb, degenerate case. Spline 00368 * reduces to four points between the arrow head and the point 00369 * where the segment between the first control point and arrow head 00370 * crosses box. 00371 */ 00372 if (inBoxf(bez->list[0], bb)) { 00373 if (inBoxf(ND_coord(tail), bb)) { 00374 agerr(AGWARN, 00375 "%s -> %s: tail is inside head cluster %s\n", 00376 agnameof(agtail(e)), agnameof(aghead(e)), agget(e, "lhead")); 00377 } else { 00378 assert(bez->sflag); /* must be arrowhead on tail */ 00379 p = boxIntersectf(bez->list[0], bez->sp, bb); 00380 bez->list[3] = p; 00381 bez->list[1] = mid_pointf(p, bez->sp); 00382 bez->list[0] = mid_pointf(bez->list[1], bez->sp); 00383 bez->list[2] = mid_pointf(bez->list[1], p); 00384 if (bez->eflag) 00385 endi = arrowEndClip(e, bez->list, 00386 starti, 0, nbez, bez->eflag); 00387 endi += 3; 00388 fixed = 1; 00389 } 00390 } else { 00391 for (endi = 0; endi < size - 1; endi += 3) { 00392 if (splineIntersectf(&(bez->list[endi]), bb)) 00393 break; 00394 } 00395 if (endi == size - 1) { /* no intersection */ 00396 assert(bez->eflag); 00397 nbez->ep = boxIntersectf(bez->ep, bez->list[endi], bb); 00398 } else { 00399 if (bez->eflag) 00400 endi = 00401 arrowEndClip(e, bez->list, 00402 starti, endi, nbez, bez->eflag); 00403 endi += 3; 00404 } 00405 fixed = 1; 00406 } 00407 } 00408 } 00409 if (fixed == 0) { /* if no lh, or something went wrong, use original head */ 00410 endi = size - 1; 00411 if (bez->eflag) 00412 nbez->ep = bez->ep; 00413 } 00414 00415 /* If tail cluster defined, find last Bezier 00416 * crossing tail cluster, and truncate spline to 00417 * box edge. 00418 * Otherwise, leave end alone. 00419 */ 00420 fixed = 0; 00421 if (lt) { 00422 bb = &(GD_bb(lt)); 00423 if (!inBoxf(ND_coord(tail), bb)) { 00424 agerr(AGWARN, "%s -> %s: tail not inside tail cluster %s\n", 00425 agnameof(agtail(e)), agnameof(aghead(e)), agget(e, "ltail")); 00426 } else { 00427 /* If last control point is in bb, degenerate case. Spline 00428 * reduces to four points between arrow head, and the point 00429 * where the segment between the last control point and the 00430 * arrow head crosses box. 00431 */ 00432 if (inBoxf(bez->list[endi], bb)) { 00433 if (inBoxf(ND_coord(head), bb)) { 00434 agerr(AGWARN, 00435 "%s -> %s: head is inside tail cluster %s\n", 00436 agnameof(agtail(e)), agnameof(aghead(e)), agget(e, "ltail")); 00437 } else { 00438 assert(bez->eflag); /* must be arrowhead on head */ 00439 p = boxIntersectf(bez->list[endi], nbez->ep, bb); 00440 starti = endi - 3; 00441 bez->list[starti] = p; 00442 bez->list[starti + 2] = mid_pointf(p, nbez->ep); 00443 bez->list[starti + 3] = mid_pointf(bez->list[starti + 2], nbez->ep); 00444 bez->list[starti + 1] = mid_pointf(bez->list[starti + 2], p); 00445 if (bez->sflag) 00446 starti = arrowStartClip(e, bez->list, starti, 00447 endi - 3, nbez, bez->sflag); 00448 fixed = 1; 00449 } 00450 } else { 00451 for (starti = endi; starti > 0; starti -= 3) { 00452 for (i = 0; i < 4; i++) 00453 pts[i] = bez->list[starti - i]; 00454 if (splineIntersectf(pts, bb)) { 00455 for (i = 0; i < 4; i++) 00456 bez->list[starti - i] = pts[i]; 00457 break; 00458 } 00459 } 00460 if (starti == 0) { 00461 assert(bez->sflag); 00462 nbez->sp = 00463 boxIntersectf(bez->sp, bez->list[starti], bb); 00464 } else { 00465 starti -= 3; 00466 if (bez->sflag) 00467 starti = arrowStartClip(e, bez->list, starti, 00468 endi - 3, nbez, bez->sflag); 00469 } 00470 fixed = 1; 00471 } 00472 } 00473 } 00474 if (fixed == 0) { /* if no lt, or something went wrong, use original tail */ 00475 /* Note: starti == 0 */ 00476 if (bez->sflag) 00477 nbez->sp = bez->sp; 00478 } 00479 00480 /* complete Bezier, free garbage and attach new Bezier to edge 00481 */ 00482 nbez->size = endi - starti + 1; 00483 nbez->list = N_GNEW(nbez->size, pointf); 00484 for (i = 0, j = starti; i < nbez->size; i++, j++) 00485 nbez->list[i] = bez->list[j]; 00486 free(bez->list); 00487 free(bez); 00488 ED_spl(e)->list = nbez; 00489 } 00490 #if 0 00491 static void dump(Dt_t* map) 00492 { 00493 clust_t* p; 00494 fprintf (stderr, "# in map: %d\n", dtsize(map)); 00495 for (p=(clust_t*)dtfirst(map);p; p = (clust_t*)dtnext(map,p)) { 00496 fprintf (stderr, " %s\n", p->name); 00497 } 00498 } 00499 #endif 00500 00501 /* dot_compoundEdges: 00502 */ 00503 void dot_compoundEdges(graph_t * g) 00504 { 00505 edge_t *e; 00506 node_t *n; 00507 #ifdef WITH_CGRAPH 00508 Dt_t* clustMap = mkClustMap (g); 00509 #endif 00510 for (n = agfstnode(g); n; n = agnxtnode(g, n)) { 00511 for (e = agfstout(g, n); e; e = agnxtout(g, e)) { 00512 #ifdef WITH_CGRAPH 00513 makeCompoundEdge(g, e, clustMap); 00514 #else 00515 makeCompoundEdge(g, e); 00516 #endif 00517 } 00518 } 00519 #ifdef WITH_CGRAPH 00520 dtclose(clustMap); 00521 #endif 00522 }
1.7.5