shithub: 3dee

ref: 590c394f3cf8e8f6c8612ff8feb76a2c78a59613
dir: /raymarch.c/

View raw version
/*
 * based on kishimisu's “An Introduction to Raymarching”
 * - https://www.youtube.com/watch?v=khblXafu7iA
 */
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <draw.h>
#include <memdraw.h>
#include <mouse.h>
#include <keyboard.h>
#include <geometry.h>
#include "libgraphics/graphics.h"
#include "fns.h"

#define sd∪(a, b)	min((a), (b))
#define sd∩(a, b)	max((a), (b))
#define sdsub(a, b)	max(-(a), (b))

Mousectl *mctl;
Keyboardctl *kctl;
Channel *drawc;
Image *screenb;
Camera *cam;
Scene *scn;
Entity *ent;
Model *mdl;

static int doprof;

static Point3
abspt3(Point3 p)
{
	return (Point3){
		fabs(p.x),
		fabs(p.y),
		fabs(p.z),
		fabs(p.w)
	};
}

static double
smin(double a, double b, double k)
{
	double h;

	h = max(k - fabs(a - b), 0)/k;
	return min(a, b) - h*h*h * k/6;
}

static double
sdBox(Point3 p, Point3 span)
{
	double tmp;

	p = subpt3(abspt3(p), span);
	tmp = min(max(p.x, max(p.y, p.z)), 0);
	return vec3len(maxpt3(p, ZP3)) + tmp;
}

static double
sdSphere(Point3 p, double r)
{
	return vec3len(p) - r;
}

static double
map(Point3 p, double dt)
{
	enum {
		RADIUS	= 1,
		SIDELEN	= 0.75,
	};
	Point3 sphp;
	double sphere, box, gnd;

	sphp = Pt3(sin(dt)*3, 0, 0, 1);
	sphere = sdSphere(subpt3(p, sphp), RADIUS);
	box = sdBox(p, Vec3(SIDELEN, SIDELEN, SIDELEN));
	gnd = p.y + 0.75;

	return sd∪(gnd, smin(sphere, box, 2));
}

static Point3
vs(Shaderparams *sp)
{
	return sp->v->p;
}

static Color
fs(Shaderparams *sp)
{
	Vertexattr *va;
	Point2 uv, m;
	Point3 mpt, ro, rd, p;
	Color c;
	double time, dt, t, d;
	int i;

	uv = Pt2(sp->p.x, sp->p.y, 1);
	uv.x = 2*uv.x - Dx(sp->su->fb->r);
	uv.y = Dy(sp->su->fb->r) - 2*uv.y;
	uv = divpt2(uv, Dy(sp->su->fb->r));

	va = sp->getuniform(sp, "time");
	time = va == nil? 0: va->n;
	dt = time/1e9;

	va = sp->getuniform(sp, "mouse");
	mpt = va == nil? ZP3: va->p;
	m = Pt2(mpt.x, mpt.y, 1);
	m.x = 2*m.x - Dx(sp->su->fb->r);
	m.y = Dy(sp->su->fb->r) - 2*m.y;
	m = divpt2(m, Dy(sp->su->fb->r)/2);

	ro = Pt3(0, 0, 3, 1);
	rd = normvec3(Vec3(uv.x, uv.y, -1));
	t = 0;

	ro = qrotate(qrotate(ro, Vec3(0,1,0), -m.x), Vec3(1,0,0), m.y);
	rd = qrotate(qrotate(rd, Vec3(0,1,0), -m.x), Vec3(1,0,0), m.y);

	for(i = 0; i < 20; i++){
		p = addpt3(ro, mulpt3(rd, t));
		d = map(p, dt);
		t += d;

		if(d < 0.001 || t > 100)
			break;
	}

	c = mulpt3(Vec3(t, t, t), 0.2);
	c.a = 1;
	return srgb2linear(c);
}

Shadertab shaders = {
	.vs = vs,
	.fs = fs
};

void
redraw(void)
{
	lockdisplay(display);
	draw(screen, screen->r, screenb, nil, ZP);
	flushimage(display, 1);
	unlockdisplay(display);
}

void
drawproc(void *)
{
	threadsetname("drawproc");

	for(;;){
		recv(drawc, nil);
		redraw();
	}
}

void
renderproc(void *)
{
	Point3 mpt;
	uvlong t0, Δt;
	double time;

	threadsetname("renderproc");

	t0 = nanosec();
	for(;;){
		time = nanosec();
		setuniform(&shaders, "time", VANumber, &time);
		mpt = Vec3(mctl->xy.x, mctl->xy.y, 0);
		setuniform(&shaders, "mouse", VAPoint, &mpt);
		shootcamera(cam, &shaders);

		Δt = nanosec() - t0;
		if(Δt > HZ2NS(60)){
			lockdisplay(display);
			draw(screenb, screenb->r, display->black, nil, ZP);
			cam->view->draw(cam->view, screenb, nil);
			unlockdisplay(display);
			nbsend(drawc, nil);
			t0 = nanosec();
		}else{
			Δt = HZ2NS(60) - Δt;
			if(Δt > 1000000ULL)
				sleep(Δt/1000000ULL);
			else
				sleep(1);
		}
	}
}

void
mouse(void)
{
	mctl->xy = subpt(mctl->xy, screen->r.min);
}

void
resize(void)
{
	lockdisplay(display);
	if(getwindow(display, Refnone) < 0)
		fprint(2, "can't reattach to window\n");
	unlockdisplay(display);
	nbsend(drawc, nil);
}

void
key(Rune r)
{
	switch(r){
	case Kdel:
	case 'q':
		threadexitsall(nil);
	}
}

static void
confproc(void)
{
	char buf[64];
	int fd;

	snprint(buf, sizeof buf, "/proc/%d/ctl", getpid());
	fd = open(buf, OWRITE);
	if(fd < 0)
		sysfatal("open: %r");

	if(doprof)
		fprint(fd, "profile\n");
	close(fd);
}

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

void
threadmain(int argc, char *argv[])
{
	Renderer *rctl;
	Primitive quad[2];
	Vertex v;
	Rune r;

	ARGBEGIN{
	case 'p': doprof++; break;
	default: usage();
	}ARGEND;
	if(argc != 0)
		usage();

	confproc();

	if(memimageinit() != 0)
		sysfatal("memimageinit: %r");
	if((rctl = initgraphics()) == nil)
		sysfatal("initgraphics: %r");
	if(initdraw(nil, nil, "raymarch") < 0)
		sysfatal("initdraw: %r");
	if((mctl = initmouse(nil, screen)) == nil)
		sysfatal("initmouse: %r");
	if((kctl = initkeyboard(nil)) == nil)
		sysfatal("initkeyboard: %r");

	rctl->doprof = doprof;

	scn = newscene(nil);
	mdl = newmodel();
	ent = newentity(nil, mdl);

	screenb = eallocimage(display, rectsubpt(screen->r, screen->r.min), XRGB32, 0, DNofill);
	cam = Cam(screenb->r, rctl, ORTHOGRAPHIC, 40*DEG, 1, 10);
	placecamera(cam, scn, Pt3(0,0,0,1), Vec3(0,0,-1), Vec3(0,1,0));

	quad[0] = quad[1] = mkprim(PTriangle);
	v = mkvert();
	v.p = mdl->addposition(mdl, vcs2clip(cam, viewport2vcs(cam, Pt3(screenb->r.min.x, screenb->r.max.y, 1, 1))));
	quad[0].v[0] = mdl->addvert(mdl, v);
	v.p = mdl->addposition(mdl, vcs2clip(cam, viewport2vcs(cam, Pt3(screenb->r.max.x, screenb->r.min.y, 1, 1))));
	quad[0].v[1] = mdl->addvert(mdl, v);
	v.p = mdl->addposition(mdl, vcs2clip(cam, viewport2vcs(cam, Pt3(screenb->r.min.x, screenb->r.min.y, 1, 1))));
	quad[0].v[2] = mdl->addvert(mdl, v);
	quad[1].v[0] = quad[0].v[0];
	v.p = mdl->addposition(mdl, vcs2clip(cam, viewport2vcs(cam, Pt3(screenb->r.max.x, screenb->r.max.y, 1, 1))));
	quad[1].v[1] = mdl->addvert(mdl, v);
	quad[1].v[2] = quad[0].v[1];
	mdl->addprim(mdl, quad[0]);
	mdl->addprim(mdl, quad[1]);
	scn->addent(scn, ent);

	drawc = chancreate(sizeof(void*), 1);
	display->locking = 1;
	unlockdisplay(display);

	proccreate(renderproc, nil, mainstacksize);
	proccreate(drawproc, nil, mainstacksize);

	enum {MOUSE, RESIZE, KEY};
	Alt a[] = {
		{mctl->c, &mctl->Mouse, CHANRCV},
		{mctl->resizec, nil, CHANRCV},
		{kctl->c, &r, CHANRCV},
		{nil, nil, CHANEND}
	};
	for(;;)
		switch(alt(a)){
		case MOUSE: mouse(); break;
		case RESIZE: resize(); break;
		case KEY: key(r); break;
		}
}