shithub: boids

ref: 891e6af8da9b09659355c663e55330b2b60ef3a9
dir: /main.c/

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

#define HZ2MS(hz)	(1000/(hz))

enum {
	TERMINALV = 40,
	BORDER = 100
};

enum {
	Cbg,
	Cfg,
	NCOLOR
};

typedef struct Bird Bird;
typedef struct Flock Flock;

struct Bird
{
	Point2 p, v;
	double sight;
	Image *color;
};

struct Flock
{
	Bird *birds;
	int nbirds;
	Rectangle jail;

	void (*separate)(Flock*);
	void (*align)(Flock*);
	void (*cohesion)(Flock*);
	void (*step)(Flock*);
};

RFrame worldrf;
Image *pal[NCOLOR];
Flock *flock;
int nbirds;
int paused;
QLock pauselk;

void flockseparate(Flock*);
void flockalign(Flock*);
void flockcohesion(Flock*);
void flockstep(Flock*);

Flock*
newflock(int nbirds, Rectangle jail)
{
	Flock *f;
	Bird *b;

	f = malloc(sizeof(Flock));
	if(f == nil)
		sysfatal("malloc: %r");
	f->birds = malloc(nbirds*sizeof(Bird));
	if(f->birds == nil)
		sysfatal("malloc: %r");
	f->nbirds = nbirds;
	f->jail = jail;
	f->separate = flockseparate;
	f->align = flockalign;
	f->cohesion = flockcohesion;
	f->step = flockstep;

	for(b = f->birds; b < f->birds + f->nbirds; b++){
		b->p = Pt2(frand()*jail.max.x + jail.min.x,frand()*jail.max.y + jail.min.y,1);
		b->v = Vec2(cos(frand()*2*PI),sin(frand()*2*PI));
		b->v = mulpt2(b->v, TERMINALV);
		b->sight = 75;
		b->color = pal[Cfg];
	}

	return f;
}

void
flockseparate(Flock *f)
{
	static double comfortdist = 8;
	static double repel = 0.05;
	Point2 repelling, Δp;
	Bird *b0, *b1;

	for(b0 = f->birds; b0 < f->birds + f->nbirds; b0++){
		repelling = Vec2(0,0);

		for(b1 = f->birds; b1 < f->birds + f->nbirds; b1++)
			if(b0 != b1){
				Δp = subpt2(b0->p, b1->p);
				if(vec2len(Δp) < comfortdist)
					repelling = addpt2(repelling, Δp);
			}

		b0->v = addpt2(b0->v, mulpt2(repelling, repel));
	}
}

void
flockalign(Flock *f)
{
	static double attract = 0.05;
	int neighbors;
	Point2 attracting;
	Bird *b0, *b1;

	for(b0 = f->birds; b0 < f->birds + f->nbirds; b0++){
		neighbors = 0;
		attracting = Vec2(0,0);

		for(b1 = f->birds; b1 < f->birds + f->nbirds; b1++)
			if(vec2len(subpt2(b0->p, b1->p)) < b0->sight){
				attracting = addpt2(attracting, b1->v);
				neighbors++;
			}

		if(neighbors > 0){
			attracting = divpt2(attracting, neighbors);
			b0->v = addpt2(b0->v, mulpt2(subpt2(attracting, b0->v), attract));
		}
	}
}

void
flockcohesion(Flock *f)
{
	static double center = 0.005;
	int neighbors;
	Point2 centering;
	Bird *b0, *b1;

	for(b0 = f->birds; b0 < f->birds + f->nbirds; b0++){
		neighbors = 0;
		centering = Vec2(0,0);

		for(b1 = f->birds; b1 < f->birds + f->nbirds; b1++)
			if(vec2len(subpt2(b0->p, b1->p)) < b0->sight){
				centering = addpt2(centering, b1->p);
				neighbors++;
			}

		if(neighbors > 0){
			centering = divpt2(centering, neighbors);
			b0->v = addpt2(b0->v, mulpt2(subpt2(centering, b0->p), center));
		}
	}
}

void
flockstep(Flock *f)
{
	static double Δt = 1;
	static Point2 a = {1,1}; /* deceleration */
	Bird *b;

	f->separate(f);
	f->align(f);
	f->cohesion(f);
	for(b = f->birds; b < f->birds + f->nbirds; b++){
		b->p = addpt2(b->p, mulpt2(b->v, Δt));
		if(b->p.x < f->jail.min.x+BORDER)
			b->v.x += a.x;
		if(b->p.y < f->jail.min.y+BORDER)
			b->v.y += a.y;
		if(b->p.x > f->jail.max.x-BORDER)
			b->v.x -= a.x;
		if(b->p.y > f->jail.max.y-BORDER)
			b->v.y -= a.y;
	}
}

void
resetflock(void)
{
	flock = newflock(nbirds, Rect(0,0,Dx(screen->r),Dy(screen->r)));
}

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
initpalette(void)
{
	pal[Cbg] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x000000ff);
	pal[Cfg] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xffffffff);
}

void
redraw(void)
{
	Bird *b;

	lockdisplay(display);
	draw(screen, screen->r, pal[Cbg], nil, ZP);
	for(b = flock->birds; b < flock->birds + flock->nbirds; b++)
		ellipse(screen, toscreen(b->p), 2, 2, 0, b->color, ZP);
	flushimage(display, 1);
	unlockdisplay(display);
}

void
resized(void)
{
	lockdisplay(display);
	if(getwindow(display, Refnone) < 0)
		sysfatal("resize failed");
	unlockdisplay(display);
	worldrf.p = Pt2(screen->r.min.x,screen->r.max.y,1);
	flock->jail = Rect(0,0,Dx(screen->r),Dy(screen->r));
	redraw();
}

void
rmb(Mousectl *mc, Keyboardctl *kc)
{
	enum {
		RESET,
		NBIRDS
	};
	static char *items[] = {
	 [RESET]	"reset",
	 [NBIRDS]	"set bird number",
		nil
	};
	static Menu menu = { .item = items };
	char buf[128];

	switch(menuhit(3, mc, &menu, _screen)){
	case NBIRDS:
		snprint(buf, sizeof buf, "%d", nbirds);
		enter("nbirds", buf, sizeof buf, mc, kc, _screen);
		nbirds = strtol(buf, nil, 10);
	case RESET:
		srand(time(nil));
		resetflock();
		break;
	}
}

void
mouse(Mousectl *mc, Keyboardctl *kc)
{
	if((mc->buttons&4) != 0)
		rmb(mc, kc);
}

void
key(Rune r)
{
	switch(r){
	case Kdel:
	case 'q':
		threadexitsall(nil);
	case ' ':
		if(paused)
			qunlock(&pauselk);
		else
			qlock(&pauselk);
		paused = !paused;
		break;
	}
}

void
runsim(void *)
{
	Ioproc *io;

	io = ioproc();

	for(;;){
		qlock(&pauselk);
		flock->step(flock);
		qunlock(&pauselk);
		redraw();
		iosleep(io, HZ2MS(24));
	}
}

void
usage(void)
{
	fprint(2, "usage: %s [-n nbirds]\n", argv0);
	exits("usage");
}

void
threadmain(int argc, char *argv[])
{
	Mousectl *mc;
	Keyboardctl *kc;
	Rune r;
	char *s;

	nbirds = 100;
	ARGBEGIN{
	case 'n':
		nbirds = strtol(EARGF(usage()), &s, 10);
		if(*s != 0)
			usage();
		break;
	default: usage();
	}ARGEND;
	if(argc > 0)
		usage();

	if(initdraw(nil, nil, nil) < 0)
		sysfatal("initdraw: %r");
	if((mc = initmouse(nil, screen)) == nil)
		sysfatal("initmouse: %r");
	if((kc = initkeyboard(nil)) == nil)
		sysfatal("initkeyboard: %r");
	initpalette();
	worldrf.p = Pt2(screen->r.min.x,screen->r.max.y,1);
	worldrf.bx = Vec2(1, 0);
	worldrf.by = Vec2(0,-1);
	resetflock();

	display->locking = 1;
	unlockdisplay(display);
	threadcreate(runsim, nil, mainstacksize);
	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();
	}
}