#include #include #include #include #include #include #include #include typedef enum { SLine, SCurve, NStrokes } Stroke; typedef enum { Drawing, Zooming, Rotating, Movingpts, NStates } State; /* * Vector model - made out of lines and curves */ typedef struct VModel VModel; struct VModel { Point2 *pts; ulong npts; ulong cap; /* WIP * l(ine) → takes 2 points * c(urve) → takes 3 points */ char *strokefmt; }; typedef struct Object Object; struct Object { RFrame; VModel *mdl; void (*scale)(Object*, double); void (*rotate)(Object*, double); }; char *strokename[NStrokes] = { [SLine] "line", [SCurve] "curve", }; char *statename[NStates] = { [Drawing] "drawing", [Zooming] "zooming", [Rotating] "rotating", [Movingpts] "moving", }; RFrame worldrf; Object mainobj; Stroke stroke; State curstate; Point2 ptstk[3]; Point2 *ptstkp; char *vmdlfile; Image *ptselcol, *guidecol; void resized(void); void* emalloc(ulong n) { void *p; p = malloc(n); if(p == nil) sysfatal("malloc: %r"); setmalloctag(p, getcallerpc(&n)); return p; } void* erealloc(void *p, ulong n) { void *np; np = realloc(p, n); if(np == nil){ if(n == 0) return nil; sysfatal("realloc: %r"); } if(p == nil) setmalloctag(np, getcallerpc(&p)); else setrealloctag(np, getcallerpc(&p)); return np; } Image* eallocimage(Display *d, Rectangle r, ulong chan, int repl, ulong col) { Image *i; i = allocimage(d, r, chan, repl, col); if(i == nil) sysfatal("allocimage: %r"); return i; } Point toscreen(Point2 p) { p = invrframexform(p, worldrf); return Pt(p.x,p.y); } Point2 fromscreen(Point p) { return rframexform(Pt2(p.x,p.y,1), worldrf); } void obj_rotate(Object *o, double θ) { Matrix R = { cos(θ), -sin(θ), 0, sin(θ), cos(θ), 0, 0, 0, 1, }; o->bx = xform(o->bx, R); o->by = xform(o->by, R); } void obj_scale(Object *o, double s) { o->bx = mulpt2(o->bx, s); o->by = mulpt2(o->by, s); } VModel * readvmodel(char *file) { ulong lineno; char *s, *args[2]; Biobuf *bin; VModel *mdl; bin = Bopen(file, OREAD); if(bin == nil) sysfatal("Bopen: %r"); mdl = emalloc(sizeof(VModel)); mdl->pts = nil; mdl->npts = mdl->cap = 0; mdl->strokefmt = nil; lineno = 0; while(s = Brdline(bin, '\n')){ s[Blinelen(bin)-1] = 0; lineno++; switch(*s++){ case '#': continue; case 'v': if(tokenize(s, args, nelem(args)) != nelem(args)){ werrstr("syntax error: %s:%lud 'v' expects %d args", file, lineno, nelem(args)); free(mdl); Bterm(bin); return nil; } mdl->cap = ++mdl->npts; mdl->pts = erealloc(mdl->pts, mdl->cap*sizeof(Point2)); mdl->pts[mdl->npts-1].x = strtod(args[0], nil); mdl->pts[mdl->npts-1].y = strtod(args[1], nil); mdl->pts[mdl->npts-1].w = 1; break; case 'l': case 'c': mdl->strokefmt = strdup(s-1); break; } } Bterm(bin); return mdl; } int writevmodel(VModel *mdl, char *file) { Point2 *p; Biobuf *bout; bout = Bopen(file, OWRITE); if(bout == nil) sysfatal("Bopen: %r"); for(p = mdl->pts; p < mdl->pts+mdl->npts; p++) if(Bprint(bout, "v %g %g\n", p->x, p->y) == Beof) goto saveerr; if(Bprint(bout, "%s\n", mdl->strokefmt) == Beof){ saveerr: Bterm(bout); sysfatal("error saving model: %r"); } Bterm(bout); return 0; } void drawvmodel(Image *dst, VModel *mdl) { int i; char *s; Point pts[3]; Point2 *p; p = mdl->pts; for(s = mdl->strokefmt; s != nil && p-mdl->pts < mdl->npts; s++) switch(*s){ case 'l': for(i = 0; i < nelem(pts)-1; i++) pts[i] = toscreen(invrframexform(p[i], mainobj)); line(dst, pts[0], pts[1], 0, 0, 0, display->white, ZP); for(i = 0; i < nelem(pts)-1; i++){ fillellipse(dst, pts[i], 2, 2, ptselcol, ZP); draw(dst, rectaddpt(Rect(0,0,1,1), pts[i]), display->black, nil, ZP); } p += 2; break; case 'c': for(i = 0; i < nelem(pts); i++) pts[i] = toscreen(invrframexform(p[i], mainobj)); bezspline(dst, pts, nelem(pts), 0, 0, 0, display->white, ZP); for(i = 0; i < nelem(pts); i++){ fillellipse(dst, pts[i], 2, 2, ptselcol, ZP); draw(dst, rectaddpt(Rect(0,0,1,1), pts[i]), display->black, nil, ZP); } p += 3; break; } } void drawaxes(void) { line(screen, toscreen(Pt2(0,512,1)), toscreen(Pt2(0,-512,1)), 0, 0, 0, display->white, ZP); line(screen, toscreen(Pt2(512,0,1)), toscreen(Pt2(-512,0,1)), 0, 0, 0, display->white, ZP); } void drawstrokepts(void) { Point2 *sp; Point pt; sp = ptstkp; while(sp-- > ptstk){ pt = toscreen(invrframexform(*sp, mainobj)); fillellipse(screen, pt, 2, 2, guidecol, ZP); draw(screen, rectaddpt(Rect(0,0,1,1), pt), display->black, nil, ZP); } } void drawinfo(void) { Point p; char buf[128]; p = Pt(10,3); snprint(buf, sizeof buf, "fmt %s", mainobj.mdl->strokefmt); string(screen, addpt(screen->r.min, p), display->white, ZP, font, buf); p.y += font->height; snprint(buf, sizeof buf, "op %s", strokename[stroke]); string(screen, addpt(screen->r.min, p), display->white, ZP, font, buf); p.y += font->height; snprint(buf, sizeof buf, "s %s", statename[curstate]); string(screen, addpt(screen->r.min, p), display->white, ZP, font, buf); } void drawguides(void) { Point p, hingepts[3]; Point2 hinge[3]; char buf[128]; double θ; int i; if(curstate == Rotating){ hinge[2] = Pt2(1,0,1); hinge[2] = invrframexform(hinge[2], mainobj); θ = atan2(hinge[2].y, hinge[2].x); hinge[0] = Vec2(1,0); hinge[2] = normvec2(Vec2(hinge[2].x,hinge[2].y)); hinge[1] = normvec2(divpt2(addpt2(hinge[0], hinge[2]), 2)); line(screen, toscreen(Pt2(0,0,1)), toscreen(addpt2(Pt2(0,0,1), mulpt2(hinge[2], 50))), 0, 0, 0, guidecol, ZP); for(i = 0; i < nelem(hinge); i++) hingepts[i] = toscreen(addpt2(Pt2(0,0,1), mulpt2(hinge[i], i&1? 25: 20))); bezspline(screen, hingepts, nelem(hingepts), 0, 0, 0, guidecol, ZP); p = toscreen(addpt2(Pt2(0,0,1), mulpt2(hinge[1], 50))); snprint(buf, sizeof buf, "%g°", θ/DEG); string(screen, p, guidecol, ZP, font, buf); } } void redraw(void) { lockdisplay(display); draw(screen, screen->r, display->black, nil, ZP); drawaxes(); drawvmodel(screen, mainobj.mdl); drawstrokepts(); drawguides(); drawinfo(); flushimage(display, 1); unlockdisplay(display); } void undo(void) { VModel *mdl; char *fmtp; mdl = mainobj.mdl; if(mdl->npts < 1) return; fmtp = &mdl->strokefmt[strlen(mdl->strokefmt)-1]; switch(*fmtp){ case 'l': mdl->npts -= 2; break; case 'c': mdl->npts -= 3; break; } *fmtp = 0; } void plot(Mousectl *mc, Keyboardctl *) { Point2 p; VModel *mdl; char *newfmt; ulong fmtlen; p = rframexform(fromscreen(mc->xy), mainobj); mdl = mainobj.mdl; if(ptstkp-ptstk < nelem(ptstk)){ *ptstkp++ = p; switch(stroke){ case SLine: if(ptstkp-ptstk == 2){ mdl->npts += 2; if(mdl->npts > mdl->cap){ mdl->cap = mdl->npts; mdl->pts = erealloc(mdl->pts, mdl->cap*sizeof(Point2)); } mdl->pts[mdl->npts-2] = ptstk[0]; mdl->pts[mdl->npts-1] = ptstk[1]; ptstkp = &ptstk[0]; if(mdl->strokefmt == nil){ fmtlen = 2; newfmt = emalloc(fmtlen); }else{ fmtlen = strlen(mdl->strokefmt)+2; newfmt = emalloc(fmtlen); memmove(newfmt, mdl->strokefmt, fmtlen-2); } newfmt[fmtlen-2] = 'l'; newfmt[fmtlen-1] = 0; free(mdl->strokefmt); mdl->strokefmt = newfmt; } break; case SCurve: if(ptstkp-ptstk == 3){ mdl->npts += 3; if(mdl->npts > mdl->cap){ mdl->cap = mdl->npts; mdl->pts = erealloc(mdl->pts, mdl->cap*sizeof(Point2)); } mdl->pts[mdl->npts-3] = ptstk[0]; mdl->pts[mdl->npts-2] = ptstk[1]; mdl->pts[mdl->npts-1] = ptstk[2]; ptstkp = &ptstk[0]; if(mdl->strokefmt == nil){ fmtlen = 2; newfmt = emalloc(fmtlen); }else{ fmtlen = strlen(mdl->strokefmt)+2; newfmt = emalloc(fmtlen); memmove(newfmt, mdl->strokefmt, fmtlen-2); } newfmt[fmtlen-2] = 'c'; newfmt[fmtlen-1] = 0; free(mdl->strokefmt); mdl->strokefmt = newfmt; } break; } } } void movept(Mousectl *mc, Keyboardctl *) { Point2 p0, p1, *pp; VModel *mdl; p1 = fromscreen(mc->xy); /* screen to world */ mdl = mainobj.mdl; for(pp = mdl->pts; pp < mdl->pts+mdl->npts; pp++){ p0 = invrframexform(*pp, mainobj); /* object to world */ if(vec2len(subpt2(p0, p1)) <= 2){ for(;;){ readmouse(mc); if(mc->buttons != 1) break; *pp = rframexform(fromscreen(mc->xy), mainobj); redraw(); } break; } } } void zoom(Mousectl *mc, Keyboardctl *) { double z; /* zooming factor */ Point oldxy, Δxy; curstate = Zooming; oldxy = mc->xy; for(;;){ readmouse(mc); if(mc->buttons != 2) break; Δxy = subpt(mc->xy, oldxy); z = tanh((double)Δxy.y/100) + 1; mainobj.scale(&mainobj, z); oldxy = mc->xy; redraw(); } curstate = Drawing; } void rota(Mousectl *mc, Keyboardctl *) { Point2 p; double oldθ, θ; curstate = Rotating; p = rframexform(fromscreen(mc->xy), mainobj); oldθ = atan2(p.y, p.x); for(;;){ readmouse(mc); if(mc->buttons != 4) break; p = rframexform(fromscreen(mc->xy), mainobj); θ = atan2(p.y, p.x) - oldθ; mainobj.rotate(&mainobj, θ); redraw(); } curstate = Drawing; } void mouse(Mousectl *mc, Keyboardctl *kc) { if((mc->buttons & 1) != 0){ if(curstate == Drawing) plot(mc, kc); else if(curstate == Movingpts) movept(mc, kc); } if((mc->buttons & 2) != 0) zoom(mc, kc); if((mc->buttons & 4) != 0) rota(mc, kc); } void key(Rune r) { switch(r){ case Kdel: case 'q': threadexitsall(nil); case 'w': writevmodel(mainobj.mdl, vmdlfile); break; case 'l': stroke = SLine; break; case 'c': stroke = SCurve; break; case 'm': curstate = Movingpts; break; case 'd': curstate = Drawing; break; case 'z': undo(); break; case Kesc: ptstkp = &ptstk[0]; break; } } void usage(void) { fprint(2, "usage: %s file\n", argv0); exits("usage"); } void threadmain(int argc, char *argv[]) { Mousectl *mc; Keyboardctl *kc; Rune r; GEOMfmtinstall(); ARGBEGIN{ default: usage(); }ARGEND; if(argc != 1) usage(); vmdlfile = argv[0]; if(newwindow(nil) < 0) sysfatal("newwindow: %r"); if(initdraw(nil, nil, "vmodeled") < 0) sysfatal("initdraw: %r"); if((mc = initmouse(nil, screen)) == nil) sysfatal("initmouse: %r"); if((kc = initkeyboard(nil)) == nil) sysfatal("initkeyboard: %r"); worldrf.p = Pt2(screen->r.min.x+Dx(screen->r)/2,screen->r.max.y-Dy(screen->r)/2,1); worldrf.bx = Vec2(1, 0); worldrf.by = Vec2(0,-1); mainobj.bx = Vec2(1, 0); mainobj.by = Vec2(0, 1); mainobj.scale = obj_scale; mainobj.rotate = obj_rotate; mainobj.mdl = readvmodel(vmdlfile); if(mainobj.mdl == nil) sysfatal("readvmodel: %r"); ptstkp = &ptstk[0]; ptselcol = eallocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellow); guidecol = eallocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalebluegreen); display->locking = 1; unlockdisplay(display); redraw(); for(;;){ enum { MOUSE, RESIZE, KEYBOARD }; Alt a[] = { {mc->c, &mc->Mouse, CHANRCV}, {mc->resizec, nil, CHANRCV}, {kc->c, &r, CHANRCV}, {nil, nil, CHANEND} }; switch(alt(a)){ case MOUSE: mouse(mc, kc); break; case RESIZE: resized(); break; case KEYBOARD: key(r); break; } redraw(); } } void resized(void) { lockdisplay(display); if(getwindow(display, Refnone) < 0) sysfatal("couldn't resize"); unlockdisplay(display); worldrf.p = Pt2(screen->r.min.x+Dx(screen->r)/2,screen->r.max.y-Dy(screen->r)/2,1); redraw(); }