shithub: musw

ref: 5a725b7c6a1c22800656f218e46b5b16b0d21884
dir: /vmodeled/main.c/

View raw version
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include <geometry.h>

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();
}