compound.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. /*************************************************************************
  2. * Copyright (c) 2011 AT&T Intellectual Property
  3. * All rights reserved. This program and the accompanying materials
  4. * are made available under the terms of the Eclipse Public License v1.0
  5. * which accompanies this distribution, and is available at
  6. * https://www.eclipse.org/legal/epl-v10.html
  7. *
  8. * Contributors: Details at https://graphviz.org
  9. *************************************************************************/
  10. /* Module for clipping splines to cluster boxes.
  11. */
  12. #include <cgraph/gv_math.h>
  13. #include <dotgen/dot.h>
  14. #include <stdbool.h>
  15. #include <stddef.h>
  16. #include <util/agxbuf.h>
  17. #include <util/alloc.h>
  18. /* Return point where line segment [pp,cp] intersects
  19. * the box bp. Assume cp is outside the box, and pp is
  20. * on or in the box.
  21. */
  22. static pointf boxIntersectf(pointf pp, pointf cp, boxf * bp)
  23. {
  24. pointf ipp;
  25. double ppx = pp.x;
  26. double ppy = pp.y;
  27. double cpx = cp.x;
  28. double cpy = cp.y;
  29. pointf ll;
  30. pointf ur;
  31. ll = bp->LL;
  32. ur = bp->UR;
  33. if (cp.x < ll.x) {
  34. ipp.x = ll.x;
  35. ipp.y = pp.y + (int) ((ipp.x - ppx) * (ppy - cpy) / (ppx - cpx));
  36. if (ipp.y >= ll.y && ipp.y <= ur.y)
  37. return ipp;
  38. }
  39. if (cp.x > ur.x) {
  40. ipp.x = ur.x;
  41. ipp.y = pp.y + (int) ((ipp.x - ppx) * (ppy - cpy) / (ppx - cpx));
  42. if (ipp.y >= ll.y && ipp.y <= ur.y)
  43. return ipp;
  44. }
  45. if (cp.y < ll.y) {
  46. ipp.y = ll.y;
  47. ipp.x = pp.x + (int) ((ipp.y - ppy) * (ppx - cpx) / (ppy - cpy));
  48. if (ipp.x >= ll.x && ipp.x <= ur.x)
  49. return ipp;
  50. }
  51. if (cp.y > ur.y) {
  52. ipp.y = ur.y;
  53. ipp.x = pp.x + (int) ((ipp.y - ppy) * (ppx - cpx) / (ppy - cpy));
  54. if (ipp.x >= ll.x && ipp.x <= ur.x)
  55. return ipp;
  56. }
  57. /* failure */
  58. agerrorf(
  59. "segment [(%.5g, %.5g),(%.5g,%.5g)] does not intersect box "
  60. "ll=(%.5g,%.5g),ur=(%.5g,%.5g)\n", pp.x, pp.y, cp.x, cp.y, ll.x, ll.y,
  61. ur.x, ur.y);
  62. assert(0);
  63. return ipp;
  64. }
  65. /* inBoxf:
  66. * Returns true if p is on or in box bb
  67. */
  68. static int inBoxf(pointf p, boxf * bb)
  69. {
  70. return INSIDE(p, *bb);
  71. }
  72. /* getCluster:
  73. * Returns subgraph with given name.
  74. * Returns NULL if no name is given, or subgraph of
  75. * that name does not exist.
  76. */
  77. static graph_t *getCluster(char *cluster_name, Dt_t *map) {
  78. Agraph_t* sg;
  79. if (!cluster_name || *cluster_name == '\0')
  80. return NULL;
  81. sg = findCluster (map, cluster_name);
  82. if (sg == NULL) {
  83. agwarningf("cluster named %s not found\n", cluster_name);
  84. }
  85. return sg;
  86. }
  87. /* The following functions are derived from pp. 411-415 (pp. 791-795)
  88. * of Graphics Gems. In the code there, they use a SGN function to
  89. * count crossings. This doesn't seem to handle certain special cases,
  90. * as when the last point is on the line. It certainly didn't work
  91. * for us when we used int values; see bug 145. We needed to use `fcmp` instead.
  92. *
  93. * Possibly unnecessary with double values, but harmless.
  94. */
  95. /* countVertCross:
  96. * Return the number of times the Bezier control polygon crosses
  97. * the vertical line x = xcoord.
  98. */
  99. static int countVertCross(pointf * pts, double xcoord)
  100. {
  101. int i;
  102. int sign, old_sign;
  103. int num_crossings = 0;
  104. sign = fcmp(pts[0].x, xcoord);
  105. if (sign == 0)
  106. num_crossings++;
  107. for (i = 1; i <= 3; i++) {
  108. old_sign = sign;
  109. sign = fcmp(pts[i].x, xcoord);
  110. if (sign != old_sign && old_sign != 0)
  111. num_crossings++;
  112. }
  113. return num_crossings;
  114. }
  115. /* countHorzCross:
  116. * Return the number of times the Bezier control polygon crosses
  117. * the horizontal line y = ycoord.
  118. */
  119. static int countHorzCross(pointf * pts, double ycoord)
  120. {
  121. int i;
  122. int sign, old_sign;
  123. int num_crossings = 0;
  124. sign = fcmp(pts[0].y, ycoord);
  125. if (sign == 0)
  126. num_crossings++;
  127. for (i = 1; i <= 3; i++) {
  128. old_sign = sign;
  129. sign = fcmp(pts[i].y, ycoord);
  130. if (sign != old_sign && old_sign != 0)
  131. num_crossings++;
  132. }
  133. return num_crossings;
  134. }
  135. /* findVertical:
  136. * Given 4 Bezier control points pts, corresponding to the portion
  137. * of an initial spline with path parameter in the range
  138. * 0.0 <= tmin <= t <= tmax <= 1.0, return t where the spline
  139. * first crosses a vertical line segment
  140. * [(xcoord,ymin),(xcoord,ymax)]. Return -1 if not found.
  141. * This is done by binary subdivision.
  142. */
  143. static double
  144. findVertical(pointf * pts, double tmin, double tmax,
  145. double xcoord, double ymin, double ymax)
  146. {
  147. pointf Left[4];
  148. pointf Right[4];
  149. double t;
  150. int no_cross;
  151. if (tmin == tmax)
  152. return tmin;
  153. no_cross = countVertCross(pts, xcoord);
  154. if (no_cross == 0)
  155. return -1.0;
  156. /* if 1 crossing and on the line x == xcoord (within 0.005 point) */
  157. if (no_cross == 1 && fabs(pts[3].x - xcoord) <= 0.005) {
  158. if (ymin <= pts[3].y && pts[3].y <= ymax) {
  159. return tmax;
  160. } else
  161. return -1.0;
  162. }
  163. /* split the Bezier into halves, trying the first half first. */
  164. Bezier(pts, 0.5, Left, Right);
  165. t = findVertical(Left, tmin, (tmin + tmax) / 2.0, xcoord, ymin, ymax);
  166. if (t >= 0.0)
  167. return t;
  168. return findVertical(Right, (tmin + tmax) / 2.0, tmax, xcoord, ymin,
  169. ymax);
  170. }
  171. /* findHorizontal:
  172. * Given 4 Bezier control points pts, corresponding to the portion
  173. * of an initial spline with path parameter in the range
  174. * 0.0 <= tmin <= t <= tmax <= 1.0, return t where the spline
  175. * first crosses a horizontal line segment
  176. * [(xmin,ycoord),(xmax,ycoord)]. Return -1 if not found.
  177. * This is done by binary subdivision.
  178. */
  179. static double
  180. findHorizontal(pointf * pts, double tmin, double tmax,
  181. double ycoord, double xmin, double xmax)
  182. {
  183. pointf Left[4];
  184. pointf Right[4];
  185. double t;
  186. int no_cross;
  187. if (tmin == tmax)
  188. return tmin;
  189. no_cross = countHorzCross(pts, ycoord);
  190. if (no_cross == 0)
  191. return -1.0;
  192. /* if 1 crossing and on the line y == ycoord (within 0.005 point) */
  193. if (no_cross == 1 && fabs(pts[3].y - ycoord) <= 0.005) {
  194. if (xmin <= pts[3].x && pts[3].x <= xmax) {
  195. return tmax;
  196. } else
  197. return -1.0;
  198. }
  199. /* split the Bezier into halves, trying the first half first. */
  200. Bezier(pts, 0.5, Left, Right);
  201. t = findHorizontal(Left, tmin, (tmin + tmax) / 2.0, ycoord, xmin,
  202. xmax);
  203. if (t >= 0.0)
  204. return t;
  205. return findHorizontal(Right, (tmin + tmax) / 2.0, tmax, ycoord, xmin,
  206. xmax);
  207. }
  208. /* splineIntersectf:
  209. * Given four spline control points and a box,
  210. * find the shortest portion of the spline from
  211. * pts[0] to the intersection with the box, if any.
  212. * If an intersection is found, the four points are stored in pts[0..3]
  213. * with pts[3] being on the box, and 1 is returned. Otherwise, pts
  214. * is left unchanged and 0 is returned.
  215. */
  216. static int splineIntersectf(pointf * pts, boxf * bb)
  217. {
  218. double tmin = 2.0;
  219. double t;
  220. pointf origpts[4];
  221. int i;
  222. for (i = 0; i < 4; i++) {
  223. origpts[i] = pts[i];
  224. }
  225. t = findVertical(pts, 0.0, 1.0, bb->LL.x, bb->LL.y, bb->UR.y);
  226. if (t >= 0 && t < tmin) {
  227. Bezier(origpts, t, pts, NULL);
  228. tmin = t;
  229. }
  230. t = findVertical(pts, 0.0, MIN(1.0, tmin), bb->UR.x, bb->LL.y,
  231. bb->UR.y);
  232. if (t >= 0 && t < tmin) {
  233. Bezier(origpts, t, pts, NULL);
  234. tmin = t;
  235. }
  236. t = findHorizontal(pts, 0.0, MIN(1.0, tmin), bb->LL.y, bb->LL.x,
  237. bb->UR.x);
  238. if (t >= 0 && t < tmin) {
  239. Bezier(origpts, t, pts, NULL);
  240. tmin = t;
  241. }
  242. t = findHorizontal(pts, 0.0, MIN(1.0, tmin), bb->UR.y, bb->LL.x,
  243. bb->UR.x);
  244. if (t >= 0 && t < tmin) {
  245. Bezier(origpts, t, pts, NULL);
  246. tmin = t;
  247. }
  248. if (tmin < 2.0) {
  249. return 1;
  250. } else
  251. return 0;
  252. }
  253. /* makeCompoundEdge:
  254. * If edge e has a cluster head and/or cluster tail,
  255. * clip spline to outside of cluster.
  256. * Requirement: spline is composed of only one part,
  257. * with n control points where n >= 4 and n (mod 3) = 1.
  258. * If edge has arrowheads, reposition them.
  259. */
  260. static void makeCompoundEdge(edge_t *e, Dt_t *clustMap) {
  261. size_t starti = 0, endi = 0; // index of first and last control point
  262. /* find head and tail target clusters, if defined */
  263. graph_t *lh = getCluster(agget(e, "lhead"), clustMap); // cluster containing head
  264. graph_t *lt = getCluster(agget(e, "ltail"), clustMap); // cluster containing tail
  265. if (!lt && !lh)
  266. return;
  267. if (!ED_spl(e)) return;
  268. /* at present, we only handle single spline case */
  269. if (ED_spl(e)->size > 1) {
  270. agwarningf("%s -> %s: spline size > 1 not supported\n",
  271. agnameof(agtail(e)), agnameof(aghead(e)));
  272. return;
  273. }
  274. bezier *bez = ED_spl(e)->list; // original Bezier for e
  275. const size_t size = bez->size;
  276. node_t *head = aghead(e);
  277. node_t *tail = agtail(e);
  278. /* allocate new Bezier */
  279. bezier nbez = {0}; // new Bezier for `e`
  280. nbez.eflag = bez->eflag;
  281. nbez.sflag = bez->sflag;
  282. /* if Bezier has four points, almost collinear,
  283. * make line - unimplemented optimization?
  284. */
  285. /* If head cluster defined, find first Bezier
  286. * crossing head cluster, and truncate spline to
  287. * box edge.
  288. * Otherwise, leave end alone.
  289. */
  290. bool fixed = false;
  291. if (lh) {
  292. boxf *bb = &GD_bb(lh);
  293. if (!inBoxf(ND_coord(head), bb)) {
  294. agwarningf("%s -> %s: head not inside head cluster %s\n",
  295. agnameof(agtail(e)), agnameof(aghead(e)), agget(e, "lhead"));
  296. } else {
  297. /* If first control point is in bb, degenerate case. Spline
  298. * reduces to four points between the arrow head and the point
  299. * where the segment between the first control point and arrow head
  300. * crosses box.
  301. */
  302. if (inBoxf(bez->list[0], bb)) {
  303. if (inBoxf(ND_coord(tail), bb)) {
  304. agwarningf(
  305. "%s -> %s: tail is inside head cluster %s\n",
  306. agnameof(agtail(e)), agnameof(aghead(e)), agget(e, "lhead"));
  307. } else {
  308. assert(bez->sflag); /* must be arrowhead on tail */
  309. pointf p = boxIntersectf(bez->list[0], bez->sp, bb);
  310. bez->list[3] = p;
  311. bez->list[1] = mid_pointf(p, bez->sp);
  312. bez->list[0] = mid_pointf(bez->list[1], bez->sp);
  313. bez->list[2] = mid_pointf(bez->list[1], p);
  314. if (bez->eflag)
  315. endi = arrowEndClip(e, bez->list,
  316. starti, 0, &nbez, bez->eflag);
  317. endi += 3;
  318. fixed = true;
  319. }
  320. } else {
  321. for (endi = 0; endi < size - 1; endi += 3) {
  322. if (splineIntersectf(&bez->list[endi], bb))
  323. break;
  324. }
  325. if (endi == size - 1) { /* no intersection */
  326. assert(bez->eflag);
  327. nbez.ep = boxIntersectf(bez->ep, bez->list[endi], bb);
  328. } else {
  329. if (bez->eflag)
  330. endi =
  331. arrowEndClip(e, bez->list,
  332. starti, endi, &nbez, bez->eflag);
  333. endi += 3;
  334. }
  335. fixed = true;
  336. }
  337. }
  338. }
  339. if (!fixed) { // if no lh, or something went wrong, use original head
  340. endi = size - 1;
  341. if (bez->eflag)
  342. nbez.ep = bez->ep;
  343. }
  344. /* If tail cluster defined, find last Bezier
  345. * crossing tail cluster, and truncate spline to
  346. * box edge.
  347. * Otherwise, leave end alone.
  348. */
  349. fixed = false;
  350. if (lt) {
  351. boxf *bb = &GD_bb(lt);
  352. if (!inBoxf(ND_coord(tail), bb)) {
  353. agwarningf("%s -> %s: tail not inside tail cluster %s\n",
  354. agnameof(agtail(e)), agnameof(aghead(e)), agget(e, "ltail"));
  355. } else {
  356. /* If last control point is in bb, degenerate case. Spline
  357. * reduces to four points between arrow head, and the point
  358. * where the segment between the last control point and the
  359. * arrow head crosses box.
  360. */
  361. if (inBoxf(bez->list[endi], bb)) {
  362. if (inBoxf(ND_coord(head), bb)) {
  363. agwarningf(
  364. "%s -> %s: head is inside tail cluster %s\n",
  365. agnameof(agtail(e)), agnameof(aghead(e)), agget(e, "ltail"));
  366. } else {
  367. assert(bez->eflag); /* must be arrowhead on head */
  368. pointf p = boxIntersectf(bez->list[endi], nbez.ep, bb);
  369. starti = endi - 3;
  370. bez->list[starti] = p;
  371. bez->list[starti + 2] = mid_pointf(p, nbez.ep);
  372. bez->list[starti + 3] = mid_pointf(bez->list[starti + 2], nbez.ep);
  373. bez->list[starti + 1] = mid_pointf(bez->list[starti + 2], p);
  374. if (bez->sflag)
  375. starti = arrowStartClip(e, bez->list, starti,
  376. endi - 3, &nbez, bez->sflag);
  377. fixed = true;
  378. }
  379. } else {
  380. for (starti = endi; starti > 0; starti -= 3) {
  381. pointf pts[4];
  382. for (size_t i = 0; i < 4; i++)
  383. pts[i] = bez->list[starti - i];
  384. if (splineIntersectf(pts, bb)) {
  385. for (size_t i = 0; i < 4; i++)
  386. bez->list[starti - i] = pts[i];
  387. break;
  388. }
  389. }
  390. if (starti == 0 && bez->sflag) {
  391. nbez.sp = boxIntersectf(bez->sp, bez->list[starti], bb);
  392. } else if (starti != 0) {
  393. starti -= 3;
  394. if (bez->sflag)
  395. starti = arrowStartClip(e, bez->list, starti,
  396. endi - 3, &nbez, bez->sflag);
  397. }
  398. fixed = true;
  399. }
  400. }
  401. }
  402. if (!fixed) { // if no lt, or something went wrong, use original tail
  403. /* Note: starti == 0 */
  404. if (bez->sflag)
  405. nbez.sp = bez->sp;
  406. }
  407. /* complete Bezier, free garbage and attach new Bezier to edge
  408. */
  409. nbez.size = endi - starti + 1;
  410. nbez.list = gv_calloc(nbez.size, sizeof(pointf));
  411. for (size_t i = 0, j = starti; i < nbez.size; i++, j++)
  412. nbez.list[i] = bez->list[j];
  413. free(bez->list);
  414. *ED_spl(e)->list = nbez;
  415. }
  416. /* dot_compoundEdges:
  417. */
  418. void dot_compoundEdges(graph_t * g)
  419. {
  420. edge_t *e;
  421. node_t *n;
  422. Dt_t* clustMap = mkClustMap (g);
  423. for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
  424. for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
  425. makeCompoundEdge(e, clustMap);
  426. }
  427. }
  428. dtclose(clustMap);
  429. }