2
* Tcl binding to drive Stephen North's and
3
* Emden Gansner's shortest path code.
5
* ellson@lucent.com October 2nd, 1996
12
#include <sys/types.h>
24
#include "tclhandle.h"
30
typedef Ppoint_t point;
32
typedef struct poly_s {
37
typedef struct vgpane_s {
38
int Npoly; /* number of polygons */
39
poly *poly; /* set of polygons */
40
int N_poly_alloc; /* for allocation */
41
vconfig_t *vc; /* visibility graph handle */
42
Tcl_Interp *interp; /* interpreter that owns the binding */
43
char *triangle_cmd; /* why is this here any more */
47
#define sincos(x,s,c) *s = sin(x); *c = cos(x)
50
tblHeader_pt vgpaneTable;
52
extern void make_CW(Ppoly_t *poly);
53
extern int Plegal_arrangement( Ppoly_t **polys, int n_polys);
55
static int polyid=0; /* unique and unchanging id for each poly */
57
static poly *allocpoly(vgpane_t *vgp, int id, int npts)
60
if (vgp->Npoly >= vgp->N_poly_alloc) {
61
vgp->N_poly_alloc *= 2;
62
vgp->poly = realloc(vgp->poly,vgp->N_poly_alloc*sizeof(poly));
64
rv = &(vgp->poly[vgp->Npoly++]);
67
rv->boundary.ps = malloc(npts * sizeof(point));
71
static void vc_stale(vgpane_t *vgp)
75
vgp->vc = (vconfig_t*)0;
79
static int vc_refresh(vgpane_t *vgp)
84
if (vgp->vc == (vconfig_t*)0) {
85
obs = malloc(vgp->Npoly * sizeof(Ppoly_t));
86
for (i= 0; i < vgp->Npoly; i++)
87
obs[i] = &(vgp->poly[i].boundary);
88
if (NOT(Plegal_arrangement(obs,vgp->Npoly)))
89
fprintf(stderr,"bad arrangement\n");
91
vgp->vc = Pobsopen(obs, vgp->Npoly);
94
return (vgp->vc != 0);
98
dgsprintxy(Tcl_DString *result, int npts, point p[])
103
if (npts != 1) Tcl_DStringStartSublist(result);
104
for (i = 0; i < npts; i++) {
105
sprintf(buf, "%g", p[i].x);
106
Tcl_DStringAppendElement(result, buf);
107
sprintf(buf, "%g", p[i].y);
108
Tcl_DStringAppendElement(result, buf);
110
if (npts != 1) Tcl_DStringEndSublist(result);
115
Tcl_Interp * interp, /* interpreter context */
116
register char *before, /* Command with percent expressions */
117
char *r, /* vgpaneHandle string to substitute for "%r" */
118
int npts, /* number of coordinates */
119
point *ppos /* Cordinates to substitute for %t */
122
register char *string;
125
Tcl_DStringInit(&scripts);
128
* Find everything up to the next % character and append it to the
132
for (string = before; (*string != 0) && (*string != '%'); string++) {
133
/* Empty loop body. */
135
if (string != before) {
136
Tcl_DStringAppend(&scripts, before, string - before);
143
* There's a percent sequence here. Process it.
148
Tcl_DStringAppend(&scripts, r, strlen(r)); /* vgcanvasHandle */
151
dgsprintxy(&scripts, npts, ppos);
154
Tcl_DStringAppend(&scripts, before+1, 1);
159
if (Tcl_GlobalEval(interp, Tcl_DStringValue(&scripts)) != TCL_OK)
160
fprintf(stderr, "%s while in binding: %s\n\n",
161
interp->result, Tcl_DStringValue(&scripts));
162
Tcl_DStringFree(&scripts);
166
triangle_callback(void *vgparg, point pqr[])
173
/* TBL_ENTRY((tblHeader_pt)vgpaneTable, (ubyte_pt)vgp));*/
175
if (vgp->triangle_cmd) {
176
sprintf(vbuf, "vgpane%lu",
177
(unsigned long) (((ubyte_pt)vgp - (vgpaneTable->bodyPtr)) / (vgpaneTable->entrySize)));
178
expandPercentsEval(vgp->interp, vgp->triangle_cmd, vbuf, 3, pqr);
183
buildBindings(char *s1, char *s2)
185
* previous binding in s1 binding to be added in s2 result in s3
187
* if s2 begins with + then append (separated by \n) else s2 replaces if
188
* resultant string is null then bindings are deleted
198
s3 = malloc(strlen(s1) + l + 2);
231
/* convert x and y string args to point */
233
scanpoint (Tcl_Interp *interp, char *argv[], point *p)
235
if (sscanf(argv[0], "%lg", &(p->x)) != 1) {
236
Tcl_AppendResult(interp, "invalid x coordinate: \"", argv[0],
240
if (sscanf(argv[1], "%lg", &(p->y)) != 1) {
241
Tcl_AppendResult(interp, "invalid y coordinate: \"", argv[1],
249
center(point vertex[], int n)
255
for (i=0; i<n; i++) {
265
distance(point p, point q)
271
return sqrt(dx*dx + dy*dy);
275
rotate(point c, point p, double alpha)
278
double beta, r, sina, cosa;
281
beta = atan2(p.x - c.x, p.y - c.y);
282
sincos(beta+alpha, &sina, &cosa);
283
q.x = c.x + r * sina;
284
q.y = c.y - r * cosa; /* adjust for tk y-down */
289
scale(point c, point p, double gain)
293
q.x = c.x + gain * (p.x - c.x);
294
q.y = c.y + gain * (p.y - c.y);
299
remove_poly (vgpane_t *vgp, int polyid)
303
for (i=0; i < vgp->Npoly; i++) {
304
if (vgp->poly[i].id == polyid) {
305
free(vgp->poly[i].boundary.ps);
306
for (j = i++; i < vgp->Npoly; i++, j++) {
307
vgp->poly[j] = vgp->poly[i];
318
insert_poly (Tcl_Interp *interp, vgpane_t *vgp, int polyid, char *vargv[], int vargc)
323
np = allocpoly(vgp, polyid, vargc);
324
for (i=0; i < vargc; i += 2) {
325
result = scanpoint(interp, &vargv[i], &(np->boundary.ps[np->boundary.pn]));
326
if (result != TCL_OK) return result;
329
make_CW(&(np->boundary));
335
make_barriers(vgpane_t *vgp, int pp, int qp, Pedge_t **barriers, int *n_barriers)
341
for (i = 0; i < vgp->Npoly; i++) {
342
if (vgp->poly[i].id == pp) continue;
343
if (vgp->poly[i].id == qp) continue;
344
n = n + vgp->poly[i].boundary.pn;
346
bar = malloc(n * sizeof(Pedge_t));
348
for (i = 0; i < vgp->Npoly; i++) {
349
if (vgp->poly[i].id == pp) continue;
350
if (vgp->poly[i].id == qp) continue;
351
for (j = 0; j < vgp->poly[i].boundary.pn; j++) {
353
if (k >= vgp->poly[i].boundary.pn) k = 0;
354
bar[b].a = vgp->poly[i].boundary.ps[j];
355
bar[b].b = vgp->poly[i].boundary.ps[k];
364
/* append the x and y coordinates of a point to the Tcl result */
366
appendpoint (Tcl_Interp *interp, point p)
370
sprintf(buf, "%g", p.x);
371
Tcl_AppendElement(interp, buf);
372
sprintf(buf, "%g", p.y);
373
Tcl_AppendElement(interp, buf);
376
/* process vgpane methods */
378
vgpanecmd(ClientData clientData, Tcl_Interp * interp, int argc, char *argv[])
380
int vargc, length, i, j, n, result;
381
char c, *s, **vargv, vbuf[30];
382
vgpane_t *vgp, **vgpp;
387
Ppolyline_t line, spline;
388
int pp, qp; /* polygon indices for p, q */
393
Tcl_AppendResult(interp, "wrong # args: should be \"",
394
" ", argv[0], " method ?arg arg ...?\"", (char *) NULL);
397
if (!(vgpp = (vgpane_t **)tclhandleXlate(vgpaneTable, argv[0]))) {
398
Tcl_AppendResult(interp, "Invalid handle: \"", argv[0],
399
"\"", (char *) NULL);
405
length = strlen(argv[1]);
407
if ((c == 'c') && (strncmp(argv[1], "coords", length) == 0)) {
409
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
410
" ", argv[1], " id ?x1 y1 x2 y2...?\"", (char *) NULL);
413
if (sscanf(argv[2], "%d", &polyid) != 1) {
414
Tcl_AppendResult(interp, "not an integer: ", argv[2],
419
/* find poly and return its coordinates */
420
for (i=0; i < vgp->Npoly; i++) {
421
if (vgp->poly[i].id == polyid) {
422
n = vgp->poly[i].boundary.pn;
423
for (j = 0; j < n; j++) {
424
appendpoint(interp, vgp->poly[i].boundary.ps[j]);
429
Tcl_AppendResult(interp, " no such polygon: ", argv[2],
433
/* accept either inline or delimited list */
435
result = Tcl_SplitList(interp, argv[3], &vargc, &vargv);
436
if (result != TCL_OK) {return result;}
441
if (!vargc || vargc%2) {
442
Tcl_AppendResult(interp,
443
"There must be a multiple of two terms in the list.",
448
/* remove old poly, add modified polygon to the end with
449
the same id as the original */
451
if (! (remove_poly(vgp, polyid))) {
452
Tcl_AppendResult(interp, " no such polygon: ", argv[2],
457
return (insert_poly(interp, vgp, polyid, vargv, vargc));
459
} else if ((c == 'd') && (strncmp(argv[1], "debug", length) == 0)) {
461
printf("debug output goes here\n");
464
} else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)) {
465
/* delete a vgpane and all memory associated with it */
466
if (vgp->vc) Pobsclose(vgp->vc);
467
free(vgp->poly); /* ### */
468
Tcl_DeleteCommand(interp, argv[0]);
469
free((char *)tclhandleFree(vgpaneTable, argv[0]));
472
} else if ((c == 'f') && (strncmp(argv[1], "find", length) == 0)) {
473
/* find the polygon that the point is inside and return it
476
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
477
" ", argv[1], " x y\"", (char *) NULL);
481
result = Tcl_SplitList(interp, argv[2], &vargc, &vargv);
482
if (result != TCL_OK) {return result;}
487
result = scanpoint(interp, &vargv[0], &p);
488
if (result != TCL_OK) return result;
490
/* determine the polygons (if any) that contain the point */
491
for (i = 0; i < vgp->Npoly; i++) {
492
if (in_poly(vgp->poly[i].boundary, p)) {
493
sprintf(vbuf, "%d", vgp->poly[i].id);
494
Tcl_AppendElement(interp, vbuf);
499
} else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)) {
500
/* add poly to end poly list, and it coordinates to the end of
503
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
504
" ", argv[1], " x1 y1 x2 y2 ...\"", (char *) NULL);
507
/* accept either inline or delimited list */
509
result = Tcl_SplitList(interp, argv[2], &vargc, &vargv);
510
if (result != TCL_OK) {return result;}
516
if (!vargc || vargc%2) {
517
Tcl_AppendResult(interp,
518
"There must be a multiple of two terms in the list.",
525
result = insert_poly(interp, vgp, polyid, vargv, vargc);
526
if (result != TCL_OK) return result;
528
sprintf(vbuf, "%d", polyid);
529
Tcl_AppendResult(interp, vbuf, (char *) NULL);
532
} else if ((c == 'l') && (strncmp(argv[1], "list", length) == 0)) {
533
/* return list of polygon ids */
534
for (i = 0; i < vgp->Npoly; i++) {
535
sprintf(vbuf, "%d", vgp->poly[i].id);
536
Tcl_AppendElement(interp, vbuf);
540
} else if ((c == 'p') && (strncmp(argv[1], "path", length) == 0)) {
541
/* return a list of points corresponding to the shortest path
542
that does not cross the remaining "visible" polygons. */
544
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
545
" ", argv[1], " x1 y1 x2 y2\"", (char *) NULL);
549
result = Tcl_SplitList(interp, argv[2], &vargc, &vargv);
550
if (result != TCL_OK) {return result;}
556
Tcl_AppendResult(interp,
557
"invalid points: should be: \"x1 y1 x2 y2\"",
561
result = scanpoint(interp, &vargv[0], &p);
562
if (result != TCL_OK) return result;
563
result = scanpoint(interp, &vargv[2], &q);
564
if (result != TCL_OK) return result;
566
/* only recompute the visibility graph if we have to */
567
if ((vc_refresh(vgp))) {
568
Pobspath(vgp->vc, p, POLYID_UNKNOWN, q, POLYID_UNKNOWN, &line);
570
for (i = 0; i < line.pn; i++) {
571
appendpoint(interp, line.ps[i]);
577
} else if ((c == 'b') && (strncmp(argv[1], "bind", length) == 0)) {
578
if ((argc < 2) || (argc > 4)) {
579
Tcl_AppendResult(interp, "wrong # args: should be \"",
580
argv[0], " bind triangle ?command?\"", (char *) NULL);
584
Tcl_AppendElement(interp, "triangle");
587
length = strlen(argv[2]);
588
if (strncmp(argv[2], "triangle", length) == 0) {
589
s = vgp->triangle_cmd;
590
if (argc == 4) vgp->triangle_cmd = s = buildBindings(s, argv[3]) ;
592
Tcl_AppendResult(interp, "unknown event \"", argv[2],
593
"\": must be one of:\n\ttriangle.", (char *) NULL);
596
if (argc == 3) Tcl_AppendResult(interp, s, (char *) NULL);
599
} else if ((c == 'b') && (strncmp(argv[1], "bpath", length) == 0)) {
600
/* return a list of points corresponding to the shortest path
601
that does not cross the remaining "visible" polygons. */
603
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
604
" ", argv[1], " x1 y1 x2 y2\"", (char *) NULL);
608
result = Tcl_SplitList(interp, argv[2], &vargc, &vargv);
609
if (result != TCL_OK) {return result;}
615
Tcl_AppendResult(interp,
616
"invalid points: should be: \"x1 y1 x2 y2\"",
621
result = scanpoint(interp, &vargv[0], &p);
622
if (result != TCL_OK) return result;
623
result = scanpoint(interp, &vargv[2], &q);
624
if (result != TCL_OK) return result;
626
/* determine the polygons (if any) that contain the endpoints */
627
pp = qp = POLYID_NONE;
628
for (i = 0; i < vgp->Npoly; i++) {
629
tpp = &(vgp->poly[i]);
630
if ((pp == POLYID_NONE) && in_poly(tpp->boundary, p)) pp = i;
631
if ((qp == POLYID_NONE) && in_poly(tpp->boundary, q)) qp = i;
634
if (vc_refresh(vgp)) {
635
/*Pobspath(vgp->vc, p, pp, q, qp, &line);*/
636
Pobspath(vgp->vc, p, POLYID_UNKNOWN, q, POLYID_UNKNOWN, &line);
637
make_barriers(vgp, pp, qp, &barriers, &n_barriers);
638
slopes[0].x = slopes[0].y = 0.0;
639
slopes[1].x = slopes[1].y = 0.0;
640
Proutespline (barriers, n_barriers, line, slopes, &spline);
642
for (i = 0; i < spline.pn; i++) {
643
appendpoint(interp, spline.ps[i]);
648
} else if ((c == 'b') && (strncmp(argv[1], "bbox", length) == 0)) {
650
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
651
" ", argv[1], " id\"", (char *) NULL);
654
if (sscanf(argv[2], "%d", &polyid) != 1) {
655
Tcl_AppendResult(interp, "not an integer: ", argv[2],
659
for (i=0; i < vgp->Npoly; i++) {
660
if (vgp->poly[i].id == polyid) {
661
Ppoly_t pp = vgp->poly[i].boundary;
664
for (j = 1; j < pp.pn; j++) {
666
if (p.x > UR.x) UR.x = p.x;
667
if (p.y > UR.y) UR.y = p.y;
668
if (p.x < LL.x) LL.x = p.x;
669
if (p.y < LL.y) LL.y = p.y;
671
appendpoint(interp, LL);
672
appendpoint(interp, UR);
676
Tcl_AppendResult(interp, " no such polygon: ", argv[2],
680
} else if ((c == 'c') && (strncmp(argv[1], "center", length) == 0)) {
682
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
683
" ", argv[1], " id\"", (char *) NULL);
686
if (sscanf(argv[2], "%d", &polyid) != 1) {
687
Tcl_AppendResult(interp, "not an integer: ", argv[2],
691
for (i=0; i < vgp->Npoly; i++) {
692
if (vgp->poly[i].id == polyid) {
693
appendpoint(interp, center(vgp->poly[i].boundary.ps,
694
vgp->poly[i].boundary.pn));
698
Tcl_AppendResult(interp, " no such polygon: ", argv[2],
702
} else if ((c == 't') && (strncmp(argv[1], "triangulate", length) == 0)) {
704
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
705
" id ", (char *) NULL);
709
if (sscanf(argv[2], "%d", &polyid) != 1) {
710
Tcl_AppendResult(interp, "not an integer: ", argv[2],
715
for (i=0; i < vgp->Npoly; i++) {
716
if (vgp->poly[i].id == polyid) {
717
Ptriangulate(&(vgp->poly[i].boundary),triangle_callback,vgp);
721
Tcl_AppendResult(interp, " no such polygon: ", argv[2], (char *) NULL);
723
} else if ((c == 'r') && (strncmp(argv[1], "rotate", length) == 0)) {
725
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
726
" ", argv[1], " id alpha\"", (char *) NULL);
729
if (sscanf(argv[2], "%d", &polyid) != 1) {
730
Tcl_AppendResult(interp, "not an integer: ", argv[2],
734
if (sscanf(argv[3], "%lg", &alpha) != 1) {
735
Tcl_AppendResult(interp, "not an angle in radians: ", argv[3],
739
for (i=0; i < vgp->Npoly; i++) {
740
if (vgp->poly[i].id == polyid) {
741
n = vgp->poly[i].boundary.pn;
742
ps = vgp->poly[i].boundary.ps;
744
for (j = 0; j < n; j++) {
745
appendpoint(interp, rotate(p, ps[j], alpha));
750
Tcl_AppendResult(interp, " no such polygon: ", argv[2],
754
} else if ((c == 's') && (strncmp(argv[1], "scale", length) == 0)) {
756
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
757
" ", argv[1], " id gain\"", (char *) NULL);
760
if (sscanf(argv[2], "%d", &polyid) != 1) {
761
Tcl_AppendResult(interp, "not an integer: ", argv[2],
765
if (sscanf(argv[3], "%lg", &gain) != 1) {
766
Tcl_AppendResult(interp, "not a number: ", argv[3],
770
for (i=0; i < vgp->Npoly; i++) {
771
if (vgp->poly[i].id == polyid) {
772
n = vgp->poly[i].boundary.pn;
773
ps = vgp->poly[i].boundary.ps;
774
for (j = 0; j < n; j++) {
775
appendpoint(interp, scale(p, ps[j], gain));
780
Tcl_AppendResult(interp, " no such polygon: ", argv[2],
784
} else if ((c == 'r') && (strncmp(argv[1], "remove", length) == 0)) {
786
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
787
" ", argv[1], " id\"", (char *) NULL);
790
if (sscanf(argv[2], "%d", &polyid) != 1) {
791
Tcl_AppendResult(interp, "not an integer: ", argv[2],
796
if (remove_poly(vgp, polyid))
799
Tcl_AppendResult(interp, " no such polygon: ", argv[2],
804
Tcl_AppendResult(interp, "bad method \"", argv[1], "\" must be one of:",
805
"\n\tbbox, bind, bpath, center, coords, delete, find,",
806
"\n\tinsert, list, path, remove, rotate, scale, triangulate.", (char *) NULL);
811
vgpane(ClientData clientData, Tcl_Interp * interp, int argc, char *argv[])
816
vgp = (vgpane_t *)malloc(sizeof(vgpane_t));
817
*(vgpane_t **)tclhandleAlloc(vgpaneTable, vbuf, NULL) = vgp;
819
vgp->vc = (vconfig_t*)0;
821
vgp->N_poly_alloc = 250;
822
vgp->poly = malloc(vgp->N_poly_alloc * sizeof(poly));
823
vgp->interp = interp;
824
vgp->triangle_cmd = (char *) NULL;
826
Tcl_CreateCommand(interp, vbuf, vgpanecmd, (ClientData) NULL,
827
(Tcl_CmdDeleteProc *) NULL);
828
Tcl_AppendResult(interp, vbuf, (char *) NULL);
832
int Tclpathplan_Init(Tcl_Interp * interp)
835
if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
839
if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 0) == NULL) {
843
if (Tcl_PkgProvide(interp, "Tclpathplan", VERSION) != TCL_OK) {
847
Tcl_CreateCommand(interp, "vgpane", vgpane, (ClientData) NULL,
848
(Tcl_CmdDeleteProc *) NULL);
850
vgpaneTable = tclhandleInit("vgpane", sizeof(vgpane_t), 10);
855
int Tclpathplan_SafeInit(Tcl_Interp * interp)
857
return Tclpathplan_Init(interp);