shithub: 3dee

Download patch

ref: eddb2d366ba452ff2993aa4c827150767a1fcc59
parent: 4966f25444d343ade4a6fbdd29f6175f372da09d
author: rodri <[email protected]>
date: Sat Jul 13 16:51:42 EDT 2024

use the new camera interface. solar got faster with backface culling.

--- a/med.c
+++ b/med.c
@@ -79,7 +79,7 @@
 Mouse om;
 Quaternion orient = {1,0,0,0};
 
-Camera cam;
+Camera *cam;
 Camcfg camcfg = {
 	0,2,4,1,
 	0,0,0,1,
@@ -121,22 +121,20 @@
 materializefrustum(void)
 {
 	Primitive l;
-	Framebuf *fb;
 	Point3 p[4];
 	int i;
 
-	fb = cam.vp->getfb(cam.vp);
 	p[0] = Pt3(0,0,1,1);
-	p[1] = Pt3(Dx(fb->r),0,1,1);
-	p[2] = Pt3(Dx(fb->r),Dy(fb->r),1,1);
-	p[3] = Pt3(0,Dy(fb->r),1,1);
+	p[1] = Pt3(Dx(cam->vp->r),0,1,1);
+	p[2] = Pt3(Dx(cam->vp->r),Dy(cam->vp->r),1,1);
+	p[3] = Pt3(0,Dy(cam->vp->r),1,1);
 	memset(&l, 0, sizeof l);
 
 	for(i = 0; i < nelem(p); i++){
 		/* front frame */
 		l.type = PLine;
-		l.v[0].p = world2model(subject, viewport2world(&cam, p[i]));
-		l.v[1].p = world2model(subject, viewport2world(&cam, p[(i+1)%nelem(p)]));
+		l.v[0].p = world2model(subject, viewport2world(cam, p[i]));
+		l.v[1].p = world2model(subject, viewport2world(cam, p[(i+1)%nelem(p)]));
 		qlock(&scenelk);
 		model->prims = erealloc(model->prims, ++model->nprims*sizeof(*model->prims));
 		model->prims[model->nprims-1] = l;
@@ -143,8 +141,8 @@
 		qunlock(&scenelk);
 
 		/* middle frame */
-		l.v[0].p = world2model(subject, viewport2world(&cam, subpt3(p[i], Vec3(0,0,0.5))));
-		l.v[1].p = world2model(subject, viewport2world(&cam, subpt3(p[(i+1)%nelem(p)], Vec3(0,0,0.5))));
+		l.v[0].p = world2model(subject, viewport2world(cam, subpt3(p[i], Vec3(0,0,0.5))));
+		l.v[1].p = world2model(subject, viewport2world(cam, subpt3(p[(i+1)%nelem(p)], Vec3(0,0,0.5))));
 		qlock(&scenelk);
 		model->prims = erealloc(model->prims, ++model->nprims*sizeof(*model->prims));
 		model->prims[model->nprims-1] = l;
@@ -151,8 +149,8 @@
 		qunlock(&scenelk);
 
 		/* back frame */
-		l.v[0].p = world2model(subject, viewport2world(&cam, subpt3(p[i], Vec3(0,0,1))));
-		l.v[1].p = world2model(subject, viewport2world(&cam, subpt3(p[(i+1)%nelem(p)], Vec3(0,0,1))));
+		l.v[0].p = world2model(subject, viewport2world(cam, subpt3(p[i], Vec3(0,0,1))));
+		l.v[1].p = world2model(subject, viewport2world(cam, subpt3(p[(i+1)%nelem(p)], Vec3(0,0,1))));
 		qlock(&scenelk);
 		model->prims = erealloc(model->prims, ++model->nprims*sizeof(*model->prims));
 		model->prims[model->nprims-1] = l;
@@ -159,7 +157,7 @@
 		qunlock(&scenelk);
 
 		/* struts */
-		l.v[1].p = world2model(subject, viewport2world(&cam, p[i]));
+		l.v[1].p = world2model(subject, viewport2world(cam, p[i]));
 		qlock(&scenelk);
 		model->prims = erealloc(model->prims, ++model->nprims*sizeof(*model->prims));
 		model->prims[model->nprims-1] = l;
@@ -437,15 +435,15 @@
 void
 zoomin(void)
 {
-	cam.fov = fclamp(cam.fov - 1*DEG, 1*DEG, 180*DEG);
-	reloadcamera(&cam);
+	cam->fov = fclamp(cam->fov - 1*DEG, 1*DEG, 180*DEG);
+	reloadcamera(cam);
 }
 
 void
 zoomout(void)
 {
-	cam.fov = fclamp(cam.fov + 1*DEG, 1*DEG, 180*DEG);
-	reloadcamera(&cam);
+	cam->fov = fclamp(cam->fov + 1*DEG, 1*DEG, 180*DEG);
+	reloadcamera(cam);
 }
 
 void
@@ -453,13 +451,13 @@
 {
 	int i;
 
-	snprint(stats[Sfov], sizeof(stats[Sfov]), "FOV %g°", cam.fov/DEG);
-	snprint(stats[Scampos], sizeof(stats[Scampos]), "%V", cam.p);
-	snprint(stats[Scambx], sizeof(stats[Scambx]), "bx %V", cam.bx);
-	snprint(stats[Scamby], sizeof(stats[Scamby]), "by %V", cam.by);
-	snprint(stats[Scambz], sizeof(stats[Scambz]), "bz %V", cam.bz);
-	snprint(stats[Sfps], sizeof(stats[Sfps]), "FPS %.0f/%.0f/%.0f/%.0f", !cam.stats.max? 0: 1e9/cam.stats.max, !cam.stats.avg? 0: 1e9/cam.stats.avg, !cam.stats.min? 0: 1e9/cam.stats.min, !cam.stats.v? 0: 1e9/cam.stats.v);
-	snprint(stats[Sframes], sizeof(stats[Sframes]), "frame %llud", cam.stats.nframes);
+	snprint(stats[Sfov], sizeof(stats[Sfov]), "FOV %g°", cam->fov/DEG);
+	snprint(stats[Scampos], sizeof(stats[Scampos]), "%V", cam->p);
+	snprint(stats[Scambx], sizeof(stats[Scambx]), "bx %V", cam->bx);
+	snprint(stats[Scamby], sizeof(stats[Scamby]), "by %V", cam->by);
+	snprint(stats[Scambz], sizeof(stats[Scambz]), "bz %V", cam->bz);
+	snprint(stats[Sfps], sizeof(stats[Sfps]), "FPS %.0f/%.0f/%.0f/%.0f", !cam->stats.max? 0: 1e9/cam->stats.max, !cam->stats.avg? 0: 1e9/cam->stats.avg, !cam->stats.min? 0: 1e9/cam->stats.min, !cam->stats.v? 0: 1e9/cam->stats.v);
+	snprint(stats[Sframes], sizeof(stats[Sframes]), "frame %llud", cam->stats.nframes);
 	for(i = 0; i < Se; i++)
 		stringbg(screen, addpt(screen->r.min, Pt(10,10 + i*font->height)), display->black, ZP, font, stats[i], display->white, ZP);
 }
@@ -468,7 +466,7 @@
 redraw(void)
 {
 	lockdisplay(display);
-	cam.vp->draw(cam.vp, screenb);
+	cam->vp->draw(cam->vp, screenb);
 	draw(screen, screen->r, screenb, nil, ZP);
 	if(showhud)
 		drawstats();
@@ -486,14 +484,14 @@
 	t0 = nsec();
 	for(;;){
 		qlock(&scenelk);
-		shootcamera(&cam, shader);
+		shootcamera(cam, shader);
 		qunlock(&scenelk);
 		if(doprof)
 		fprint(2, "R %llud %llud\nE %llud %llud\nT %llud %llud\nr %llud %llud\n\n",
-			cam.times.R[cam.times.cur-1].t0, cam.times.R[cam.times.cur-1].t1,
-			cam.times.E[cam.times.cur-1].t0, cam.times.E[cam.times.cur-1].t1,
-			cam.times.Tn[cam.times.cur-1].t0, cam.times.Tn[cam.times.cur-1].t1,
-			cam.times.Rn[cam.times.cur-1].t0, cam.times.Rn[cam.times.cur-1].t1);
+			cam->times.R[cam->times.cur-1].t0, cam->times.R[cam->times.cur-1].t1,
+			cam->times.E[cam->times.cur-1].t0, cam->times.E[cam->times.cur-1].t1,
+			cam->times.Tn[cam->times.cur-1].t0, cam->times.Tn[cam->times.cur-1].t1,
+			cam->times.Rn[cam->times.cur-1].t0, cam->times.Rn[cam->times.cur-1].t1);
 		Δt = nsec() - t0;
 		if(Δt > HZ2MS(60)*1000000ULL){
 			nbsend(drawc, nil);
@@ -525,9 +523,9 @@
 		Δorient = mulq(orient, invq(Δorient));
 
 		for(e = scene->ents.next; e != &scene->ents; e = e->next){
-			e->bx = vcs2world(&cam, Vecquat(mulq(mulq(Δorient, Quatvec(0, world2vcs(&cam, e->bx))), invq(Δorient))));
-			e->by = vcs2world(&cam, Vecquat(mulq(mulq(Δorient, Quatvec(0, world2vcs(&cam, e->by))), invq(Δorient))));
-			e->bz = vcs2world(&cam, Vecquat(mulq(mulq(Δorient, Quatvec(0, world2vcs(&cam, e->bz))), invq(Δorient))));
+			e->bx = vcs2world(cam, Vecquat(mulq(mulq(Δorient, Quatvec(0, world2vcs(cam, e->bx))), invq(Δorient))));
+			e->by = vcs2world(cam, Vecquat(mulq(mulq(Δorient, Quatvec(0, world2vcs(cam, e->by))), invq(Δorient))));
+			e->bz = vcs2world(cam, Vecquat(mulq(mulq(Δorient, Quatvec(0, world2vcs(cam, e->bz))), invq(Δorient))));
 		}
 	}
 }
@@ -595,7 +593,7 @@
 		goto nohit;
 	if(idx < nelem(shadertab)){
 		shader = &shadertab[idx];
-		memset(&cam.stats, 0, sizeof(cam.stats));
+		memset(&cam->stats, 0, sizeof(cam->stats));
 	}
 	idx -= nelem(shadertab);
 	switch(idx){
@@ -688,29 +686,29 @@
 	static int okdown;
 
 	if(kdown & 1<<K↑)
-		placecamera(&cam, subpt3(cam.p, mulpt3(cam.bz, 0.1)), cam.bz, cam.by);
+		movecamera(cam, mulpt3(cam->bz, -0.1));
 	if(kdown & 1<<K↓)
-		placecamera(&cam, addpt3(cam.p, mulpt3(cam.bz, 0.1)), cam.bz, cam.by);
+		movecamera(cam, mulpt3(cam->bz, 0.1));
 	if(kdown & 1<<K←)
-		placecamera(&cam, subpt3(cam.p, mulpt3(cam.bx, 0.1)), cam.bz, cam.by);
+		movecamera(cam, mulpt3(cam->bx, -0.1));
 	if(kdown & 1<<K→)
-		placecamera(&cam, addpt3(cam.p, mulpt3(cam.bx, 0.1)), cam.bz, cam.by);
+		movecamera(cam, mulpt3(cam->bx, 0.1));
 	if(kdown & 1<<Krise)
-		placecamera(&cam, addpt3(cam.p, mulpt3(cam.by, 0.1)), cam.bz, cam.by);
+		movecamera(cam, mulpt3(cam->by, 0.1));
 	if(kdown & 1<<Kfall)
-		placecamera(&cam, subpt3(cam.p, mulpt3(cam.by, 0.1)), cam.bz, cam.by);
+		movecamera(cam, mulpt3(cam->by, -0.1));
 	if(kdown & 1<<KR↑)
-		aimcamera(&cam, qrotate(cam.bz, cam.bx, 1*DEG));
+		rotatecamera(cam, cam->bx, 1*DEG);
 	if(kdown & 1<<KR↓)
-		aimcamera(&cam, qrotate(cam.bz, cam.bx, -1*DEG));
+		rotatecamera(cam, cam->bx, -1*DEG);
 	if(kdown & 1<<KR←)
-		aimcamera(&cam, qrotate(cam.bz, cam.by, 1*DEG));
+		rotatecamera(cam, cam->by, 1*DEG);
 	if(kdown & 1<<KR→)
-		aimcamera(&cam, qrotate(cam.bz, cam.by, -1*DEG));
+		rotatecamera(cam, cam->by, -1*DEG);
 	if(kdown & 1<<KR↺)
-		placecamera(&cam, cam.p, cam.bz, qrotate(cam.by, cam.bz, 1*DEG));
+		rotatecamera(cam, cam->bz, 1*DEG);
 	if(kdown & 1<<KR↻)
-		placecamera(&cam, cam.p, cam.bz, qrotate(cam.by, cam.bz, -1*DEG));
+		rotatecamera(cam, cam->bz, -1*DEG);
 	if(kdown & 1<<Kzoomin)
 		zoomin();
 	if(kdown & 1<<Kzoomout)
@@ -762,7 +760,6 @@
 void
 threadmain(int argc, char *argv[])
 {
-	Viewport *v;
 	Renderer *rctl;
 	Channel *keyc;
 
@@ -795,11 +792,8 @@
 		sysfatal("initmouse: %r");
 
 	screenb = eallocimage(display, rectsubpt(screen->r, screen->r.min), XRGB32, 0, DNofill);
-	v = mkviewport(screenb->r);
-	placecamera(&cam, camcfg.p, camcfg.lookat, camcfg.up);
-	configcamera(&cam, v, camcfg.fov, camcfg.clipn, camcfg.clipf, camcfg.ptype);
-	cam.scene = scene;
-	cam.rctl = rctl;
+	cam = Cam(screenb->r, rctl, camcfg.ptype, camcfg.fov, camcfg.clipn, camcfg.clipf);
+	placecamera(cam, scene, camcfg.p, camcfg.lookat, camcfg.up);
 	light.p = Pt3(0,100,100,1);
 	light.c = Pt3(1,1,1,1);
 	light.type = LIGHT_POINT;
--- a/projtest.c
+++ b/projtest.c
@@ -10,7 +10,7 @@
 #include "libgraphics/graphics.h"
 #include "fns.h"
 
-Camera cam;
+Camera *cam;
 
 void
 usage(void)
@@ -35,15 +35,15 @@
 	if(memimageinit() != 0)
 		sysfatal("memimageinit: %r");
 
-	placecamera(&cam, Pt3(0,0,1,1), Vec3(0,0,0), Vec3(0,1,0));
-	configcamera(&cam, mkviewport(Rect(0,0,640,480)), 40*DEG, 0.01, 10, PERSPECTIVE);
+	cam = Cam(Rect(0,0,640,480), nil, PERSPECTIVE, 40*DEG, 0.01, 10);
+	placecamera(cam, nil, Pt3(0,0,1,1), Vec3(0,0,0), Vec3(0,1,0));
 
-	fb = cam.vp->getfb(cam.vp);
+	fb = cam->vp->getfb(cam->vp);
 	np = Pt3(0,0,-0.01,1);
 	fp = Pt3(0,0,-10,1);
 	fprint(2, "near %V\nfar %V\n", np, fp);
-	np = vcs2clip(&cam, np);
-	fp = vcs2clip(&cam, fp);
+	np = vcs2clip(cam, np);
+	fp = vcs2clip(cam, fp);
 	fprint(2, "E → C\n");
 	fprint(2, "near %V\n", np);
 	fprint(2, "far %V\n", fp);
@@ -62,8 +62,8 @@
 	fprint(2, "V → N\n");
 	fprint(2, "near %V\n", np);
 	fprint(2, "far %V\n", fp);
-	np = ndc2vcs(&cam, np);
-	fp = ndc2vcs(&cam, fp);
+	np = ndc2vcs(cam, np);
+	fp = ndc2vcs(cam, fp);
 	fprint(2, "N → E\n");
 	fprint(2, "near %V\n", np);
 	fprint(2, "far %V\n", fp);
@@ -78,13 +78,13 @@
 	fprint(2, "V → N\n");
 	fprint(2, "near %V\n", np);
 	fprint(2, "far %V\n", fp);
-	np = ndc2vcs(&cam, np);
-	fp = ndc2vcs(&cam, fp);
+	np = ndc2vcs(cam, np);
+	fp = ndc2vcs(cam, fp);
 	fprint(2, "N → E\n");
 	fprint(2, "near %V\n", np);
 	fprint(2, "far %V\n", fp);
-	np = vcs2clip(&cam, np);
-	fp = vcs2clip(&cam, fp);
+	np = vcs2clip(cam, np);
+	fp = vcs2clip(cam, fp);
 	fprint(2, "E → C\n");
 	fprint(2, "near %V\n", np);
 	fprint(2, "far %V\n", fp);
--- a/solar.c
+++ b/solar.c
@@ -145,10 +145,10 @@
 char datestr[16];
 Scene *scene;
 
-Camera camera;
+Camera *camera;
 Camcfg cameracfg = {
 	0,0,0,1,
-	0,0,0,1,
+	0,0,1,0,
 	0,1,0,0,
 	80*DEG, 1, 1e12, PERSPECTIVE
 };
@@ -273,7 +273,8 @@
 static void
 gotoplanet(Planet *p)
 {
-	placecamera(&camera, addpt3(p->body->p, Vec3(0,0,1.5*p->scale)), p->body->p, cameracfg.up);
+	movecamera(camera, addpt3(p->body->p, Vec3(0,0,1.5*p->scale)));
+	aimcamera(camera, p->body->p);
 }
 
 /*
@@ -347,15 +348,15 @@
 void
 zoomin(void)
 {
-	camera.fov = fclamp(camera.fov - 1*DEG, 1*DEG, 180*DEG);
-	reloadcamera(&camera);
+	camera->fov = fclamp(camera->fov - 1*DEG, 1*DEG, 180*DEG);
+	reloadcamera(camera);
 }
 
 void
 zoomout(void)
 {
-	camera.fov = fclamp(camera.fov + 1*DEG, 1*DEG, 180*DEG);
-	reloadcamera(&camera);
+	camera->fov = fclamp(camera->fov + 1*DEG, 1*DEG, 180*DEG);
+	reloadcamera(camera);
 }
 
 void
@@ -363,13 +364,13 @@
 {
 	int i;
 
-	snprint(stats[Sfov], sizeof(stats[Sfov]), "FOV %g°", camera.fov/DEG);
-	snprint(stats[Scampos], sizeof(stats[Scampos]), "%V", camera.p);
-	snprint(stats[Scambx], sizeof(stats[Scambx]), "bx %V", camera.bx);
-	snprint(stats[Scamby], sizeof(stats[Scamby]), "by %V", camera.by);
-	snprint(stats[Scambz], sizeof(stats[Scambz]), "bz %V", camera.bz);
-	snprint(stats[Sfps], sizeof(stats[Sfps]), "FPS %.0f/%.0f/%.0f/%.0f", !camera.stats.max? 0: 1e9/camera.stats.max, !camera.stats.avg? 0: 1e9/camera.stats.avg, !camera.stats.min? 0: 1e9/camera.stats.min, !camera.stats.v? 0: 1e9/camera.stats.v);
-	snprint(stats[Sframes], sizeof(stats[Sframes]), "frame %llud", camera.stats.nframes);
+	snprint(stats[Sfov], sizeof(stats[Sfov]), "FOV %g°", camera->fov/DEG);
+	snprint(stats[Scampos], sizeof(stats[Scampos]), "%V", camera->p);
+	snprint(stats[Scambx], sizeof(stats[Scambx]), "bx %V", camera->bx);
+	snprint(stats[Scamby], sizeof(stats[Scamby]), "by %V", camera->by);
+	snprint(stats[Scambz], sizeof(stats[Scambz]), "bz %V", camera->bz);
+	snprint(stats[Sfps], sizeof(stats[Sfps]), "FPS %.0f/%.0f/%.0f/%.0f", !camera->stats.max? 0: 1e9/camera->stats.max, !camera->stats.avg? 0: 1e9/camera->stats.avg, !camera->stats.min? 0: 1e9/camera->stats.min, !camera->stats.v? 0: 1e9/camera->stats.v);
+	snprint(stats[Sframes], sizeof(stats[Sframes]), "frame %llud", camera->stats.nframes);
 	snprint(stats[Splanet], sizeof(stats[Splanet]), "%s", selplanet == nil? "": selplanet->name);
 	for(i = 0; i < Se; i++)
 		stringbg(screen, addpt(screen->r.min, Pt(10,10 + i*font->height)), display->black, ZP, font, stats[i], display->white, ZP);
@@ -381,8 +382,7 @@
 	int i;
 
 	lockdisplay(display);
-	camera.vp->draw(camera.vp, screenb);
-	draw(screen, rectaddpt(viewr, screen->r.min), display->black, nil, ZP);
+	camera->vp->draw(camera->vp, screenb);
 	draw(screen, rectaddpt(viewr, screen->r.min), screenb, nil, ZP);
 	draw(screen, rectaddpt(cmdbox.r, screen->r.min), display->white, nil, ZP);
 	for(i = 0; i < cmdbox.ncmds; i++){
@@ -404,13 +404,13 @@
 
 	t0 = nsec();
 	for(;;){
-		shootcamera(&camera, &shader);
+		shootcamera(camera, &shader);
 		if(doprof)
 		fprint(2, "R %llud %llud\nE %llud %llud\nT %llud %llud\nr %llud %llud\n\n",
-			camera.times.R[camera.times.cur-1].t0, camera.times.R[camera.times.cur-1].t1,
-			camera.times.E[camera.times.cur-1].t0, camera.times.E[camera.times.cur-1].t1,
-			camera.times.Tn[camera.times.cur-1].t0, camera.times.Tn[camera.times.cur-1].t1,
-			camera.times.Rn[camera.times.cur-1].t0, camera.times.Rn[camera.times.cur-1].t1);
+			camera->times.R[camera->times.cur-1].t0, camera->times.R[camera->times.cur-1].t1,
+			camera->times.E[camera->times.cur-1].t0, camera->times.E[camera->times.cur-1].t1,
+			camera->times.Tn[camera->times.cur-1].t0, camera->times.Tn[camera->times.cur-1].t1,
+			camera->times.Rn[camera->times.cur-1].t0, camera->times.Rn[camera->times.cur-1].t1);
 		Δt = nsec() - t0;
 		if(Δt > HZ2MS(60)*1000000ULL){
 			nbsend(drawc, nil);
@@ -449,7 +449,7 @@
 	idx = menuhit(1, mctl, &menu, _screen);
 	if(idx >= 0){
 		p = &planets[idx];
-		placecamera(&camera, camera.p, p->body->p, cameracfg.up);
+		aimcamera(camera, p->body->p);
 	}
 	unlockdisplay(display);
 	nbsend(drawc, nil);
@@ -513,14 +513,14 @@
 
 	mp = subpt(mctl->xy, screen->r.min);
 	if(ptinrect(mp, viewr)){
-		p0 = viewport2world(&camera, Pt3(mp.x,mp.y,1,1));
-		u = normvec3(subpt3(p0, camera.p));
+		p0 = viewport2world(camera, Pt3(mp.x,mp.y,1,1));
+		u = normvec3(subpt3(p0, camera->p));
 		p = nil;
 		lastz = Inf(1);
 
 		for(i = 0; i < nelem(planets); i++)
 			if(rayXsphere(nil, p0, u, planets[i].body->p, planets[i].scale) > 0){
-				z = vec3len(subpt3(planets[i].body->p, camera.p));
+				z = vec3len(subpt3(planets[i].body->p, camera->p));
 				/* select the closest one */
 				if(z < lastz){
 					lastz = z;
@@ -653,29 +653,29 @@
 	static int okdown;
 
 	if(kdown & 1<<K↑)
-		placecamera(&camera, subpt3(camera.p, mulpt3(camera.bz, speed)), camera.bz, camera.by);
+		movecamera(camera, mulpt3(camera->bz, -speed));
 	if(kdown & 1<<K↓)
-		placecamera(&camera, addpt3(camera.p, mulpt3(camera.bz, speed)), camera.bz, camera.by);
+		movecamera(camera, mulpt3(camera->bz, speed));
 	if(kdown & 1<<K←)
-		placecamera(&camera, subpt3(camera.p, mulpt3(camera.bx, speed)), camera.bz, camera.by);
+		movecamera(camera, mulpt3(camera->bx, -speed));
 	if(kdown & 1<<K→)
-		placecamera(&camera, addpt3(camera.p, mulpt3(camera.bx, speed)), camera.bz, camera.by);
+		movecamera(camera, mulpt3(camera->bx, speed));
 	if(kdown & 1<<Krise)
-		placecamera(&camera, addpt3(camera.p, mulpt3(camera.by, speed)), camera.bz, camera.by);
+		movecamera(camera, mulpt3(camera->by, speed));
 	if(kdown & 1<<Kfall)
-		placecamera(&camera, subpt3(camera.p, mulpt3(camera.by, speed)), camera.bz, camera.by);
+		movecamera(camera, mulpt3(camera->by, -speed));
 	if(kdown & 1<<KR↑)
-		aimcamera(&camera, qrotate(camera.bz, camera.bx, 1*DEG));
+		rotatecamera(camera, camera->bx, 1*DEG);
 	if(kdown & 1<<KR↓)
-		aimcamera(&camera, qrotate(camera.bz, camera.bx, -1*DEG));
+		rotatecamera(camera, camera->bx, -1*DEG);
 	if(kdown & 1<<KR←)
-		aimcamera(&camera, qrotate(camera.bz, camera.by, 1*DEG));
+		rotatecamera(camera, camera->by, 1*DEG);
 	if(kdown & 1<<KR→)
-		aimcamera(&camera, qrotate(camera.bz, camera.by, -1*DEG));
+		rotatecamera(camera, camera->by, -1*DEG);
 	if(kdown & 1<<KR↺)
-		placecamera(&camera, camera.p, camera.bz, qrotate(camera.by, camera.bz, 1*DEG));
+		rotatecamera(camera, camera->bz, 1*DEG);
 	if(kdown & 1<<KR↻)
-		placecamera(&camera, camera.p, camera.bz, qrotate(camera.by, camera.bz, -1*DEG));
+		rotatecamera(camera, camera->bz, -1*DEG);
 	if(kdown & 1<<Kzoomin)
 		zoomin();
 	if(kdown & 1<<Kzoomout)
@@ -723,7 +723,6 @@
 void
 threadmain(int argc, char *argv[])
 {
-	Viewport *v;
 	Renderer *rctl;
 	Channel *keyc;
 	Entity *subject;
@@ -802,11 +801,9 @@
 			cmds[i].r = rectaddpt(cmds[i].r, Pt(cmds[i-1].r.max.x+Cmdmargin,cmds[i-1].r.min.y));
 	}
 	screenb = eallocimage(display, viewr, XRGB32, 0, DNofill);
-	v = mkviewport(screenb->r);
-	placecamera(&camera, cameracfg.p, cameracfg.lookat, cameracfg.up);
-	configcamera(&camera, v, cameracfg.fov, cameracfg.clipn, cameracfg.clipf, cameracfg.ptype);
-	camera.scene = scene;
-	camera.rctl = rctl;
+	camera = Cam(screenb->r, rctl, cameracfg.ptype, cameracfg.fov, cameracfg.clipn, cameracfg.clipf);
+	placecamera(camera, scene, cameracfg.p, cameracfg.lookat, cameracfg.up);
+	camera->cullmode = CullBack;
 	gotoplanet(getplanet("Sol"));
 
 	kctl = emalloc(sizeof *kctl);
--- a/vis.c
+++ b/vis.c
@@ -60,7 +60,7 @@
 Mouse om;
 Quaternion orient = {1,0,0,0};
 
-Camera cams[4], *maincam;
+Camera *cams[4], *maincam;
 Camcfg camcfgs[4] = {
 	2,0,-4,1,
 	0,0,0,1,
@@ -467,9 +467,14 @@
 void
 drawstats(void)
 {
-	int i;
+	int i, camno;
 
-	snprint(stats[Scamno], sizeof(stats[Scamno]), "CAM %lld", maincam-cams+1);
+	camno = -1;
+	for(i = 0; i < nelem(cams); i++)
+		if(maincam == cams[i])
+			camno = i+1;
+
+	snprint(stats[Scamno], sizeof(stats[Scamno]), "CAM %d", camno);
 	snprint(stats[Sfov], sizeof(stats[Sfov]), "FOV %g°", maincam->fov/DEG);
 	snprint(stats[Scampos], sizeof(stats[Scampos]), "%V", maincam->p);
 	snprint(stats[Scambx], sizeof(stats[Scambx]), "bx %V", maincam->bx);
@@ -682,7 +687,7 @@
 	if(idx >= 0){
 		shader = &shadertab[idx];
 		for(idx = 0; idx < nelem(cams); idx++)
-			memset(&cams[idx].stats, 0, sizeof(cams[idx].stats));
+			memset(&cams[idx]->stats, 0, sizeof(cams[idx]->stats));
 	}
 	unlockdisplay(display);
 	nbsend(drawc, nil);
@@ -768,41 +773,41 @@
 	static int okdown;
 
 	if(kdown & 1<<K↑)
-		placecamera(maincam, subpt3(maincam->p, mulpt3(maincam->bz, 0.1)), maincam->bz, maincam->by);
+		movecamera(maincam, mulpt3(maincam->bz, -0.1));
 	if(kdown & 1<<K↓)
-		placecamera(maincam, addpt3(maincam->p, mulpt3(maincam->bz, 0.1)), maincam->bz, maincam->by);
+		movecamera(maincam, mulpt3(maincam->bz, 0.1));
 	if(kdown & 1<<K←)
-		placecamera(maincam, subpt3(maincam->p, mulpt3(maincam->bx, 0.1)), maincam->bz, maincam->by);
+		movecamera(maincam, mulpt3(maincam->bx, -0.1));
 	if(kdown & 1<<K→)
-		placecamera(maincam, addpt3(maincam->p, mulpt3(maincam->bx, 0.1)), maincam->bz, maincam->by);
+		movecamera(maincam, mulpt3(maincam->bx, 0.1));
 	if(kdown & 1<<Krise)
-		placecamera(maincam, addpt3(maincam->p, mulpt3(maincam->by, 0.1)), maincam->bz, maincam->by);
+		movecamera(maincam, mulpt3(maincam->by, 0.1));
 	if(kdown & 1<<Kfall)
-		placecamera(maincam, subpt3(maincam->p, mulpt3(maincam->by, 0.1)), maincam->bz, maincam->by);
+		movecamera(maincam, mulpt3(maincam->by, -0.1));
 	if(kdown & 1<<KR↑)
-		aimcamera(maincam, qrotate(maincam->bz, maincam->bx, 1*DEG));
+		rotatecamera(maincam, maincam->bx, 1*DEG);
 	if(kdown & 1<<KR↓)
-		aimcamera(maincam, qrotate(maincam->bz, maincam->bx, -1*DEG));
+		rotatecamera(maincam, maincam->bx, -1*DEG);
 	if(kdown & 1<<KR←)
-		aimcamera(maincam, qrotate(maincam->bz, maincam->by, 1*DEG));
+		rotatecamera(maincam, maincam->by, 1*DEG);
 	if(kdown & 1<<KR→)
-		aimcamera(maincam, qrotate(maincam->bz, maincam->by, -1*DEG));
+		rotatecamera(maincam, maincam->by, -1*DEG);
 	if(kdown & 1<<KR↺)
-		placecamera(maincam, maincam->p, maincam->bz, qrotate(maincam->by, maincam->bz, 1*DEG));
+		rotatecamera(maincam, maincam->bz, 1*DEG);
 	if(kdown & 1<<KR↻)
-		placecamera(maincam, maincam->p, maincam->bz, qrotate(maincam->by, maincam->bz, -1*DEG));
+		rotatecamera(maincam, maincam->bz, -1*DEG);
 	if(kdown & 1<<Kzoomin)
 		zoomin();
 	if(kdown & 1<<Kzoomout)
 		zoomout();
 	if(kdown & 1<<Kcam0)
-		maincam = &cams[0];
+		maincam = cams[0];
 	if(kdown & 1<<Kcam1)
-		maincam = &cams[1];
+		maincam = cams[1];
 	if(kdown & 1<<Kcam2)
-		maincam = &cams[2];
+		maincam = cams[2];
 	if(kdown & 1<<Kcam3)
-		maincam = &cams[3];
+		maincam = cams[3];
 
 	if((okdown & 1<<Khud) == 0 && (kdown & 1<<Khud) != 0)
 		showhud ^= 1;
@@ -850,7 +855,6 @@
 void
 threadmain(int argc, char *argv[])
 {
-	Viewport *v;
 	Renderer *rctl;
 	Channel *keyc;
 	Entity *subject;
@@ -912,13 +916,11 @@
 
 	screenb = eallocimage(display, rectsubpt(screen->r, screen->r.min), XRGB32, 0, DNofill);
 	for(i = 0; i < nelem(cams); i++){
-		v = mkviewport(screenb->r);
-		placecamera(&cams[i], camcfgs[i].p, camcfgs[i].lookat, camcfgs[i].up);
-		configcamera(&cams[i], v, camcfgs[i].fov, camcfgs[i].clipn, camcfgs[i].clipf, camcfgs[i].ptype);
-		cams[i].scene = scene;
-		cams[i].rctl = rctl;
+		cams[i] = Cam(screenb->r, rctl,
+				camcfgs[i].ptype, camcfgs[i].fov, camcfgs[i].clipn, camcfgs[i].clipf);
+		placecamera(cams[i], scene, camcfgs[i].p, camcfgs[i].lookat, camcfgs[i].up);
 	}
-	maincam = &cams[3];
+	maincam = cams[3];
 	light.p = Pt3(0,100,100,1);
 //	light.dir = Vec3(0,-1,0);
 	light.c = Pt3(1,1,1,1);