tclpathplan.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  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. /*
  11. * Tcl binding to drive Stephen North's and
  12. * Emden Gansner's shortest path code.
  13. *
  14. * [email protected] October 2nd, 1996
  15. */
  16. #include "config.h"
  17. #include <sys/types.h>
  18. #include <stdbool.h>
  19. #include <stdint.h>
  20. #include <stdlib.h>
  21. #include <string.h>
  22. #include <assert.h>
  23. #include <cgraph/list.h>
  24. #include <limits.h>
  25. #include "makecw.h"
  26. #include <math.h>
  27. #include <pathplan/pathutil.h>
  28. #include <pathplan/vispath.h>
  29. #include <pathplan/tri.h>
  30. #include "Plegal_arrangement.h"
  31. #include <tcl.h>
  32. #include <util/agxbuf.h>
  33. #include <util/alloc.h>
  34. #include <util/prisize_t.h>
  35. #if ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 6)) || ( TCL_MAJOR_VERSION > 8)
  36. #else
  37. #ifndef Tcl_GetStringResult
  38. #define Tcl_GetStringResult(interp) interp->result
  39. #endif
  40. #endif
  41. typedef Ppoint_t point;
  42. typedef struct poly_s {
  43. int id;
  44. Ppoly_t boundary;
  45. } poly;
  46. DEFINE_LIST(polys, poly)
  47. /// printf format for TCL handles
  48. #define HANDLE_FORMAT ("vgpane%" PRISIZE_T)
  49. typedef struct {
  50. polys_t poly; // set of polygons
  51. vconfig_t *vc; /* visibility graph handle */
  52. Tcl_Interp *interp; /* interpreter that owns the binding */
  53. char *triangle_cmd; /* why is this here any more */
  54. size_t index; ///< position within allocated handles list
  55. bool valid; ///< is this pane currently allocated?
  56. } vgpane_t;
  57. DEFINE_LIST(vgpanes, vgpane_t)
  58. static vgpanes_t vgpaneTable;
  59. static int polyid = 0; /* unique and unchanging id for each poly */
  60. static poly *allocpoly(vgpane_t * vgp, int id, int npts)
  61. {
  62. polys_append(&vgp->poly, (poly){.id = id});
  63. poly *rv = polys_back(&vgp->poly);
  64. rv->boundary.pn = 0;
  65. rv->boundary.ps = gv_calloc(npts, sizeof(point));
  66. return rv;
  67. }
  68. static void vc_stale(vgpane_t * vgp)
  69. {
  70. if (vgp->vc) {
  71. Pobsclose(vgp->vc);
  72. vgp->vc = NULL;
  73. }
  74. }
  75. static int vc_refresh(vgpane_t * vgp)
  76. {
  77. if (vgp->vc == NULL) {
  78. Ppoly_t **obs = gv_calloc(polys_size(&vgp->poly), sizeof(Ppoly_t*));
  79. for (size_t i = 0; i < polys_size(&vgp->poly); i++)
  80. obs[i] = &polys_at(&vgp->poly, i)->boundary;
  81. if (!Plegal_arrangement(obs, polys_size(&vgp->poly)))
  82. fprintf(stderr, "bad arrangement\n");
  83. else
  84. vgp->vc = Pobsopen(obs, (int)polys_size(&vgp->poly));
  85. free(obs);
  86. }
  87. return vgp->vc != NULL;
  88. }
  89. static void dgsprintxy(agxbuf *result, int npts, const point p[]) {
  90. int i;
  91. assert(npts > 1);
  92. if (agxblen(result) > 0) {
  93. agxbputc(result, ' ');
  94. }
  95. agxbputc(result, '{');
  96. const char *separator = "";
  97. for (i = 0; i < npts; i++) {
  98. agxbprint(result, "%s%g %g", separator, p[i].x, p[i].y);
  99. separator = " ";
  100. }
  101. agxbputc(result, '}');
  102. }
  103. /// @param interp Interpreter context
  104. /// @param before Command with percent expressions
  105. /// @param vgcanvasHandle Index to use in "%r" substitution
  106. /// @param npts Number of coordinates
  107. /// @param ppos Coordinates to substitute for %t
  108. static void expandPercentsEval(Tcl_Interp *interp, char *before,
  109. size_t vgcanvasHandle, int npts,
  110. const point *ppos) {
  111. agxbuf scripts = {0};
  112. while (1) {
  113. /*
  114. * Find everything up to the next % character and append it to the
  115. * result string.
  116. */
  117. char *string = strchr(before, '%');
  118. if (string != NULL) {
  119. agxbput_n(&scripts, before, (size_t)(string - before));
  120. before = string;
  121. }
  122. if (*before == 0) {
  123. break;
  124. }
  125. /*
  126. * There's a percent sequence here. Process it.
  127. */
  128. switch (before[1]) {
  129. case 'r':
  130. agxbprint(&scripts, HANDLE_FORMAT, vgcanvasHandle);
  131. break;
  132. case 't':
  133. dgsprintxy(&scripts, npts, ppos);
  134. break;
  135. default:
  136. agxbputc(&scripts, before[1]);
  137. break;
  138. }
  139. if (before[1] == '\0') {
  140. break;
  141. }
  142. before += 2;
  143. }
  144. const char *script_value = agxbuse(&scripts);
  145. if (Tcl_GlobalEval(interp, script_value) != TCL_OK)
  146. fprintf(stderr, "%s while in binding: %s\n\n",
  147. Tcl_GetStringResult(interp), script_value);
  148. agxbfree(&scripts);
  149. }
  150. static void triangle_callback(void *vgparg, const point pqr[]) {
  151. vgpane_t *vgp = vgparg;
  152. if (vgp->triangle_cmd) {
  153. const size_t vgcanvasHandle = vgp->index;
  154. expandPercentsEval(vgp->interp, vgp->triangle_cmd, vgcanvasHandle, 3, pqr);
  155. }
  156. }
  157. static char *buildBindings(char *s1, const char *s2)
  158. /*
  159. * previous binding in s1 binding to be added in s2 result in s3
  160. *
  161. * if s2 begins with + then append (separated by \n) else s2 replaces if
  162. * resultant string is null then bindings are deleted
  163. */
  164. {
  165. char *s3;
  166. size_t l;
  167. if (s2[0] == '+') {
  168. if (s1) {
  169. l = strlen(s2) - 1;
  170. if (l) {
  171. agxbuf new = {0};
  172. agxbprint(&new, "%s\n%s", s1, s2 + 1);
  173. free(s1);
  174. return agxbdisown(&new);
  175. } else {
  176. s3 = s1;
  177. }
  178. } else {
  179. l = strlen(s2) - 1;
  180. if (l) {
  181. s3 = gv_strdup(s2 + 1);
  182. } else {
  183. s3 = NULL;
  184. }
  185. }
  186. } else {
  187. free(s1);
  188. l = strlen(s2);
  189. if (l) {
  190. s3 = gv_strdup(s2);
  191. } else {
  192. s3 = NULL;
  193. }
  194. }
  195. return s3;
  196. }
  197. /* convert x and y string args to point */
  198. static int scanpoint(Tcl_Interp * interp, const char *argv[], point * p)
  199. {
  200. if (sscanf(argv[0], "%lg", &(p->x)) != 1) {
  201. Tcl_AppendResult(interp, "invalid x coordinate: \"", argv[0], "\"", NULL);
  202. return TCL_ERROR;
  203. }
  204. if (sscanf(argv[1], "%lg", &(p->y)) != 1) {
  205. Tcl_AppendResult(interp, "invalid y coordinate: \"", argv[1], "\"", NULL);
  206. return TCL_ERROR;
  207. }
  208. return TCL_OK;
  209. }
  210. static point center(point vertex[], size_t n) {
  211. point c;
  212. c.x = c.y = 0;
  213. for (size_t i = 0; i < n; i++) {
  214. c.x += vertex[i].x;
  215. c.y += vertex[i].y;
  216. }
  217. c.x /= (int)n;
  218. c.y /= (int)n;
  219. return c;
  220. }
  221. static double distance(point p, point q)
  222. {
  223. double dx, dy;
  224. dx = p.x - q.x;
  225. dy = p.y - q.y;
  226. return hypot(dx, dy);
  227. }
  228. static point rotate(point c, point p, double alpha)
  229. {
  230. point q;
  231. double beta, r;
  232. r = distance(c, p);
  233. beta = atan2(p.x - c.x, p.y - c.y);
  234. const double sina = sin(beta + alpha);
  235. const double cosa = cos(beta + alpha);
  236. q.x = c.x + r * sina;
  237. q.y = c.y - r * cosa; /* adjust for tk y-down */
  238. return q;
  239. }
  240. static point scale(point c, point p, double gain)
  241. {
  242. point q;
  243. q.x = c.x + gain * (p.x - c.x);
  244. q.y = c.y + gain * (p.y - c.y);
  245. return q;
  246. }
  247. static bool remove_poly(vgpane_t *vgp, int id) {
  248. for (size_t i = 0; i < polys_size(&vgp->poly); i++) {
  249. if (polys_get(&vgp->poly, i).id == id) {
  250. free(polys_get(&vgp->poly, i).boundary.ps);
  251. for (size_t j = i++; i < polys_size(&vgp->poly); i++, j++) {
  252. polys_set(&vgp->poly, j, polys_get(&vgp->poly, i));
  253. }
  254. polys_resize(&vgp->poly, polys_size(&vgp->poly) - 1, (poly){0});
  255. vc_stale(vgp);
  256. return true;
  257. }
  258. }
  259. return false;
  260. }
  261. static int
  262. insert_poly(Tcl_Interp * interp, vgpane_t * vgp, int id, const char *vargv[],
  263. int vargc)
  264. {
  265. poly *np;
  266. int i, result;
  267. np = allocpoly(vgp, id, vargc);
  268. for (i = 0; i < vargc; i += 2) {
  269. result =
  270. scanpoint(interp, &vargv[i],
  271. &(np->boundary.ps[np->boundary.pn]));
  272. if (result != TCL_OK)
  273. return result;
  274. np->boundary.pn++;
  275. }
  276. make_CW(&(np->boundary));
  277. vc_stale(vgp);
  278. return TCL_OK;
  279. }
  280. static void make_barriers(vgpane_t *vgp, int pp, int qp, Pedge_t **barriers,
  281. size_t *n_barriers) {
  282. size_t n = 0;
  283. for (size_t i = 0; i < polys_size(&vgp->poly); i++) {
  284. if (polys_get(&vgp->poly, i).id == pp)
  285. continue;
  286. if (polys_get(&vgp->poly, i).id == qp)
  287. continue;
  288. n += polys_get(&vgp->poly, i).boundary.pn;
  289. }
  290. Pedge_t *bar = gv_calloc(n, sizeof(Pedge_t));
  291. size_t b = 0;
  292. for (size_t i = 0; i < polys_size(&vgp->poly); i++) {
  293. if (polys_get(&vgp->poly, i).id == pp)
  294. continue;
  295. if (polys_get(&vgp->poly, i).id == qp)
  296. continue;
  297. for (size_t j = 0; j < polys_get(&vgp->poly, i).boundary.pn; j++) {
  298. size_t k = j + 1;
  299. if (k >= polys_get(&vgp->poly, i).boundary.pn)
  300. k = 0;
  301. bar[b].a = polys_get(&vgp->poly, i).boundary.ps[j];
  302. bar[b].b = polys_get(&vgp->poly, i).boundary.ps[k];
  303. b++;
  304. }
  305. }
  306. assert(b == n);
  307. *barriers = bar;
  308. *n_barriers = n;
  309. }
  310. /* append the x and y coordinates of a point to the Tcl result */
  311. static void appendpoint(Tcl_Interp * interp, point p)
  312. {
  313. char buf[30];
  314. snprintf(buf, sizeof(buf), "%g", p.x);
  315. Tcl_AppendElement(interp, buf);
  316. snprintf(buf, sizeof(buf), "%g", p.y);
  317. Tcl_AppendElement(interp, buf);
  318. }
  319. /// lookup an existing pane
  320. ///
  321. /// @param panes Collection to search
  322. /// @param handle Text descriptor of the pane
  323. /// @return A pointer to the pane or `NULL` if there is no such pane
  324. static vgpane_t *find_vgpane(vgpanes_t *panes, const char *handle) {
  325. assert(panes != NULL);
  326. assert(handle != NULL);
  327. size_t index;
  328. if (sscanf(handle, HANDLE_FORMAT, &index) != 1) {
  329. return NULL;
  330. }
  331. if (index >= vgpanes_size(panes)) {
  332. return NULL;
  333. }
  334. vgpane_t *const pane = vgpanes_at(panes, index);
  335. if (!pane->valid) {
  336. return NULL;
  337. }
  338. return pane;
  339. }
  340. /// cleanup unused panes where possible
  341. ///
  342. /// Panes need to have a stable identifier (index) for their lifetime. Thus they
  343. /// cannot be removed from a `vgpanes_t` when deallocated because removing them
  344. /// would alter the index of any still-live panes that comes after them.
  345. /// Instead, panes are marked `->valid = false` when deallocated and then later
  346. /// removed here.
  347. ///
  348. /// It is only safe to remove deallocated panes that have no live panes
  349. /// following them, which is exactly what this function does.
  350. ///
  351. /// @param panes Collection to sweep
  352. static void garbage_collect_vgpanes(vgpanes_t *panes) {
  353. assert(panes != NULL);
  354. // shrink list, discarding previously deallocated entries
  355. while (!vgpanes_is_empty(panes)) {
  356. if (!vgpanes_back(panes)->valid) {
  357. (void)vgpanes_pop_back(panes);
  358. }
  359. }
  360. // if this left us with none, fully deallocate to make leak checkers happy
  361. if (vgpanes_is_empty(panes)) {
  362. vgpanes_free(panes);
  363. }
  364. }
  365. /* process vgpane methods */
  366. static int
  367. vgpanecmd(ClientData clientData, Tcl_Interp * interp, int argc,
  368. const char *argv[])
  369. {
  370. (void)clientData;
  371. int vargc, result;
  372. char vbuf[30];
  373. const char **vargv;
  374. vgpane_t *vgp;
  375. point p, q, *ps;
  376. double alpha, gain;
  377. Pvector_t slopes[2];
  378. Ppolyline_t line, spline;
  379. Pedge_t *barriers;
  380. if (argc < 2) {
  381. Tcl_AppendResult(interp, "wrong # args: should be \"",
  382. " ", argv[0], " method ?arg arg ...?\"", NULL);
  383. return TCL_ERROR;
  384. }
  385. if (!(vgp = find_vgpane(&vgpaneTable, argv[0]))) {
  386. Tcl_AppendResult(interp, "Invalid handle: \"", argv[0], "\"", NULL);
  387. return TCL_ERROR;
  388. }
  389. if (strcmp(argv[1], "coords") == 0) {
  390. if (argc < 3) {
  391. Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  392. " ", argv[1], " id ?x1 y1 x2 y2...?\"", NULL);
  393. return TCL_ERROR;
  394. }
  395. if (sscanf(argv[2], "%d", &polyid) != 1) {
  396. Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
  397. return TCL_ERROR;
  398. }
  399. if (argc == 3) {
  400. /* find poly and return its coordinates */
  401. for (size_t i = 0; i < polys_size(&vgp->poly); i++) {
  402. if (polys_get(&vgp->poly, i).id == polyid) {
  403. const size_t n = polys_get(&vgp->poly, i).boundary.pn;
  404. for (size_t j = 0; j < n; j++) {
  405. appendpoint(interp, polys_get(&vgp->poly, i).boundary.ps[j]);
  406. }
  407. return TCL_OK;
  408. }
  409. }
  410. Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
  411. return TCL_ERROR;
  412. }
  413. /* accept either inline or delimited list */
  414. if (argc == 4) {
  415. result =
  416. Tcl_SplitList(interp, argv[3], &vargc,
  417. (const char ***) &vargv);
  418. if (result != TCL_OK) {
  419. return result;
  420. }
  421. } else {
  422. vargc = argc - 3;
  423. vargv = &argv[3];
  424. }
  425. if (!vargc || vargc % 2) {
  426. Tcl_AppendResult(interp,
  427. "There must be a multiple of two terms in the list.", NULL);
  428. return TCL_ERROR;
  429. }
  430. /* remove old poly, add modified polygon to the end with
  431. the same id as the original */
  432. if (!remove_poly(vgp, polyid)) {
  433. Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
  434. return TCL_ERROR;
  435. }
  436. return insert_poly(interp, vgp, polyid, vargv, vargc);
  437. } else if (strcmp(argv[1], "debug") == 0) {
  438. /* debug only */
  439. printf("debug output goes here\n");
  440. return TCL_OK;
  441. } else if (strcmp(argv[1], "delete") == 0) {
  442. /* delete a vgpane and all memory associated with it */
  443. free(vgp->triangle_cmd);
  444. if (vgp->vc)
  445. Pobsclose(vgp->vc);
  446. polys_free(&vgp->poly);
  447. Tcl_DeleteCommand(interp, argv[0]);
  448. vgp->valid = false;
  449. garbage_collect_vgpanes(&vgpaneTable);
  450. return TCL_OK;
  451. } else if (strcmp(argv[1], "find") == 0) {
  452. /* find the polygon that the point is inside and return it
  453. id, or null */
  454. if (argc < 3) {
  455. Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  456. " ", argv[1], " x y\"", NULL);
  457. return TCL_ERROR;
  458. }
  459. if (argc == 3) {
  460. result =
  461. Tcl_SplitList(interp, argv[2], &vargc,
  462. (const char ***) &vargv);
  463. if (result != TCL_OK) {
  464. return result;
  465. }
  466. } else {
  467. vargc = argc - 2;
  468. vargv = &argv[2];
  469. }
  470. result = scanpoint(interp, &vargv[0], &p);
  471. if (result != TCL_OK)
  472. return result;
  473. /* determine the polygons (if any) that contain the point */
  474. for (size_t i = 0; i < polys_size(&vgp->poly); i++) {
  475. if (in_poly(polys_get(&vgp->poly, i).boundary, p)) {
  476. snprintf(vbuf, sizeof(vbuf), "%d", polys_get(&vgp->poly, i).id);
  477. Tcl_AppendElement(interp, vbuf);
  478. }
  479. }
  480. return TCL_OK;
  481. } else if (strcmp(argv[1], "insert") == 0) {
  482. /* add poly to end poly list, and it coordinates to the end of
  483. the point list */
  484. if ((argc < 3)) {
  485. Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  486. " ", argv[1], " x1 y1 x2 y2 ...\"", NULL);
  487. return TCL_ERROR;
  488. }
  489. /* accept either inline or delimited list */
  490. if (argc == 3) {
  491. result =
  492. Tcl_SplitList(interp, argv[2], &vargc,
  493. (const char ***) &vargv);
  494. if (result != TCL_OK) {
  495. return result;
  496. }
  497. } else {
  498. vargc = argc - 2;
  499. vargv = &argv[2];
  500. }
  501. if (!vargc || vargc % 2) {
  502. Tcl_AppendResult(interp,
  503. "There must be a multiple of two terms in the list.", NULL);
  504. return TCL_ERROR;
  505. }
  506. polyid++;
  507. result = insert_poly(interp, vgp, polyid, vargv, vargc);
  508. if (result != TCL_OK)
  509. return result;
  510. snprintf(vbuf, sizeof(vbuf), "%d", polyid);
  511. Tcl_AppendResult(interp, vbuf, NULL);
  512. return TCL_OK;
  513. } else if (strcmp(argv[1], "list") == 0) {
  514. /* return list of polygon ids */
  515. for (size_t i = 0; i < polys_size(&vgp->poly); i++) {
  516. snprintf(vbuf, sizeof(vbuf), "%d", polys_get(&vgp->poly, i).id);
  517. Tcl_AppendElement(interp, vbuf);
  518. }
  519. return TCL_OK;
  520. } else if (strcmp(argv[1], "path") == 0) {
  521. /* return a list of points corresponding to the shortest path
  522. that does not cross the remaining "visible" polygons. */
  523. if (argc < 3) {
  524. Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  525. " ", argv[1], " x1 y1 x2 y2\"", NULL);
  526. return TCL_ERROR;
  527. }
  528. if (argc == 3) {
  529. result =
  530. Tcl_SplitList(interp, argv[2], &vargc,
  531. (const char ***) &vargv);
  532. if (result != TCL_OK) {
  533. return result;
  534. }
  535. } else {
  536. vargc = argc - 2;
  537. vargv = &argv[2];
  538. }
  539. if (vargc < 4) {
  540. Tcl_AppendResult(interp,
  541. "invalid points: should be: \"x1 y1 x2 y2\"", NULL);
  542. return TCL_ERROR;
  543. }
  544. result = scanpoint(interp, &vargv[0], &p);
  545. if (result != TCL_OK)
  546. return result;
  547. result = scanpoint(interp, &vargv[2], &q);
  548. if (result != TCL_OK)
  549. return result;
  550. /* only recompute the visibility graph if we have to */
  551. if (vc_refresh(vgp)) {
  552. Pobspath(vgp->vc, p, POLYID_UNKNOWN, q, POLYID_UNKNOWN, &line);
  553. for (size_t i = 0; i < line.pn; i++) {
  554. appendpoint(interp, line.ps[i]);
  555. }
  556. }
  557. return TCL_OK;
  558. } else if (strcmp(argv[1], "bind") == 0) {
  559. if (argc < 2 || argc > 4) {
  560. Tcl_AppendResult(interp, "wrong # args: should be \"",
  561. argv[0], " bind triangle ?command?\"", NULL);
  562. return TCL_ERROR;
  563. }
  564. if (argc == 2) {
  565. Tcl_AppendElement(interp, "triangle");
  566. return TCL_OK;
  567. }
  568. char *s = NULL;
  569. if (strcmp(argv[2], "triangle") == 0) {
  570. s = vgp->triangle_cmd;
  571. if (argc == 4)
  572. vgp->triangle_cmd = buildBindings(s, argv[3]);
  573. } else {
  574. Tcl_AppendResult(interp, "unknown event \"", argv[2],
  575. "\": must be one of:\n\ttriangle.", NULL);
  576. return TCL_ERROR;
  577. }
  578. if (argc == 3)
  579. Tcl_AppendResult(interp, s, NULL);
  580. return TCL_OK;
  581. } else if (strcmp(argv[1], "bpath") == 0) {
  582. /* return a list of points corresponding to the shortest path
  583. that does not cross the remaining "visible" polygons. */
  584. if (argc < 3) {
  585. Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  586. " ", argv[1], " x1 y1 x2 y2\"", NULL);
  587. return TCL_ERROR;
  588. }
  589. if (argc == 3) {
  590. result =
  591. Tcl_SplitList(interp, argv[2], &vargc,
  592. (const char ***) &vargv);
  593. if (result != TCL_OK) {
  594. return result;
  595. }
  596. } else {
  597. vargc = argc - 2;
  598. vargv = &argv[2];
  599. }
  600. if ((vargc < 4)) {
  601. Tcl_AppendResult(interp,
  602. "invalid points: should be: \"x1 y1 x2 y2\"", NULL);
  603. return TCL_ERROR;
  604. }
  605. result = scanpoint(interp, &vargv[0], &p);
  606. if (result != TCL_OK)
  607. return result;
  608. result = scanpoint(interp, &vargv[2], &q);
  609. if (result != TCL_OK)
  610. return result;
  611. /* determine the polygons (if any) that contain the endpoints */
  612. int pp = POLYID_NONE;
  613. int qp = POLYID_NONE;
  614. for (size_t i = 0; i < polys_size(&vgp->poly); i++) {
  615. const poly tpp = polys_get(&vgp->poly, i);
  616. if (pp == POLYID_NONE && in_poly(tpp.boundary, p))
  617. pp = (int)i;
  618. if (qp == POLYID_NONE && in_poly(tpp.boundary, q))
  619. qp = (int)i;
  620. }
  621. if (vc_refresh(vgp)) {
  622. Pobspath(vgp->vc, p, POLYID_UNKNOWN, q, POLYID_UNKNOWN, &line);
  623. size_t n_barriers;
  624. make_barriers(vgp, pp, qp, &barriers, &n_barriers);
  625. slopes[0].x = slopes[0].y = 0.0;
  626. slopes[1].x = slopes[1].y = 0.0;
  627. Proutespline(barriers, n_barriers, line, slopes, &spline);
  628. for (size_t i = 0; i < spline.pn; i++) {
  629. appendpoint(interp, spline.ps[i]);
  630. }
  631. }
  632. return TCL_OK;
  633. } else if (strcmp(argv[1], "bbox") == 0) {
  634. if (argc < 3) {
  635. Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  636. " ", argv[1], " id\"", NULL);
  637. return TCL_ERROR;
  638. }
  639. if (sscanf(argv[2], "%d", &polyid) != 1) {
  640. Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
  641. return TCL_ERROR;
  642. }
  643. for (size_t i = 0; i < polys_size(&vgp->poly); i++) {
  644. if (polys_get(&vgp->poly, i).id == polyid) {
  645. Ppoly_t pp = polys_get(&vgp->poly, i).boundary;
  646. point LL, UR;
  647. LL = UR = pp.ps[0];
  648. for (size_t j = 1; j < pp.pn; j++) {
  649. p = pp.ps[j];
  650. UR.x = fmax(UR.x, p.x);
  651. UR.y = fmax(UR.y, p.y);
  652. LL.x = fmin(LL.x, p.x);
  653. LL.y = fmin(LL.y, p.y);
  654. }
  655. appendpoint(interp, LL);
  656. appendpoint(interp, UR);
  657. return TCL_OK;
  658. }
  659. }
  660. Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
  661. return TCL_ERROR;
  662. } else if (strcmp(argv[1], "center") == 0) {
  663. if (argc < 3) {
  664. Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  665. " ", argv[1], " id\"", NULL);
  666. return TCL_ERROR;
  667. }
  668. if (sscanf(argv[2], "%d", &polyid) != 1) {
  669. Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
  670. return TCL_ERROR;
  671. }
  672. for (size_t i = 0; i < polys_size(&vgp->poly); i++) {
  673. if (polys_get(&vgp->poly, i).id == polyid) {
  674. appendpoint(interp, center(polys_get(&vgp->poly, i).boundary.ps,
  675. polys_get(&vgp->poly, i).boundary.pn));
  676. return TCL_OK;
  677. }
  678. }
  679. Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
  680. return TCL_ERROR;
  681. } else if (strcmp(argv[1], "triangulate") == 0) {
  682. if (argc < 3) {
  683. Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  684. " ", argv[1], " id\"", NULL);
  685. return TCL_ERROR;
  686. }
  687. if (sscanf(argv[2], "%d", &polyid) != 1) {
  688. Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
  689. return TCL_ERROR;
  690. }
  691. for (size_t i = 0; i < polys_size(&vgp->poly); i++) {
  692. if (polys_get(&vgp->poly, i).id == polyid) {
  693. Ppoly_t *polygon = &polys_at(&vgp->poly, i)->boundary;
  694. if (polygon->pn < 3) {
  695. Tcl_AppendResult(interp, "polygon ", argv[2], " has fewer than 3 points "
  696. "and thus cannot be triangulated", NULL);
  697. return TCL_ERROR;
  698. }
  699. Ptriangulate(polygon, triangle_callback, vgp);
  700. return TCL_OK;
  701. }
  702. }
  703. Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
  704. return TCL_ERROR;
  705. } else if (strcmp(argv[1], "rotate") == 0) {
  706. if (argc < 4) {
  707. Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  708. " ", argv[1], " id alpha\"", NULL);
  709. return TCL_ERROR;
  710. }
  711. if (sscanf(argv[2], "%d", &polyid) != 1) {
  712. Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
  713. return TCL_ERROR;
  714. }
  715. if (sscanf(argv[3], "%lg", &alpha) != 1) {
  716. Tcl_AppendResult(interp, "not an angle in radians: ", argv[3], NULL);
  717. return TCL_ERROR;
  718. }
  719. for (size_t i = 0; i < polys_size(&vgp->poly); i++) {
  720. if (polys_get(&vgp->poly, i).id == polyid) {
  721. const size_t n = polys_get(&vgp->poly, i).boundary.pn;
  722. ps = polys_get(&vgp->poly, i).boundary.ps;
  723. p = center(ps, n);
  724. for (size_t j = 0; j < n; j++) {
  725. appendpoint(interp, rotate(p, ps[j], alpha));
  726. }
  727. return TCL_OK;
  728. }
  729. }
  730. Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
  731. return TCL_ERROR;
  732. } else if (strcmp(argv[1], "scale") == 0) {
  733. if (argc < 4) {
  734. Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  735. " ", argv[1], " id gain\"", NULL);
  736. return TCL_ERROR;
  737. }
  738. if (sscanf(argv[2], "%d", &polyid) != 1) {
  739. Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
  740. return TCL_ERROR;
  741. }
  742. if (sscanf(argv[3], "%lg", &gain) != 1) {
  743. Tcl_AppendResult(interp, "not a number: ", argv[3], NULL);
  744. return TCL_ERROR;
  745. }
  746. for (size_t i = 0; i < polys_size(&vgp->poly); i++) {
  747. if (polys_get(&vgp->poly, i).id == polyid) {
  748. const size_t n = polys_get(&vgp->poly, i).boundary.pn;
  749. ps = polys_get(&vgp->poly, i).boundary.ps;
  750. p = center(ps, n);
  751. for (size_t j = 0; j < n; j++) {
  752. appendpoint(interp, scale(p, ps[j], gain));
  753. }
  754. return TCL_OK;
  755. }
  756. }
  757. Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
  758. return TCL_ERROR;
  759. } else if (strcmp(argv[1], "remove") == 0) {
  760. if (argc < 3) {
  761. Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  762. " ", argv[1], " id\"", NULL);
  763. return TCL_ERROR;
  764. }
  765. if (sscanf(argv[2], "%d", &polyid) != 1) {
  766. Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
  767. return TCL_ERROR;
  768. }
  769. if (remove_poly(vgp, polyid))
  770. return TCL_OK;
  771. Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
  772. return TCL_ERROR;
  773. }
  774. Tcl_AppendResult(interp, "bad method \"", argv[1],
  775. "\" must be one of:",
  776. "\n\tbbox, bind, bpath, center, coords, delete, find,",
  777. "\n\tinsert, list, path, remove, rotate, scale, triangulate.", NULL);
  778. return TCL_ERROR;
  779. }
  780. static int
  781. vgpane(ClientData clientData, Tcl_Interp * interp, int argc, const char *argv[])
  782. {
  783. (void)clientData;
  784. (void)argc;
  785. (void)argv;
  786. const vgpane_t vg = {.interp = interp};
  787. vgpanes_append(&vgpaneTable, vg);
  788. vgpane_t *const vgp = vgpanes_back(&vgpaneTable);
  789. vgp->index = vgpanes_size(&vgpaneTable) - 1;
  790. vgp->valid = true;
  791. agxbuf buffer = {0};
  792. agxbprint(&buffer, HANDLE_FORMAT, vgp->index);
  793. const char *const vbuf = agxbuse(&buffer);
  794. Tcl_CreateCommand(interp, vbuf, vgpanecmd, NULL, NULL);
  795. Tcl_AppendResult(interp, vbuf, NULL);
  796. agxbfree(&buffer);
  797. return TCL_OK;
  798. }
  799. int Tclpathplan_Init(Tcl_Interp *interp);
  800. int Tclpathplan_Init(Tcl_Interp * interp)
  801. {
  802. #ifdef USE_TCL_STUBS
  803. if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
  804. return TCL_ERROR;
  805. }
  806. #else
  807. if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 0) == NULL) {
  808. return TCL_ERROR;
  809. }
  810. #endif
  811. // inter-release Graphviz versions have a number including '~dev.' that does
  812. // not comply with TCL version number rules, so replace this with 'b'
  813. char adjusted_version[sizeof(PACKAGE_VERSION)] = PACKAGE_VERSION;
  814. char *tilde_dev = strstr(adjusted_version, "~dev.");
  815. if (tilde_dev != NULL) {
  816. *tilde_dev = 'b';
  817. memmove(tilde_dev + 1, tilde_dev + strlen("~dev."),
  818. strlen(tilde_dev + strlen("~dev.")) + 1);
  819. }
  820. if (Tcl_PkgProvide(interp, "Tclpathplan", adjusted_version) != TCL_OK) {
  821. return TCL_ERROR;
  822. }
  823. Tcl_CreateCommand(interp, "vgpane", vgpane, NULL, NULL);
  824. return TCL_OK;
  825. }
  826. int Tclpathplan_SafeInit(Tcl_Interp *interp);
  827. int Tclpathplan_SafeInit(Tcl_Interp * interp)
  828. {
  829. return Tclpathplan_Init(interp);
  830. }