Graphviz 16.1.1~dev.20261030.0125
Loading...
Searching...
No Matches
tclpathplan.c
Go to the documentation of this file.
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 v2.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10
11/*
12 * Tcl binding to drive Stephen North's and
13 * Emden Gansner's shortest path code.
14 *
15 * ellson@graphviz.org October 2nd, 1996
16 */
17
18#include "config.h"
19
20#include "../tcl-compat.h"
21#include <sys/types.h>
22#include <stdbool.h>
23#include <stdint.h>
24#include <stdlib.h>
25#include <string.h>
26
27#include <assert.h>
28#include <limits.h>
29#include "makecw.h"
30#include <math.h>
31#include <pathplan/pathutil.h>
32#include <pathplan/vispath.h>
33#include <pathplan/tri.h>
34#include "Plegal_arrangement.h"
35#include <tcl.h>
36#include <util/agxbuf.h>
37#include <util/alloc.h>
38#include <util/itos.h>
39#include <util/list.h>
40#include <util/prisize_t.h>
41
42#if (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION >= 6) || TCL_MAJOR_VERSION > 8
43#else
44#ifndef Tcl_GetStringResult
45#define Tcl_GetStringResult(interp) interp->result
46#endif
47#endif
48
50
51typedef struct poly_s {
52 int id;
55
57#define HANDLE_FORMAT ("vgpane%" PRISIZE_T)
58
59typedef struct {
60 LIST(poly) poly; // set of polygons
61 vconfig_t *vc; /* visibility graph handle */
62 Tcl_Interp *interp; /* interpreter that owns the binding */
63 char *triangle_cmd; /* why is this here any more */
64 size_t index;
65 bool valid;
66} vgpane_t;
67
68typedef LIST(vgpane_t) vgpanes_t;
69
70static vgpanes_t vgpaneTable;
71
72static int polyid = 0; /* unique and unchanging id for each poly */
73
74static poly *allocpoly(vgpane_t *vgp, int id, Tcl_Size npts) {
75 LIST_APPEND(&vgp->poly, (poly){.id = id});
76 poly *rv = LIST_BACK(&vgp->poly);
77 rv->boundary.pn = 0;
78 rv->boundary.ps = gv_calloc(npts, sizeof(point));
79 return rv;
80}
81
82static void vc_stale(vgpane_t * vgp)
83{
84 if (vgp->vc) {
85 Pobsclose(vgp->vc);
86 vgp->vc = NULL;
87 }
88}
89
90static int vc_refresh(vgpane_t * vgp)
91{
92 if (vgp->vc == NULL) {
93 Ppoly_t **obs = gv_calloc(LIST_SIZE(&vgp->poly), sizeof(Ppoly_t*));
94 for (size_t i = 0; i < LIST_SIZE(&vgp->poly); i++)
95 obs[i] = &LIST_AT(&vgp->poly, i)->boundary;
96 if (!Plegal_arrangement(obs, LIST_SIZE(&vgp->poly)))
97 fprintf(stderr, "bad arrangement\n");
98 else
99 vgp->vc = Pobsopen(obs, (int)LIST_SIZE(&vgp->poly));
100 free(obs);
101 }
102 return vgp->vc != NULL;
103}
104
105static void dgsprintxy(agxbuf *result, int npts, const point p[]) {
106 int i;
107
108 assert(npts > 1);
109 if (agxblen(result) > 0) {
110 agxbputc(result, ' ');
111 }
112 agxbputc(result, '{');
113 const char *separator = "";
114 for (i = 0; i < npts; i++) {
115 agxbprint(result, "%s%g %g", separator, p[i].x, p[i].y);
116 separator = " ";
117 }
118 agxbputc(result, '}');
119}
120
126static void expandPercentsEval(Tcl_Interp *interp, char *before,
127 size_t vgcanvasHandle, int npts,
128 const point *ppos) {
129 agxbuf scripts = {0};
130
131 while (1) {
132 /*
133 * Find everything up to the next % character and append it to the
134 * result string.
135 */
136
137 char *string = strchr(before, '%');
138 if (string != NULL) {
139 agxbput_n(&scripts, before, (size_t)(string - before));
140 before = string;
141 }
142 if (*before == 0) {
143 break;
144 }
145 /*
146 * There's a percent sequence here. Process it.
147 */
148
149 switch (before[1]) {
150 case 'r':
151 agxbprint(&scripts, HANDLE_FORMAT, vgcanvasHandle);
152 break;
153 case 't':
154 dgsprintxy(&scripts, npts, ppos);
155 break;
156 default:
157 agxbputc(&scripts, before[1]);
158 break;
159 }
160 if (before[1] == '\0') {
161 break;
162 }
163 before += 2;
164 }
165 const char *script_value = agxbuse(&scripts);
166 if (Tcl_GlobalEval(interp, script_value) != TCL_OK)
167 fprintf(stderr, "%s while in binding: %s\n\n",
168 Tcl_GetStringResult(interp), script_value);
169 agxbfree(&scripts);
170}
171
172static void triangle_callback(void *vgparg, const point pqr[]) {
173 vgpane_t *vgp = vgparg;
174 if (vgp->triangle_cmd) {
175 const size_t vgcanvasHandle = vgp->index;
176 expandPercentsEval(vgp->interp, vgp->triangle_cmd, vgcanvasHandle, 3, pqr);
177 }
178}
179
180static char *buildBindings(char *s1, const char *s2)
181/*
182 * previous binding in s1 binding to be added in s2 result in s3
183 *
184 * if s2 begins with + then append (separated by \n) else s2 replaces if
185 * resultant string is null then bindings are deleted
186 */
187{
188 char *s3;
189 size_t l;
190
191 if (s2[0] == '+') {
192 if (s1) {
193 l = strlen(s2) - 1;
194 if (l) {
195 agxbuf new = {0};
196 agxbprint(&new, "%s\n%s", s1, s2 + 1);
197 free(s1);
198 return agxbdisown(&new);
199 } else {
200 s3 = s1;
201 }
202 } else {
203 l = strlen(s2) - 1;
204 if (l) {
205 s3 = gv_strdup(s2 + 1);
206 } else {
207 s3 = NULL;
208 }
209 }
210 } else {
211 free(s1);
212 l = strlen(s2);
213 if (l) {
214 s3 = gv_strdup(s2);
215 } else {
216 s3 = NULL;
217 }
218 }
219 return s3;
220}
221
222
223
224/* convert x and y string args to point */
225static int scanpoint(Tcl_Interp * interp, const char *argv[], point * p)
226{
227 if (sscanf(argv[0], "%lg", &p->x) != 1) {
228 Tcl_AppendResult(interp, "invalid x coordinate: \"", argv[0], "\"", NULL);
229 return TCL_ERROR;
230 }
231 if (sscanf(argv[1], "%lg", &p->y) != 1) {
232 Tcl_AppendResult(interp, "invalid y coordinate: \"", argv[1], "\"", NULL);
233 return TCL_ERROR;
234 }
235 return TCL_OK;
236}
237
238static point center(point vertex[], size_t n) {
239 point c;
240
241 c.x = c.y = 0;
242 for (size_t i = 0; i < n; i++) {
243 c.x += vertex[i].x;
244 c.y += vertex[i].y;
245 }
246 c.x /= (int)n;
247 c.y /= (int)n;
248 return c;
249}
250
251static double distance(point p, point q)
252{
253 double dx, dy;
254
255 dx = p.x - q.x;
256 dy = p.y - q.y;
257 return hypot(dx, dy);
258}
259
260static point rotate(point c, point p, double alpha)
261{
262 point q;
263 double beta, r;
264
265 r = distance(c, p);
266 beta = atan2(p.x - c.x, p.y - c.y);
267 const double sina = sin(beta + alpha);
268 const double cosa = cos(beta + alpha);
269 q.x = c.x + r * sina;
270 q.y = c.y - r * cosa; /* adjust for tk y-down */
271 return q;
272}
273
274static point scale(point c, point p, double gain)
275{
276 point q;
277
278 q.x = c.x + gain * (p.x - c.x);
279 q.y = c.y + gain * (p.y - c.y);
280 return q;
281}
282
283static bool remove_poly(vgpane_t *vgp, int id) {
284 for (size_t i = 0; i < LIST_SIZE(&vgp->poly); i++) {
285 if (LIST_GET(&vgp->poly, i).id == id) {
286 free(LIST_GET(&vgp->poly, i).boundary.ps);
287 for (size_t j = i++; i < LIST_SIZE(&vgp->poly); i++, j++) {
288 LIST_SET(&vgp->poly, j, LIST_GET(&vgp->poly, i));
289 }
290 LIST_DROP_BACK(&vgp->poly);
291 vc_stale(vgp);
292 return true;
293 }
294 }
295 return false;
296}
297
298static int insert_poly(Tcl_Interp *interp, vgpane_t *vgp, int id,
299 const char *vargv[], Tcl_Size vargc) {
300 poly *np;
301 int result;
302
303 np = allocpoly(vgp, id, vargc);
304 for (Tcl_Size i = 0; i < vargc; i += 2) {
305 result = scanpoint(interp, &vargv[i], &np->boundary.ps[np->boundary.pn]);
306 if (result != TCL_OK)
307 return result;
308 np->boundary.pn++;
309 }
310 make_CW(&np->boundary);
311 vc_stale(vgp);
312 return TCL_OK;
313}
314
315static void make_barriers(vgpane_t *vgp, int pp, int qp, Pedge_t **barriers,
316 size_t *n_barriers) {
317
318 size_t n = 0;
319 for (size_t i = 0; i < LIST_SIZE(&vgp->poly); i++) {
320 if (LIST_GET(&vgp->poly, i).id == pp)
321 continue;
322 if (LIST_GET(&vgp->poly, i).id == qp)
323 continue;
324 n += LIST_GET(&vgp->poly, i).boundary.pn;
325 }
326 Pedge_t *bar = gv_calloc(n, sizeof(Pedge_t));
327 size_t b = 0;
328 for (size_t i = 0; i < LIST_SIZE(&vgp->poly); i++) {
329 if (LIST_GET(&vgp->poly, i).id == pp)
330 continue;
331 if (LIST_GET(&vgp->poly, i).id == qp)
332 continue;
333 for (size_t j = 0; j < LIST_GET(&vgp->poly, i).boundary.pn; j++) {
334 size_t k = j + 1;
335 if (k >= LIST_GET(&vgp->poly, i).boundary.pn)
336 k = 0;
337 bar[b].a = LIST_GET(&vgp->poly, i).boundary.ps[j];
338 bar[b].b = LIST_GET(&vgp->poly, i).boundary.ps[k];
339 b++;
340 }
341 }
342 assert(b == n);
343 *barriers = bar;
344 *n_barriers = n;
345}
346
347/* append the x and y coordinates of a point to the Tcl result */
348static void appendpoint(Tcl_Interp * interp, point p)
349{
350 char buf[30];
351
352 snprintf(buf, sizeof(buf), "%g", p.x);
353 Tcl_AppendElement(interp, buf);
354 snprintf(buf, sizeof(buf), "%g", p.y);
355 Tcl_AppendElement(interp, buf);
356}
357
363static vgpane_t *find_vgpane(vgpanes_t *panes, const char *handle) {
364 assert(panes != NULL);
365 assert(handle != NULL);
366
367 size_t index;
368 if (sscanf(handle, HANDLE_FORMAT, &index) != 1) {
369 return NULL;
370 }
371 if (index >= LIST_SIZE(panes)) {
372 return NULL;
373 }
374 vgpane_t *const pane = LIST_AT(panes, index);
375 if (!pane->valid) {
376 return NULL;
377 }
378
379 return pane;
380}
381
394static void garbage_collect_vgpanes(vgpanes_t *panes) {
395 assert(panes != NULL);
396
397 // shrink list, discarding previously deallocated entries
398 while (!LIST_IS_EMPTY(panes)) {
399 if (!LIST_BACK(panes)->valid) {
400 LIST_DROP_BACK(panes);
401 }
402 }
403
404 // if this left us with none, fully deallocate to make leak checkers happy
405 if (LIST_IS_EMPTY(panes)) {
406 LIST_FREE(panes);
407 }
408}
409
410/* process vgpane methods */
411static int
412vgpanecmd(ClientData clientData, Tcl_Interp * interp, int argc,
413 const char *argv[])
414{
415 (void)clientData;
416
417 int result;
418 vgpane_t *vgp;
419 point p, q, *ps;
420 double alpha, gain;
421 Ppolyline_t line, spline;
422 Pedge_t *barriers;
423
424 if (argc < 2) {
425 Tcl_AppendResult(interp, "wrong # args: should be \"",
426 " ", argv[0], " method ?arg arg ...?\"", NULL);
427 return TCL_ERROR;
428 }
429 if (!(vgp = find_vgpane(&vgpaneTable, argv[0]))) {
430 Tcl_AppendResult(interp, "Invalid handle: \"", argv[0], "\"", NULL);
431 return TCL_ERROR;
432 }
433
434 if (strcmp(argv[1], "coords") == 0) {
435 if (argc < 3) {
436 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
437 " ", argv[1], " id ?x1 y1 x2 y2...?\"", NULL);
438 return TCL_ERROR;
439 }
440 if (sscanf(argv[2], "%d", &polyid) != 1) {
441 Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
442 return TCL_ERROR;
443 }
444 if (argc == 3) {
445 /* find poly and return its coordinates */
446 for (size_t i = 0; i < LIST_SIZE(&vgp->poly); i++) {
447 if (LIST_GET(&vgp->poly, i).id == polyid) {
448 const size_t n = LIST_GET(&vgp->poly, i).boundary.pn;
449 for (size_t j = 0; j < n; j++) {
450 appendpoint(interp, LIST_GET(&vgp->poly, i).boundary.ps[j]);
451 }
452 return TCL_OK;
453 }
454 }
455 Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
456 return TCL_ERROR;
457 }
458 /* accept either inline or delimited list */
459 Tcl_Size vargc;
460 const char **vargv;
461 bool vargv_needs_free = false;
462 if (argc == 4) {
463 result = Tcl_SplitList(interp, argv[3], &vargc, &vargv);
464 if (result != TCL_OK) {
465 return result;
466 }
467 vargv_needs_free = true;
468 } else {
469 vargc = (Tcl_Size)argc - 3;
470 vargv = &argv[3];
471 }
472 if (!vargc || vargc % 2) {
473 Tcl_AppendResult(interp,
474 "There must be a multiple of two terms in the list.", NULL);
475 if (vargv_needs_free) {
476 Tcl_Free((char *)vargv);
477 }
478 return TCL_ERROR;
479 }
480
481 /* remove old poly, add modified polygon to the end with
482 the same id as the original */
483
484 if (!remove_poly(vgp, polyid)) {
485 Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
486 if (vargv_needs_free) {
487 Tcl_Free((char *)vargv);
488 }
489 return TCL_ERROR;
490 }
491
492 result = insert_poly(interp, vgp, polyid, vargv, vargc);
493 if (vargv_needs_free) {
494 Tcl_Free((char *)vargv);
495 }
496 return result;
497
498 } else if (strcmp(argv[1], "debug") == 0) {
499 /* debug only */
500 printf("debug output goes here\n");
501 return TCL_OK;
502
503 } else if (strcmp(argv[1], "delete") == 0) {
504 /* delete a vgpane and all memory associated with it */
505 free(vgp->triangle_cmd);
506 if (vgp->vc)
507 Pobsclose(vgp->vc);
508 LIST_FREE(&vgp->poly);
509 Tcl_DeleteCommand(interp, argv[0]);
510 vgp->valid = false;
511 garbage_collect_vgpanes(&vgpaneTable);
512 return TCL_OK;
513
514 } else if (strcmp(argv[1], "find") == 0) {
515 /* find the polygon that the point is inside and return it
516 id, or null */
517 if (argc < 3) {
518 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
519 " ", argv[1], " x y\"", NULL);
520 return TCL_ERROR;
521 }
522 const char **vargv;
523 bool vargv_needs_free = false;
524 if (argc == 3) {
525 result = Tcl_SplitList(interp, argv[2], &(Tcl_Size){0}, &vargv);
526 if (result != TCL_OK) {
527 return result;
528 }
529 vargv_needs_free = true;
530 } else {
531 vargv = &argv[2];
532 }
533 result = scanpoint(interp, &vargv[0], &p);
534 if (vargv_needs_free) {
535 Tcl_Free((char *)vargv);
536 }
537 if (result != TCL_OK)
538 return result;
539
540 /* determine the polygons (if any) that contain the point */
541 for (size_t i = 0; i < LIST_SIZE(&vgp->poly); i++) {
542 if (in_poly(LIST_GET(&vgp->poly, i).boundary, p)) {
543 Tcl_AppendElement(interp, ITOS(LIST_GET(&vgp->poly, i).id));
544 }
545 }
546 return TCL_OK;
547
548 } else if (strcmp(argv[1], "insert") == 0) {
549 /* add poly to end poly list, and it coordinates to the end of
550 the point list */
551 if (argc < 3) {
552 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
553 " ", argv[1], " x1 y1 x2 y2 ...\"", NULL);
554 return TCL_ERROR;
555 }
556 /* accept either inline or delimited list */
557 Tcl_Size vargc;
558 const char **vargv;
559 bool vargv_needs_free = false;
560 if (argc == 3) {
561 result = Tcl_SplitList(interp, argv[2], &vargc, &vargv);
562 if (result != TCL_OK) {
563 return result;
564 }
565 vargv_needs_free = true;
566 } else {
567 vargc = (Tcl_Size)argc - 2;
568 vargv = &argv[2];
569 }
570
571 if (!vargc || vargc % 2) {
572 Tcl_AppendResult(interp,
573 "There must be a multiple of two terms in the list.", NULL);
574 if (vargv_needs_free) {
575 Tcl_Free((char *)vargv);
576 }
577 return TCL_ERROR;
578 }
579
580 polyid++;
581
582 result = insert_poly(interp, vgp, polyid, vargv, vargc);
583 if (vargv_needs_free) {
584 Tcl_Free((char *)vargv);
585 }
586 if (result != TCL_OK)
587 return result;
588
589 Tcl_AppendResult(interp, ITOS(polyid), NULL);
590 return TCL_OK;
591
592 } else if (strcmp(argv[1], "list") == 0) {
593 /* return list of polygon ids */
594 for (size_t i = 0; i < LIST_SIZE(&vgp->poly); i++) {
595 Tcl_AppendElement(interp, ITOS(LIST_GET(&vgp->poly, i).id));
596 }
597 return TCL_OK;
598
599 } else if (strcmp(argv[1], "path") == 0) {
600 /* return a list of points corresponding to the shortest path
601 that does not cross the remaining "visible" polygons. */
602 if (argc < 3) {
603 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
604 " ", argv[1], " x1 y1 x2 y2\"", NULL);
605 return TCL_ERROR;
606 }
607 Tcl_Size vargc;
608 const char **vargv;
609 bool vargv_needs_free = false;
610 if (argc == 3) {
611 result = Tcl_SplitList(interp, argv[2], &vargc, &vargv);
612 if (result != TCL_OK) {
613 return result;
614 }
615 vargv_needs_free = true;
616 } else {
617 vargc = (Tcl_Size)argc - 2;
618 vargv = &argv[2];
619 }
620 if (vargc < 4) {
621 Tcl_AppendResult(interp,
622 "invalid points: should be: \"x1 y1 x2 y2\"", NULL);
623 if (vargv_needs_free) {
624 Tcl_Free((char *)vargv);
625 }
626 return TCL_ERROR;
627 }
628 result = scanpoint(interp, &vargv[0], &p);
629 if (result != TCL_OK) {
630 if (vargv_needs_free) {
631 Tcl_Free((char *)vargv);
632 }
633 return result;
634 }
635 result = scanpoint(interp, &vargv[2], &q);
636 if (vargv_needs_free) {
637 Tcl_Free((char *)vargv);
638 }
639 if (result != TCL_OK)
640 return result;
641
642 /* only recompute the visibility graph if we have to */
643 if (vc_refresh(vgp)) {
644 Pobspath(vgp->vc, p, POLYID_UNKNOWN, q, POLYID_UNKNOWN, &line);
645
646 for (size_t i = 0; i < line.pn; i++) {
647 appendpoint(interp, line.ps[i]);
648 }
649 }
650
651 return TCL_OK;
652
653 } else if (strcmp(argv[1], "bind") == 0) {
654 if (argc < 2 || argc > 4) {
655 Tcl_AppendResult(interp, "wrong # args: should be \"",
656 argv[0], " bind triangle ?command?\"", NULL);
657 return TCL_ERROR;
658 }
659 if (argc == 2) {
660 Tcl_AppendElement(interp, "triangle");
661 return TCL_OK;
662 }
663 char *s = NULL;
664 if (strcmp(argv[2], "triangle") == 0) {
665 s = vgp->triangle_cmd;
666 if (argc == 4)
667 vgp->triangle_cmd = buildBindings(s, argv[3]);
668 } else {
669 Tcl_AppendResult(interp, "unknown event \"", argv[2],
670 "\": must be one of:\n\ttriangle.", NULL);
671 return TCL_ERROR;
672 }
673 if (argc == 3)
674 Tcl_AppendResult(interp, s, NULL);
675 return TCL_OK;
676
677 } else if (strcmp(argv[1], "bpath") == 0) {
678 /* return a list of points corresponding to the shortest path
679 that does not cross the remaining "visible" polygons. */
680 if (argc < 3) {
681 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
682 " ", argv[1], " x1 y1 x2 y2\"", NULL);
683 return TCL_ERROR;
684 }
685 Tcl_Size vargc;
686 const char **vargv;
687 bool vargv_needs_free = false;
688 if (argc == 3) {
689 result = Tcl_SplitList(interp, argv[2], &vargc, &vargv);
690 if (result != TCL_OK) {
691 return result;
692 }
693 vargv_needs_free = true;
694 } else {
695 vargc = (Tcl_Size)argc - 2;
696 vargv = &argv[2];
697 }
698 if (vargc < 4) {
699 Tcl_AppendResult(interp,
700 "invalid points: should be: \"x1 y1 x2 y2\"", NULL);
701 return TCL_ERROR;
702 }
703
704 result = scanpoint(interp, &vargv[0], &p);
705 if (result != TCL_OK) {
706 if (vargv_needs_free) {
707 Tcl_Free((char *)vargv);
708 }
709 return result;
710 }
711 result = scanpoint(interp, &vargv[2], &q);
712 if (vargv_needs_free) {
713 Tcl_Free((char *)vargv);
714 }
715 if (result != TCL_OK)
716 return result;
717
718 /* determine the polygons (if any) that contain the endpoints */
719 int pp = POLYID_NONE;
720 int qp = POLYID_NONE;
721 for (size_t i = 0; i < LIST_SIZE(&vgp->poly); i++) {
722 const poly tpp = LIST_GET(&vgp->poly, i);
723 if (pp == POLYID_NONE && in_poly(tpp.boundary, p))
724 pp = (int)i;
725 if (qp == POLYID_NONE && in_poly(tpp.boundary, q))
726 qp = (int)i;
727 }
728
729 if (vc_refresh(vgp)) {
730 Pobspath(vgp->vc, p, POLYID_UNKNOWN, q, POLYID_UNKNOWN, &line);
731 size_t n_barriers;
732 make_barriers(vgp, pp, qp, &barriers, &n_barriers);
733 Proutespline(barriers, n_barriers, line, (Pvector_t[2]){0}, &spline);
734
735 for (size_t i = 0; i < spline.pn; i++) {
736 appendpoint(interp, spline.ps[i]);
737 }
738 }
739 return TCL_OK;
740
741 } else if (strcmp(argv[1], "bbox") == 0) {
742 if (argc < 3) {
743 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
744 " ", argv[1], " id\"", NULL);
745 return TCL_ERROR;
746 }
747 if (sscanf(argv[2], "%d", &polyid) != 1) {
748 Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
749 return TCL_ERROR;
750 }
751 for (size_t i = 0; i < LIST_SIZE(&vgp->poly); i++) {
752 if (LIST_GET(&vgp->poly, i).id == polyid) {
753 Ppoly_t pp = LIST_GET(&vgp->poly, i).boundary;
754 point LL, UR;
755 LL = UR = pp.ps[0];
756 for (size_t j = 1; j < pp.pn; j++) {
757 p = pp.ps[j];
758 UR.x = fmax(UR.x, p.x);
759 UR.y = fmax(UR.y, p.y);
760 LL.x = fmin(LL.x, p.x);
761 LL.y = fmin(LL.y, p.y);
762 }
763 appendpoint(interp, LL);
764 appendpoint(interp, UR);
765 return TCL_OK;
766 }
767 }
768 Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
769 return TCL_ERROR;
770
771 } else if (strcmp(argv[1], "center") == 0) {
772 if (argc < 3) {
773 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
774 " ", argv[1], " id\"", NULL);
775 return TCL_ERROR;
776 }
777 if (sscanf(argv[2], "%d", &polyid) != 1) {
778 Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
779 return TCL_ERROR;
780 }
781 for (size_t i = 0; i < LIST_SIZE(&vgp->poly); i++) {
782 if (LIST_GET(&vgp->poly, i).id == polyid) {
783 appendpoint(interp, center(LIST_GET(&vgp->poly, i).boundary.ps,
784 LIST_GET(&vgp->poly, i).boundary.pn));
785 return TCL_OK;
786 }
787 }
788 Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
789 return TCL_ERROR;
790
791 } else if (strcmp(argv[1], "triangulate") == 0) {
792 if (argc < 3) {
793 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
794 " ", argv[1], " id\"", NULL);
795 return TCL_ERROR;
796 }
797
798 if (sscanf(argv[2], "%d", &polyid) != 1) {
799 Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
800 return TCL_ERROR;
801 }
802
803 for (size_t i = 0; i < LIST_SIZE(&vgp->poly); i++) {
804 if (LIST_GET(&vgp->poly, i).id == polyid) {
805 Ppoly_t *polygon = &LIST_AT(&vgp->poly, i)->boundary;
806 if (polygon->pn < 3) {
807 Tcl_AppendResult(interp, "polygon ", argv[2], " has fewer than 3 points "
808 "and thus cannot be triangulated", NULL);
809 return TCL_ERROR;
810 }
812 return TCL_OK;
813 }
814 }
815 Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
816 return TCL_ERROR;
817 } else if (strcmp(argv[1], "rotate") == 0) {
818 if (argc < 4) {
819 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
820 " ", argv[1], " id alpha\"", NULL);
821 return TCL_ERROR;
822 }
823 if (sscanf(argv[2], "%d", &polyid) != 1) {
824 Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
825 return TCL_ERROR;
826 }
827 if (sscanf(argv[3], "%lg", &alpha) != 1) {
828 Tcl_AppendResult(interp, "not an angle in radians: ", argv[3], NULL);
829 return TCL_ERROR;
830 }
831 for (size_t i = 0; i < LIST_SIZE(&vgp->poly); i++) {
832 if (LIST_GET(&vgp->poly, i).id == polyid) {
833 const size_t n = LIST_GET(&vgp->poly, i).boundary.pn;
834 ps = LIST_GET(&vgp->poly, i).boundary.ps;
835 p = center(ps, n);
836 for (size_t j = 0; j < n; j++) {
837 appendpoint(interp, rotate(p, ps[j], alpha));
838 }
839 return TCL_OK;
840 }
841 }
842 Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
843 return TCL_ERROR;
844
845 } else if (strcmp(argv[1], "scale") == 0) {
846 if (argc < 4) {
847 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
848 " ", argv[1], " id gain\"", NULL);
849 return TCL_ERROR;
850 }
851 if (sscanf(argv[2], "%d", &polyid) != 1) {
852 Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
853 return TCL_ERROR;
854 }
855 if (sscanf(argv[3], "%lg", &gain) != 1) {
856 Tcl_AppendResult(interp, "not a number: ", argv[3], NULL);
857 return TCL_ERROR;
858 }
859 for (size_t i = 0; i < LIST_SIZE(&vgp->poly); i++) {
860 if (LIST_GET(&vgp->poly, i).id == polyid) {
861 const size_t n = LIST_GET(&vgp->poly, i).boundary.pn;
862 ps = LIST_GET(&vgp->poly, i).boundary.ps;
863 p = center(ps, n);
864 for (size_t j = 0; j < n; j++) {
865 appendpoint(interp, scale(p, ps[j], gain));
866 }
867 return TCL_OK;
868 }
869 }
870 Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
871 return TCL_ERROR;
872
873 } else if (strcmp(argv[1], "remove") == 0) {
874 if (argc < 3) {
875 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
876 " ", argv[1], " id\"", NULL);
877 return TCL_ERROR;
878 }
879 if (sscanf(argv[2], "%d", &polyid) != 1) {
880 Tcl_AppendResult(interp, "not an integer: ", argv[2], NULL);
881 return TCL_ERROR;
882 }
883
884 if (remove_poly(vgp, polyid))
885 return TCL_OK;
886
887 Tcl_AppendResult(interp, " no such polygon: ", argv[2], NULL);
888 return TCL_ERROR;
889 }
890
891 Tcl_AppendResult(interp, "bad method \"", argv[1],
892 "\" must be one of:",
893 "\n\tbbox, bind, bpath, center, coords, delete, find,",
894 "\n\tinsert, list, path, remove, rotate, scale, triangulate.", NULL);
895 return TCL_ERROR;
896}
897
898static int
899vgpane(ClientData clientData, Tcl_Interp * interp, int argc, const char *argv[])
900{
901 (void)clientData;
902 (void)argc;
903 (void)argv;
904
905 const vgpane_t vg = {.interp = interp};
906 LIST_APPEND(&vgpaneTable, vg);
907 vgpane_t *const vgp = LIST_BACK(&vgpaneTable);
908 vgp->index = LIST_SIZE(&vgpaneTable) - 1;
909 vgp->valid = true;
910
911 agxbuf buffer = {0};
912 agxbprint(&buffer, HANDLE_FORMAT, vgp->index);
913 const char *const vbuf = agxbuse(&buffer);
914
915 Tcl_CreateCommand(interp, vbuf, vgpanecmd, NULL, NULL);
916 Tcl_AppendResult(interp, vbuf, NULL);
917 agxbfree(&buffer);
918 return TCL_OK;
919}
920
921#ifdef GVDLL
922__declspec(dllexport)
923#endif
924int Tclpathplan_Init(Tcl_Interp *interp);
925int Tclpathplan_Init(Tcl_Interp * interp)
926{
927#ifdef USE_TCL_STUBS
928 if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
929 return TCL_ERROR;
930 }
931#else
932 if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 0) == NULL) {
933 return TCL_ERROR;
934 }
935#endif
936 // inter-release Graphviz versions have a number including '~dev.' that does
937 // not comply with TCL version number rules, so replace this with 'b'
938 char adjusted_version[sizeof(PACKAGE_VERSION)] = PACKAGE_VERSION;
939 char *tilde_dev = strstr(adjusted_version, "~dev.");
940 if (tilde_dev != NULL) {
941 *tilde_dev = 'b';
942 memmove(tilde_dev + 1, tilde_dev + strlen("~dev."),
943 strlen(tilde_dev + strlen("~dev.")) + 1);
944 }
945 if (Tcl_PkgProvide(interp, "Tclpathplan", adjusted_version) != TCL_OK) {
946 return TCL_ERROR;
947 }
948
949 Tcl_CreateCommand(interp, "vgpane", vgpane, NULL, NULL);
950
951 return TCL_OK;
952}
953
954#ifdef GVDLL
955__declspec(dllexport)
956#endif
957int Tclpathplan_SafeInit(Tcl_Interp *interp);
958int Tclpathplan_SafeInit(Tcl_Interp * interp)
959{
960 return Tclpathplan_Init(interp);
961}
Dynamically expanding string buffers.
static void agxbfree(agxbuf *xb)
free any malloced resources
Definition agxbuf.h:97
static size_t agxbput_n(agxbuf *xb, const char *s, size_t ssz)
append string s of length ssz into xb
Definition agxbuf.h:268
static int agxbprint(agxbuf *xb, const char *fmt,...)
Printf-style output to an agxbuf.
Definition agxbuf.h:252
static WUR char * agxbuse(agxbuf *xb)
Definition agxbuf.h:325
static size_t agxblen(const agxbuf *xb)
return number of characters currently stored
Definition agxbuf.h:108
static int agxbputc(agxbuf *xb, char c)
add character to buffer
Definition agxbuf.h:295
static char * agxbdisown(agxbuf *xb)
Definition agxbuf.h:345
Memory allocation wrappers that exit on failure.
static char * gv_strdup(const char *original)
Definition alloc.h:101
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
vconfig_t * Pobsopen(Ppoly_t **obs, int n_obs)
Definition cvt.c:28
void Pobsclose(vconfig_t *config)
Definition cvt.c:89
void Pobspath(vconfig_t *config, Ppoint_t p0, int poly0, Ppoint_t p1, int poly1, Ppolyline_t *output_route)
Definition cvt.c:102
static float dy
Definition draw.c:43
static float dx
Definition draw.c:42
void free(void *)
node NULL
Definition grammar.y:181
static uint64_t id
Definition gv2gml.c:42
bool in_poly(const Ppoly_t poly, Ppoint_t q)
Definition inpoly.c:26
#define ITOS(i)
Definition itos.h:43
int Plegal_arrangement(Ppoly_t **polys, int n_polys)
Definition legal.c:357
type-generic dynamically expanding list
#define LIST_AT(list, index)
Definition list.h:210
#define LIST_APPEND(list,...)
Definition list.h:151
#define LIST(type)
Definition list.h:66
#define LIST_BACK(list)
Definition list.h:233
#define LIST_SIZE(list)
Definition list.h:92
#define LIST_DROP_BACK(list)
Definition list.h:483
#define LIST_FREE(list)
Definition list.h:413
#define LIST_SET(list, index, item)
Definition list.h:253
#define LIST_IS_EMPTY(list)
Definition list.h:102
#define LIST_GET(list, index)
Definition list.h:197
void make_CW(Ppoly_t *poly)
Definition makecw.c:26
NEATOPROCS_API void s1(graph_t *, node_t *)
Definition stuff.c:651
int Proutespline(Pedge_t *barriers, size_t n_barriers, Ppolyline_t input_route, Pvector_t endpoint_slopes[2], Ppolyline_t *output_route)
#define alpha
Definition shapes.c:4034
Ppoint_t b
Definition pathgeom.h:53
Ppoint_t a
Definition pathgeom.h:53
size_t pn
Definition pathgeom.h:47
Ppoint_t * ps
Definition pathgeom.h:46
Definition geom.h:27
int y
Definition geom.h:27
int x
Definition geom.h:27
Ppoly_t boundary
Definition tclpathplan.c:53
Definition legal.c:34
#define Tcl_Size
Definition tcl-compat.h:33
static void vc_stale(vgpane_t *vgp)
Definition tclpathplan.c:82
static int vgpane(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
int Tclpathplan_Init(Tcl_Interp *interp)
static char * buildBindings(char *s1, const char *s2)
static int vc_refresh(vgpane_t *vgp)
Definition tclpathplan.c:90
static void garbage_collect_vgpanes(vgpanes_t *panes)
static void appendpoint(Tcl_Interp *interp, point p)
static point scale(point c, point p, double gain)
static void expandPercentsEval(Tcl_Interp *interp, char *before, size_t vgcanvasHandle, int npts, const point *ppos)
static void triangle_callback(void *vgparg, const point pqr[])
int Tclpathplan_SafeInit(Tcl_Interp *interp)
static vgpane_t * find_vgpane(vgpanes_t *panes, const char *handle)
struct poly_s poly
#define HANDLE_FORMAT
printf format for TCL handles
Definition tclpathplan.c:57
static void make_barriers(vgpane_t *vgp, int pp, int qp, Pedge_t **barriers, size_t *n_barriers)
static void dgsprintxy(agxbuf *result, int npts, const point p[])
static int insert_poly(Tcl_Interp *interp, vgpane_t *vgp, int id, const char *vargv[], Tcl_Size vargc)
static int vgpanecmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
static point center(point vertex[], size_t n)
static double distance(point p, point q)
static point rotate(point c, point p, double alpha)
Ppoint_t point
Definition tclpathplan.c:49
#define Tcl_GetStringResult(interp)
Definition tclpathplan.c:45
static int scanpoint(Tcl_Interp *interp, const char *argv[], point *p)
static bool remove_poly(vgpane_t *vgp, int id)
TRI_API int Ptriangulate(Ppoly_t *polygon, void(*fn)(void *closure, const Ppoint_t tri[]), void *vc)
Definition grammar.c:90
#define POLYID_UNKNOWN
Definition vispath.h:51
#define POLYID_NONE
Definition vispath.h:50