shithub: 3dee

Download patch

ref: 380057f08bc96785ec1fc7801e457ff3d08e91c0
parent: 56c0e4ec2df1bce0e7345c90bf42628b3e1f77e7
author: rodri <rgl@antares-labs.eu>
date: Sat Jun 28 16:30:56 EDT 2025

use the new indexed attributes and vertices Model structure

i took the chance to clean, fix and make some improvements
in some of the tools:

	- solar: was leaking an Entity every time the user made
		a selection.
	- vis: didn't initialize the mkblendtest() model correctly.
		also got rid of an ιnception mode warning.

--- a/med.c
+++ b/med.c
@@ -239,86 +239,114 @@
 materializefrustum(void)
 {
 	Primitive l;
+	Vertex v;
 	Point3 p[4];
-	int i;
+	usize vidx0;
+	int i, j;
 
 	p[0] = Pt3(0,0,1,1);
 	p[1] = Pt3(Dx(cam->view->r),0,1,1);
 	p[2] = Pt3(Dx(cam->view->r),Dy(cam->view->r),1,1);
 	p[3] = Pt3(0,Dy(cam->view->r),1,1);
-	memset(&l, 0, sizeof l);
-	l.type = PLine;
-	l.v[0].c = l.v[1].c = Pt3(1,1,1,1);
+	l = mkprim(PLine);
+	v = mkvert();
+	v.c = model->addcolor(model, Pt3(1,1,1,1));
 
-	for(i = 0; i < nelem(p); i++){
-		/* front frame */
-		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->addprim(model, l);
-		qunlock(&scenelk);
+	qlock(&scenelk);
+	vidx0 = model->verts->nitems;
+	/* add front, middle and back frames' vertices */
+	for(i = 0; i < 3; i++)
+		for(j = 0; j < nelem(p); j++){
+			v.p = model->addposition(model, world2model(subject, viewport2world(cam, p[i])));
+			p[i] = addpt3(p[i], Vec3(0,0,0.5));
+			model->addvert(model, v);
+		}
 
-		/* 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))));
-		qlock(&scenelk);
-		model->addprim(model, l);
-		qunlock(&scenelk);
+	/* build the frames */
+	for(i = 0; i < 3; i++)
+		for(j = 1; j <= nelem(p); j++){
+			l.v[0] = vidx0 + 4*i + j-1;
+			l.v[1] = vidx0 + 4*i + j%4;
+			model->addprim(model, l);
+		}
 
-		/* 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))));
-		qlock(&scenelk);
-		model->addprim(model, l);
-		qunlock(&scenelk);
-
-		/* struts */
-		l.v[1].p = world2model(subject, viewport2world(cam, p[i]));
-		qlock(&scenelk);
-		model->addprim(model, l);
-		qunlock(&scenelk);
-	}
+	/* connect the frames with struts */
+	for(i = 0; i < nelem(p); i++)
+		for(j = 1; j < 3; j++){
+			l.v[0] = vidx0 + 4*(j-1) + i;
+			l.v[1] = vidx0 + 4*j + i;
+			model->addprim(model, l);
+		}
+	qunlock(&scenelk);
 }
 
 void
 addcube(void)
 {
-	static Point3 axis[3] = {{0,1,0,0}, {1,0,0,0}, {0,0,1,0}};
-	Primitive t[2];
-	Point3 p, v1, v2;
-	int i, j, k;
+	static int pindices[] = {
+		/* front */
+		0, 1, 4+1, 4+0,
+		/* bottom */
+		0, 3, 2, 1,
+		/* right */
+		1, 2, 4+2, 4+1,
+		/* back */
+		3, 4+3, 4+2, 2,
+		/* top */
+		4+0, 4+1, 4+2, 4+3,
+		/* left */
+		0, 4+0, 4+3, 3,
+	};
+	static Point3 axes[3] = {{0,1,0,0}, {1,0,0,0}, {0,0,1,0}};
+	Primitive t;
+	Vertex v;
+	Point3 p;
+	usize pidx0, nidx0, vidx0;
+	int i;
 
-	memset(t, 0, sizeof t);
-	t[0].type = t[1].type = PTriangle;
+	t = mkprim(PTriangle);
+	v = mkvert();
 
-	/* build the first face/quad, facing the positive z axis */
-	p = Vec3(-0.5,-0.5,0.5);
-	v1 = Vec3(1,0,0);
-	v2 = Vec3(0,1,0);
-	t[0].v[0].p = addpt3(center, p);
-	t[0].v[0].n = t[0].v[1].n = t[0].v[2].n = t[1].v[2].n = Vec3(0,0,1);
-	t[0].v[1].p = addpt3(center, addpt3(p, v1));
-	t[0].v[2].p = addpt3(center, addpt3(p, addpt3(v1, v2)));
-	t[0].v[0].c = t[0].v[1].c = t[0].v[2].c = Pt3(1,1,1,1);
-	t[1].v[0] = t[0].v[0];
-	t[1].v[1] = t[0].v[2];
-	t[1].v[2].p = addpt3(center, addpt3(p, v2));
-	t[1].v[2].c = Pt3(1,1,1,1);
+	v.c = model->addcolor(model, Pt3(1,1,1,1));
 
-	/* make a cube by rotating the reference face */
+	qlock(&scenelk);
+	/* build bottom and top vertex quads around y-axis */
+	pidx0 = model->positions->nitems;
+	p = Pt3(-0.5,-0.5,0.5,1);
+	for(i = 0; i < 8; i++){
+		if(i == 4)
+			p.y++;
+		model->addposition(model, p);
+		p = qrotate(p, Vec3(0,1,0), PI/2);
+	}
+
+	/* build normals */
+	nidx0 = model->normals->nitems;
+	p = Vec3(0,0,1);
 	for(i = 0; i < 6; i++){
-		if(i > 0)
-			for(j = 0; j < 2; j++)
-				for(k = 0; k < 3; k++){
-					t[j].v[k].p = qrotate(t[j].v[k].p, axis[i%3], PI/2);
-					t[j].v[k].n = qrotate(t[j].v[k].n, axis[i%3], PI/2);
-				}
+		model->addnormal(model, p);
+		p = qrotate(p, axes[(i+1)%3], PI/2);
+	}
 
-		qlock(&scenelk);
-		model->addprim(model, t[0]);
-		model->addprim(model, t[1]);
-		qunlock(&scenelk);
+	/* build vertices */
+	vidx0 = model->verts->nitems;
+	for(i = 0; i < nelem(pindices); i++){
+		v.p = pidx0 + pindices[i];
+		v.n = nidx0 + i/4;
+		model->addvert(model, v);
 	}
+
+	/* triangulate vertices */
+	for(i = 0; i < 6; i++){
+		t.v[0] = vidx0 + 4*i + 0;
+		t.v[1] = vidx0 + 4*i + 1;
+		t.v[2] = vidx0 + 4*i + 2;
+		model->addprim(model, t);
+		t.v[1] = vidx0 + 4*i + 2;
+		t.v[2] = vidx0 + 4*i + 3;
+		model->addprim(model, t);
+	}
+	qunlock(&scenelk);
 }
 
 static void
@@ -326,25 +354,33 @@
 {
 	Entity *e;
 	Model *m;
-	Primitive prims[3];
+	Vertex v;
+	Primitive l;
+	int i;
 
 	m = newmodel();
 	e = newentity("basis", m);
 
-	memset(prims, 0, sizeof prims);
-	prims[0].type = prims[1].type = prims[2].type = PLine;
-	prims[0].v[0].p = prims[1].v[0].p = prims[2].v[0].p = center;
-	prims[0].v[0].c = prims[1].v[0].c = prims[2].v[0].c = Pt3(0,0,0,1);
-	prims[0].v[1].p = addpt3(center, e->bx);
-	prims[0].v[1].c = Pt3(1,0,0,1);
-	prims[1].v[1].p = addpt3(center, e->by);
-	prims[1].v[1].c = Pt3(0,1,0,1);
-	prims[2].v[1].p = addpt3(center, e->bz);
-	prims[2].v[1].c = Pt3(0,0,1,1);
+	v = mkvert();
+	v.p = m->addposition(m, center);
+	v.c = m->addcolor(m, Pt3(0,0,0,1));
+	m->addvert(m, v);
+	v.p = m->addposition(m, addpt3(center, e->bx));
+	v.c = m->addcolor(m, Pt3(1,0,0,1));
+	m->addvert(m, v);
+	v.p = m->addposition(m, addpt3(center, e->by));
+	v.c = m->addcolor(m, Pt3(0,1,0,1));
+	m->addvert(m, v);
+	v.p = m->addposition(m, addpt3(center, e->bz));
+	v.c = m->addcolor(m, Pt3(0,0,1,1));
+	m->addvert(m, v);
 
-	m->addprim(m, prims[0]);
-	m->addprim(m, prims[1]);
-	m->addprim(m, prims[2]);
+	l = mkprim(PLine);
+	l.v[0] = 0;
+	for(i = 0; i < 3; i++){
+		l.v[1] = i+1;
+		m->addprim(m, l);
+	}
 
 	s->addent(s, e);
 }
@@ -488,9 +524,13 @@
 	static Quaternion orient = {1,0,0,0};
 	Quaternion Δorient;
 	Point3 p, cp0, cp1, cv;
+	Primitive *prim, *lastprim;
+	Vertex *v;
 	double cr;
-	int i, j;
+	int i, cidx;
 
+	cidx = -1;
+
 	switch(opmode){
 	case OMOrbit:
 		if((om.buttons^mctl->buttons) != 0)
@@ -524,10 +564,16 @@
 		cv = viewport2world(cam, Pt3(mctl->xy.x+10, mctl->xy.y, 1, 1));
 		cr = vec3len(subpt3(cv, cp0)) * cam->clip.f/cam->clip.n;
 
-		for(i = 0; i < model->nprims; i++)
-			for(j = 0; j < model->prims[i].type+1; j++){
-				if(ptincone(model->prims[i].v[j].p, cam->p, cp1, cr))
-					model->prims[i].v[j].c = Pt3(0.5,0.5,0,1);
+		if(cidx < 0)
+			cidx = model->addcolor(model, Pt3(0.5,0.5,0,1));
+
+		lastprim = itemarrayget(model->prims, model->prims->nitems-1);
+		for(prim = model->prims->items; prim <= lastprim; prim++)
+			for(i = 0; i < prim->type+1; i++){
+				v = itemarrayget(model->verts, prim->v[i]);
+				p = *(Point3*)itemarrayget(model->positions, v->p);
+				if(ptincone(p, cam->p, cp1, cr))
+					v->c = cidx;
 			}
 		break;
 	}
@@ -573,7 +619,7 @@
 			usrlog->send(usrlog, "create: %r");
 			break;
 		}
-		writemodel(fd, model, 1);
+		writemodel(fd, model);
 		close(fd);
 		break;
 	case QUIT:
--- a/obj.c
+++ b/obj.c
@@ -8,6 +8,18 @@
 #include "fns.h"
 #include "libobj/obj.h"
 
+static Point3
+VGP3(OBJVertex v)
+{
+	return Pt3(v.x, v.y, v.z, 1);
+}
+
+static Point2
+VGP2(OBJVertex v)
+{
+	return Pt2(v.x, v.y, 1);
+}
+
 /*
  * fan triangulation.
  *
@@ -121,8 +133,9 @@
 static int
 loadobjmodel(Model *m, OBJ *obj)
 {
-	Primitive *p;
-	OBJVertex *pverts, *tverts, *nverts, *v;	/* geometric, texture and normals vertices */
+	Primitive prim;
+	Vertex v, *vp;
+	OBJVertex *pverts, *tverts, *nverts;		/* geometric, texture and normals vertices */
 	OBJElem **trielems, *e, *ne;
 	OBJObject *o;
 	OBJIndexArray *idxtab;
@@ -129,8 +142,8 @@
 	OBJ2MtlMap mtlmap;
 	OBJMaterial *objmtl;
 	Material *mtl;
-	Point3 n;					/* surface normal */
-	int i, idx, nt, maxnt, neednormal, gottaclean;
+	int i, idx, nt, maxnt, hastexcoords, neednormal, gottaclean;
+	int defcolidx, nidx;				/* default color and normal indices */
 
 	pverts = obj->vertdata[OBJVGeometric].verts;
 	tverts = obj->vertdata[OBJVTexture].verts;
@@ -175,6 +188,14 @@
 			addmtlmap(&mtlmap, objmtl, m->nmaterials-1);
 		}
 
+	for(i = 0; i < obj->vertdata[OBJVGeometric].nvert; i++)
+		m->addposition(m, VGP3(pverts[i]));
+	for(i = 0; i < obj->vertdata[OBJVNormal].nvert; i++)
+		m->addnormal(m, VGP3(nverts[i]));
+	for(i = 0; i < obj->vertdata[OBJVTexture].nvert; i++)
+		m->addtexcoord(m, VGP2(tverts[i]));
+	defcolidx = m->addcolor(m, Pt3(1,1,1,1));
+
 	for(i = 0; i < nelem(obj->objtab); i++)
 		for(o = obj->objtab[i]; o != nil; o = o->next)
 			for(e = o->child; e != nil; e = ne){
@@ -182,42 +203,37 @@
 
 				switch(e->type){
 				case OBJEPoint:
-					m->prims = erealloc(m->prims, ++m->nprims*sizeof(*m->prims));
-					p = &m->prims[m->nprims-1];
-					memset(p, 0, sizeof *p);
-					p->type = PPoint;
-					p->mtl = getmtlmap(&mtlmap, e->mtl);
+					prim = mkprim(PPoint);
+					prim.mtl = getmtlmap(&mtlmap, e->mtl);
 
+					v = mkvert();
 					idxtab = &e->indextab[OBJVGeometric];
-					v = &pverts[idxtab->indices[0]];
-					p->v[0].p = Pt3(v->x, v->y, v->z, v->w);
-					p->v[0].c = Pt3(1,1,1,1);
+					v.p = idxtab->indices[0];
+					v.c = defcolidx;
 
 					idxtab = &e->indextab[OBJVTexture];
-					if(idxtab->nindex == 1){
-						v = &tverts[idxtab->indices[0]];
-						p->v[0].uv = Pt2(v->u, v->v, 1);
-					}
+					if(idxtab->nindex == 1)
+						v.uv = idxtab->indices[0];
+
+					prim.v[0] = m->addvert(m, v);
+					m->addprim(m, prim);
 					break;
 				case OBJELine:
-					m->prims = erealloc(m->prims, ++m->nprims*sizeof(*m->prims));
-					p = &m->prims[m->nprims-1];
-					memset(p, 0, sizeof *p);
-					p->type = PLine;
-					p->mtl = getmtlmap(&mtlmap, e->mtl);
+					prim = mkprim(PLine);
+					prim.mtl = getmtlmap(&mtlmap, e->mtl);
 
 					for(idx = 0; idx < 2; idx++){
+						v = mkvert();
 						idxtab = &e->indextab[OBJVGeometric];
-						v = &pverts[idxtab->indices[idx]];
-						p->v[idx].p = Pt3(v->x, v->y, v->z, v->w);
-						p->v[idx].c = Pt3(1,1,1,1);
+						v.p = idxtab->indices[idx];
+						v.c = defcolidx;
 
 						idxtab = &e->indextab[OBJVTexture];
-						if(idxtab->nindex == 2){
-							v = &tverts[idxtab->indices[idx]];
-							p->v[idx].uv = Pt2(v->u, v->v, 1);
-						}
+						if(idxtab->nindex == 2)
+							v.uv = idxtab->indices[idx];
+						prim.v[idx] = m->addvert(m, v);
 					}
+					m->addprim(m, prim);
 					break;
 				case OBJEFace:
 					idxtab = &e->indextab[OBJVGeometric];
@@ -238,54 +254,78 @@
 
 					while(nt-- > 0){
 						e = trielems[nt];
+						hastexcoords = 0;
 						neednormal = 0;
 
-						m->prims = erealloc(m->prims, ++m->nprims*sizeof(*m->prims));
-						p = &m->prims[m->nprims-1];
-						memset(p, 0, sizeof *p);
-						p->type = PTriangle;
-						p->mtl = getmtlmap(&mtlmap, e->mtl);
+						prim = mkprim(PTriangle);
+						prim.mtl = getmtlmap(&mtlmap, e->mtl);
 
 						for(idx = 0; idx < 3; idx++){
+							v = mkvert();
 							idxtab = &e->indextab[OBJVGeometric];
-							v = &pverts[idxtab->indices[idx]];
-							p->v[idx].p = Pt3(v->x, v->y, v->z, v->w);
-							p->v[idx].c = Pt3(1,1,1,1);
+							v.p = idxtab->indices[idx];
+							v.c = defcolidx;
 
 							idxtab = &e->indextab[OBJVNormal];
-							if(idxtab->nindex == 3){
-								v = &nverts[idxtab->indices[idx]];
-								p->v[idx].n = normvec3(Vec3(v->i, v->j, v->k));
-							}else
+							if(idxtab->nindex == 3)
+								v.n = idxtab->indices[idx];
+							else
 								neednormal = 1;
 
 							idxtab = &e->indextab[OBJVTexture];
 							if(idxtab->nindex == 3){
-								v = &tverts[idxtab->indices[idx]];
-								p->v[idx].uv = Pt2(v->u, v->v, 1);
+								hastexcoords = 1;
+								v.uv = idxtab->indices[idx];
 							}
+							prim.v[idx] = m->addvert(m, v);
 						}
-						if(p->v[0].uv.w != 0){
-							Point3 e0, e1;
-							Point2 Δuv0, Δuv1;
+
+						if(hastexcoords){
+							Point3 *p[3], e0, e1, tangent;
+							Point2 *uv[3], Δuv0, Δuv1;
 							double det;
 
-							e0 = subpt3(p->v[1].p, p->v[0].p);
-							e1 = subpt3(p->v[2].p, p->v[0].p);
-							Δuv0 = subpt2(p->v[1].uv, p->v[0].uv);
-							Δuv1 = subpt2(p->v[2].uv, p->v[0].uv);
+							for(idx = 0; idx < 3; idx++){
+								vp = itemarrayget(m->verts, prim.v[idx]);
+								p[idx] = itemarrayget(m->positions, vp->p);
+								uv[idx] = itemarrayget(m->texcoords, vp->uv);
+							}
 
+							e0 = subpt3(*p[1], *p[0]);
+							e1 = subpt3(*p[2], *p[0]);
+							Δuv0 = subpt2(*uv[1], *uv[0]);
+							Δuv1 = subpt2(*uv[2], *uv[0]);
+
 							det = Δuv0.x * Δuv1.y - Δuv1.x * Δuv0.y;
 							det = det == 0? 0: 1.0/det;
-							p->tangent.x = det*(Δuv1.y * e0.x - Δuv0.y * e1.x);
-							p->tangent.y = det*(Δuv1.y * e0.y - Δuv0.y * e1.y);
-							p->tangent.z = det*(Δuv1.y * e0.z - Δuv0.y * e1.z);
-							p->tangent = normvec3(p->tangent);
+
+							tangent.x = det*(Δuv1.y * e0.x - Δuv0.y * e1.x);
+							tangent.y = det*(Δuv1.y * e0.y - Δuv0.y * e1.y);
+							tangent.z = det*(Δuv1.y * e0.z - Δuv0.y * e1.z);
+							tangent.w = 0;
+							tangent = normvec3(tangent);
+
+							prim.tangent = m->addtangent(m, tangent);
 						}
+
 						if(neednormal){
-							n = normvec3(crossvec3(subpt3(p->v[1].p, p->v[0].p), subpt3(p->v[2].p, p->v[0].p)));
-							p->v[0].n = p->v[1].n = p->v[2].n = n;
+							Point3 *p[3], n;
+
+							for(idx = 0; idx < 3; idx++){
+								vp = itemarrayget(m->verts, prim.v[idx]);
+								p[idx] = itemarrayget(m->positions, vp->p);
+							}
+
+							n = normvec3(crossvec3(subpt3(*p[1], *p[0]), subpt3(*p[2], *p[0])));
+							nidx = m->addnormal(m, n);
+							for(idx = 0; idx < 3; idx++){
+								vp = itemarrayget(m->verts, prim.v[idx]);
+								vp->n = nidx;
+							}
 						}
+
+						m->addprim(m, prim);
+
 						if(gottaclean){
 							free(e->indextab[OBJVGeometric].indices);
 							free(e->indextab[OBJVNormal].indices);
@@ -300,7 +340,7 @@
 
 	free(trielems);
 	clrmtlmap(&mtlmap);
-	return m->nprims;
+	return m->prims->nitems;
 }
 
 static Model *
@@ -333,7 +373,7 @@
 
 	dedup = 1;
 	ARGBEGIN{
-	case 'd': dedup--; break;
+	case 'd': dedup--; break;	/* TODO waiting for a Model compaction routine */
 	default: usage();
 	}ARGEND;
 	if(argc > 2)
@@ -347,10 +387,10 @@
 		sysfatal("readobjmodel: %r");
 
 	if(dstdir == nil){
-		if(writemodel(1, m, dedup) == 0)
+		if(writemodel(1, m) == 0)
 			sysfatal("writemodel: %r");
 	}else{
-		if(exportmodel(dstdir, m, dedup) < 0)
+		if(exportmodel(dstdir, m) < 0)
 			sysfatal("exportmodel: %r");
 	}
 
--- a/plot3.c
+++ b/plot3.c
@@ -174,7 +174,8 @@
 	Model *mdl;
 	Entity *ent;
 	Primitive line, mark;
-	Point3 stepv;
+	Vertex v;
+	Point3 stepv, p0, p1, mp0;
 	int i;
 
 	mdl = newmodel();
@@ -181,41 +182,59 @@
 	ent = newentity("axis scales", mdl);
 	theplot.scn->addent(theplot.scn, ent);
 
-	line.type = PLine;
-	line.v[0].c = line.v[1].c = Pt3(0.4,0.4,0.4,1);
+	line = mkprim(PLine);
+	v = mkvert();
+	v.c = mdl->addcolor(mdl, Pt3(0.4,0.4,0.4,1));
 	mark = line;
 
 	/* x scale */
-	line.v[0].p = Pt3(smallestbbox(x), smallestbbox(y), smallestbbox(z), 1);
-	line.v[1].p = Pt3(biggestbbox(x), smallestbbox(y), smallestbbox(z), 1);
+	v.p = mdl->addposition(mdl, Pt3(smallestbbox(x), smallestbbox(y), smallestbbox(z), 1));
+	line.v[0] = mdl->addvert(mdl, v);
+	v.p = mdl->addposition(mdl, Pt3(biggestbbox(x), smallestbbox(y), smallestbbox(z), 1));
+	line.v[1] = mdl->addvert(mdl, v);
 	mdl->addprim(mdl, line);
-	stepv = subpt3(line.v[1].p, line.v[0].p);
+	p0 = *(Point3*)itemarrayget(mdl->positions, v.p - 1);
+	p1 = *(Point3*)itemarrayget(mdl->positions, v.p);
+	stepv = subpt3(p1, p0);
 	stepv = divpt3(stepv, 10);
 	for(i = 1; i <= 10; i++){
-		mark.v[0].p = addpt3(line.v[0].p, mulpt3(stepv, i));
-		mark.v[1].p = addpt3(mark.v[0].p, qrotate(stepv, Vec3(0,1,0), 90*DEG));
+		mp0 = addpt3(p0, mulpt3(stepv, i));
+		v.p = mdl->addposition(mdl, mp0);
+		mark.v[0] = mdl->addvert(mdl, v);
+		v.p = mdl->addposition(mdl, addpt3(mp0, qrotate(stepv, Vec3(0,1,0), 90*DEG)));
+		mark.v[1] = mdl->addvert(mdl, v);
 		mdl->addprim(mdl, mark);
 	}
 
 	/* y scale */
-	line.v[1].p = Pt3(smallestbbox(x), biggestbbox(y), smallestbbox(z), 1);
+	v.p = mdl->addposition(mdl, Pt3(smallestbbox(x), biggestbbox(y), smallestbbox(z), 1));
+	line.v[1] = mdl->addvert(mdl, v);
 	mdl->addprim(mdl, line);
-	stepv = subpt3(line.v[1].p, line.v[0].p);
+	p1 = *(Point3*)itemarrayget(mdl->positions, v.p);
+	stepv = subpt3(p1, p0);
 	stepv = divpt3(stepv, 10);
 	for(i = 1; i <= 10; i++){
-		mark.v[0].p = addpt3(line.v[0].p, mulpt3(stepv, i));
-		mark.v[1].p = addpt3(mark.v[0].p, qrotate(stepv, normvec3(Vec3(-1,0,1)), 90*DEG));
+		mp0 = addpt3(p0, mulpt3(stepv, i));
+		v.p = mdl->addposition(mdl, mp0);
+		mark.v[0] = mdl->addvert(mdl, v);
+		v.p = mdl->addposition(mdl, addpt3(mp0, qrotate(stepv, normvec3(Vec3(-1,0,1)), 90*DEG)));
+		mark.v[1] = mdl->addvert(mdl, v);
 		mdl->addprim(mdl, mark);
 	}
 
 	/* z scale */
-	line.v[1].p = Pt3(smallestbbox(x), smallestbbox(y), biggestbbox(z), 1);
+	v.p = mdl->addposition(mdl, Pt3(smallestbbox(x), smallestbbox(y), biggestbbox(z), 1));
+	line.v[1] = mdl->addvert(mdl, v);
 	mdl->addprim(mdl, line);
-	stepv = subpt3(line.v[1].p, line.v[0].p);
+	p1 = *(Point3*)itemarrayget(mdl->positions, v.p);
+	stepv = subpt3(p1, p0);
 	stepv = divpt3(stepv, 10);
 	for(i = 1; i <= 10; i++){
-		mark.v[0].p = addpt3(line.v[0].p, mulpt3(stepv, i));
-		mark.v[1].p = addpt3(mark.v[0].p, qrotate(stepv, Vec3(0,1,0), -90*DEG));
+		mp0 = addpt3(p0, mulpt3(stepv, i));
+		v.p = mdl->addposition(mdl, mp0);
+		mark.v[0] = mdl->addvert(mdl, v);
+		v.p = mdl->addposition(mdl, addpt3(mp0, qrotate(stepv, Vec3(0,1,0), -90*DEG)));
+		mark.v[1] = mdl->addvert(mdl, v);
 		mdl->addprim(mdl, mark);
 	}
 }
@@ -227,6 +246,8 @@
 	Entity *ent;
 	Scene *scn;
 	Primitive prim;
+	Vertex v;
+	static Color prevcol;
 	Plotpt *p;
 
 	mdl = newmodel();
@@ -235,11 +256,15 @@
 	scn->addent(scn, ent);
 	theplot.scn = scn;
 
-	memset(&prim, 0, sizeof prim);
-	prim.type = PPoint;
+	prim = mkprim(PPoint);
+	v = mkvert();
 	for(p = theplot.pts; p < theplot.pts + theplot.npts; p++){
-		prim.v[0].p = p->p;
-		prim.v[0].c = p->c->c;
+		v.p = mdl->addposition(mdl, p->p);
+		if(!eqpt3(prevcol, p->c->c)){
+			v.c = mdl->addcolor(mdl, p->c->c);
+			prevcol = p->c->c;
+		}
+		prim.v[0] = mdl->addvert(mdl, v);
 		mdl->addprim(mdl, prim);
 	}
 	free(theplot.pts);
--- a/procgen.c
+++ b/procgen.c
@@ -147,6 +147,7 @@
 {
 	Memimage *out;
 	Point dim;
+	Vertex v;
 	int skip;
 	double time;
 
@@ -181,13 +182,17 @@
 	cam = Cam(out->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].type = quad[1].type = PTriangle;
-	quad[0].v[0].p = vcs2clip(cam, viewport2vcs(cam, Pt3(out->r.min.x, out->r.max.y, 1, 1)));
-	quad[0].v[1].p = vcs2clip(cam, viewport2vcs(cam, Pt3(out->r.max.x, out->r.min.y, 1, 1)));
-	quad[0].v[2].p = vcs2clip(cam, viewport2vcs(cam, Pt3(out->r.min.x, out->r.min.y, 1, 1)));
-	quad[1].v[0].p = quad[0].v[0].p;
-	quad[1].v[1].p = vcs2clip(cam, viewport2vcs(cam, Pt3(out->r.max.x, out->r.max.y, 1, 1)));
-	quad[1].v[2].p = quad[0].v[1].p;
+	quad[0] = quad[1] = mkprim(PTriangle);
+	v.p = mdl->addposition(mdl, vcs2clip(cam, viewport2vcs(cam, Pt3(out->r.min.x, out->r.max.y, 1, 1))));
+	quad[0].v[0] = mdl->addvert(mdl, v);
+	v.p = mdl->addposition(mdl, vcs2clip(cam, viewport2vcs(cam, Pt3(out->r.max.x, out->r.min.y, 1, 1))));
+	quad[0].v[1] = mdl->addvert(mdl, v);
+	v.p = mdl->addposition(mdl, vcs2clip(cam, viewport2vcs(cam, Pt3(out->r.min.x, out->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(out->r.max.x, out->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);
--- a/solar.c
+++ b/solar.c
@@ -283,7 +283,9 @@
 	Entity *e, *esel;
 	Model *msel;
 	Primitive l;
-	int i, j;
+	Vertex v;
+	Point3 *pt, *lastpt;
+	int i;
 
 	if(p == oldp)
 		return;
@@ -291,8 +293,10 @@
 	qlock(&scenelk);
 	oldp = selplanet = p;
 	esel = scene->getent(scene, "selection");
-	if(esel != nil)
+	if(esel != nil){
 		scene->delent(scene, esel);
+		delentity(esel);
+	}
 	qunlock(&scenelk);
 
 	lockdisplay(display);
@@ -314,43 +318,47 @@
 	unlockdisplay(display);
 
 	memset(&aabb, 0, sizeof aabb);
-	for(i = 0; i < e->mdl->nprims; i++)
-		for(j = 0; j < e->mdl->prims[i].type+1; j++){
-			aabb.min = minpt3(aabb.min, e->mdl->prims[i].v[j].p);
-			aabb.max = maxpt3(aabb.max, e->mdl->prims[i].v[j].p);
-		}
+	pt = e->mdl->positions->items;
+	for(lastpt = pt + e->mdl->positions->nitems; pt < lastpt; pt++){
+		aabb.min = minpt3(aabb.min, *pt);
+		aabb.max = maxpt3(aabb.max, *pt);
+	}
 	aabb.min = mulpt3(aabb.min, p->scale*0.8);
 	aabb.max = mulpt3(aabb.max, p->scale*0.8);
 	aabb.min.w = aabb.max.w = 1;
 
-	memset(&l, 0, sizeof l);
-	l.type = PLine;
-	l.v[0].c = l.v[1].c = Pt3(0.2666, 0.5333, 0.2666, 1);
+	l = mkprim(PLine);
+	v = mkvert();
+	v.c = msel->addcolor(msel, Pt3(0.2666, 0.5333, 0.2666, 1));
+
+	for(i = 0; i < 4; i++){
+		v.p = msel->addposition(msel, aabb.min);
+		msel->addvert(msel, v);
+		aabb.min = qrotate(aabb.min, Vec3(0,1,0), PI/2);
+	}
+	aabb.max = qrotate(aabb.max, Vec3(0,1,0), PI);
+	for(i = 0; i < 4; i++){
+		v.p = msel->addposition(msel, aabb.max);
+		msel->addvert(msel, v);
+		aabb.max = qrotate(aabb.max, Vec3(0,1,0), PI/2);
+	}
+
 	/* bottom */
-	l.v[0].p = aabb.min;
-	l.v[1].p = qrotate(aabb.min, Vec3(0,1,0), PI/2);
-	msel->addprim(msel, l);
-	for(i = 0; i < 3; i++){
-		l.v[0].p = l.v[1].p;
-		l.v[1].p = qrotate(l.v[1].p, Vec3(0,1,0), PI/2);
+	for(i = 1; i <= 4; i++){
+		l.v[0] = i-1;
+		l.v[1] = i%4;
 		msel->addprim(msel, l);
 	}
 	/* top */
-	l.v[0].p = aabb.max;
-	l.v[1].p = qrotate(aabb.max, Vec3(0,1,0), PI/2);
-	msel->addprim(msel, l);
-	for(i = 0; i < 3; i++){
-		l.v[0].p = l.v[1].p;
-		l.v[1].p = qrotate(l.v[1].p, Vec3(0,1,0), PI/2);
+	for(i = 4+1; i <= 4+4; i++){
+		l.v[0] = i-1;
+		l.v[1] = i%4 + 4;
 		msel->addprim(msel, l);
 	}
 	/* struts */
-	l.v[0].p = aabb.min;
-	l.v[1].p = qrotate(aabb.max, Vec3(0,1,0), PI);
-	msel->addprim(msel, l);
-	for(i = 0; i < 3; i++){
-		l.v[0].p = qrotate(l.v[0].p, Vec3(0,1,0), PI/2);
-		l.v[1].p = qrotate(l.v[1].p, Vec3(0,1,0), PI/2);
+	for(i = 0; i < 4; i++){
+		l.v[0] = i;
+		l.v[1] = i+4;
 		msel->addprim(msel, l);
 	}
 
@@ -886,6 +894,7 @@
 	Channel *keyc;
 	Entity *subject;
 	Model *model;
+	Point3 *p, *lastp;
 	Point lblsiz;
 	int fd, i, j;
 
@@ -913,11 +922,11 @@
 	 * normalize the vertices so that we can scale
 	 * each planet based on its radius
 	 */
-	for(i = 0; i < model->nprims; i++)
-		for(j = 0; j < model->prims[i].type+1; j++){
-			model->prims[i].v[j].p = normvec3(model->prims[i].v[j].p);
-			model->prims[i].v[j].p.w = 1;
-		}
+	p = model->positions->items;
+	for(lastp = p + model->positions->nitems; p < lastp; p++){
+		*p = normvec3(*p);
+		p->w = 1;
+	}
 	scene = newscene(nil);
 	for(i = 0; i < nelem(planets); i++){
 		subject = newentity(planets[i].name, model);
--- a/stl.c
+++ b/stl.c
@@ -11,24 +11,26 @@
 static int
 loadstlmodel(Model *m, Stl *stl)
 {
-	Primitive p;
+	Primitive prim;
+	Vertex v;
 	Stltri **tri;
+	int i;
 
-	memset(&p, 0, sizeof p);
-	p.type = PTriangle;
-	p.v[0].c = p.v[1].c = p.v[2].c = Pt3(1,1,1,1);
+	prim = mkprim(PTriangle);
+	v = mkvert();
+	v.c = m->addcolor(m, Pt3(1,1,1,1));
 
 	for(tri = stl->tris; tri < stl->tris+stl->ntris; tri++){
-		p.v[0].p = Pt3((*tri)->v[0][0], (*tri)->v[0][1], (*tri)->v[0][2], 1);
-		p.v[1].p = Pt3((*tri)->v[1][0], (*tri)->v[1][1], (*tri)->v[1][2], 1);
-		p.v[2].p = Pt3((*tri)->v[2][0], (*tri)->v[2][1], (*tri)->v[2][2], 1);
-		p.v[0].n = Vec3((*tri)->n[0], (*tri)->n[1], (*tri)->n[2]);
-		p.v[1].n = p.v[2].n = p.v[0].n;
+		v.n = m->addnormal(m, Vec3((*tri)->n[0], (*tri)->n[1], (*tri)->n[2]));
+		for(i = 0; i < 3; i++){
+			v.p = m->addposition(m, Pt3((*tri)->v[i][0], (*tri)->v[i][1], (*tri)->v[i][2], 1));
+			prim.v[i] = m->addvert(m, v);
+		}
 
-		m->addprim(m, p);
+		m->addprim(m, prim);
 	}
 
-	return m->nprims;
+	return m->prims->nitems;
 }
 
 static Model *
@@ -62,7 +64,7 @@
 	dedup = 1;
 	infile = "/fd/0";
 	ARGBEGIN{
-	case 'd': dedup--; break;
+	case 'd': dedup--; break;	/* TODO waiting for a Model compaction routine */
 	default: usage();
 	}ARGEND;
 	if(argc == 1)
@@ -78,7 +80,7 @@
 	if(m == nil)
 		sysfatal("readstlmodel: %r");
 
-	if(writemodel(1, m, dedup) == 0)
+	if(writemodel(1, m) == 0)
 		sysfatal("writemodel: %r");
 
 	exits(nil);
--- a/toobj.c
+++ b/toobj.c
@@ -10,26 +10,26 @@
 #include "libobj/obj.h"
 
 static OBJVertex
-GP2V(Point2 p)
+GP2V(Point2 *p)
 {
 	OBJVertex v;
 
-	v.x = p.x;
-	v.y = p.y;
-	v.z = p.w;
+	v.x = p->x;
+	v.y = p->y;
+	v.z = p->w;
 	v.w = 0;
 	return v;
 }
 
 static OBJVertex
-GP3V(Point3 p)
+GP3V(Point3 *p)
 {
 	OBJVertex v;
 
-	v.x = p.x;
-	v.y = p.y;
-	v.z = p.z;
-	v.w = p.w;
+	v.x = p->x;
+	v.y = p->y;
+	v.z = p->z;
+	v.w = p->w;
 	return v;
 }
 
@@ -36,13 +36,13 @@
 static int
 loadobjmodel(OBJ *obj, Model *m)
 {
-	Primitive *prim;
-	OBJVertex v;
+	Primitive *prim, *lastprim;
+	Vertex *v;
 	OBJElem *e;
 	OBJObject *o;
 	OBJMaterial *objmtl;
 	Material *mtl;
-	int i, idx;
+	int i;
 
 	if(m->nmaterials > 0)
 		obj->materials = objallocmtl("main.mtl");
@@ -85,9 +85,17 @@
 		objaddmtl(obj->materials, objmtl);
 	}
 
+	for(i = 0; i < m->positions->nitems; i++)
+		objaddvertex(obj, GP3V(itemarrayget(m->positions, i)), OBJVGeometric);
+	for(i = 0; i < m->normals->nitems; i++)
+		objaddvertex(obj, GP3V(itemarrayget(m->normals, i)), OBJVNormal);
+	for(i = 0; i < m->texcoords->nitems; i++)
+		objaddvertex(obj, GP3V(itemarrayget(m->texcoords, i)), OBJVTexture);
+
 	o = objallocobject("default");
 	objpushobject(obj, o);
-	for(prim = m->prims; prim < m->prims + m->nprims; prim++){
+	lastprim = itemarrayget(m->prims, m->prims->nitems-1);
+	for(prim = m->prims->items; prim <= lastprim; prim++){
 		/*
 		 * XXX A Model doesn't have the indexed attribute
 		 * structure an OBJ has, so this conversion is very
@@ -108,21 +116,12 @@
 		}
 
 		for(i = 0; i < prim->type+1; i++){
-			v = GP3V(prim->v[i].p);
-			idx = objaddvertex(obj, v, OBJVGeometric);
-			objaddelemidx(e, OBJVGeometric, idx);
-
-			if(memcmp(&prim->v[i].n, &ZP3, sizeof(Point3)) != 0){
-				v = GP3V(prim->v[i].n);
-				idx = objaddvertex(obj, v, OBJVNormal);
-				objaddelemidx(e, OBJVNormal, idx);
-			}
-
-			if(memcmp(&prim->v[i].uv, &ZP2, sizeof(Point2)) != 0){
-				v = GP2V(prim->v[i].uv);
-				idx = objaddvertex(obj, v, OBJVTexture);
-				objaddelemidx(e, OBJVTexture, idx);
-			}
+			v = itemarrayget(m->verts, prim->v[i]);
+			objaddelemidx(e, OBJVGeometric, v->p);
+			if(v->n != NaI)
+				objaddelemidx(e, OBJVNormal, v->n);
+			if(v->uv != NaI)
+				objaddelemidx(e, OBJVTexture, v->uv);
 		}
 
 		if(prim->mtl != nil)
@@ -130,7 +129,7 @@
 		objaddelem(o, e);
 	}
 
-	return m->nprims;
+	return m->prims->nitems;
 }
 
 static void
--- a/tostl.c
+++ b/tostl.c
@@ -11,7 +11,9 @@
 static int
 loadstlmodel(Stl *stl, Model *m)
 {
-	Primitive *p;
+	Primitive *prim, *lastprim;
+	Vertex *v;
+	Point3 *p;
 	Stltri **tri, *t;
 	int i;
 
@@ -18,7 +20,7 @@
 	snprint((char*)stl->hdr, sizeof(stl->hdr), "Exported with libstl from ⑨");
 
 	/* XXX we assume all prims are triangles */
-	stl->ntris = m->nprims;
+	stl->ntris = m->prims->nitems;
 	stl->tris = emalloc(stl->ntris*sizeof(Stltri*));
 
 	/* since we don't use attributes we can allocate tris in bulk */
@@ -28,20 +30,26 @@
 		*tri = &t[tri - stl->tris];
 
 	tri = stl->tris;
-	for(p = m->prims; p < m->prims+m->nprims; p++){
-		if(p->type != PTriangle){
+	lastprim = itemarrayget(m->prims, m->prims->nitems-1);
+	for(prim = m->prims->items; prim <= lastprim; prim++){
+		if(prim->type != PTriangle){
 			stl->ntris--;
 			continue;
 		}
 
-		(*tri)->n[0] = p->v[0].n.x;
-		(*tri)->n[1] = p->v[0].n.y;
-		(*tri)->n[2] = p->v[0].n.z;
+		v = itemarrayget(m->verts, prim->v[0]);
+		p = itemarrayget(m->normals, v->n);
 
+		(*tri)->n[0] = p->x;
+		(*tri)->n[1] = p->y;
+		(*tri)->n[2] = p->z;
+
 		for(i = 0; i < 3; i++){
-			(*tri)->v[i][0] = p->v[i].p.x;
-			(*tri)->v[i][1] = p->v[i].p.y;
-			(*tri)->v[i][2] = p->v[i].p.z;
+			v = itemarrayget(m->verts, prim->v[i]);
+			p = itemarrayget(m->positions, v->p);
+			(*tri)->v[i][0] = p->x;
+			(*tri)->v[i][1] = p->y;
+			(*tri)->v[i][2] = p->z;
 		}
 		tri++;
 	}
--- a/vis.c
+++ b/vis.c
@@ -194,20 +194,25 @@
 {
 	static int inited;
 	Model *m;
-	Primitive *prim;
+	Primitive *prim, *lastprim;
 	Vertex *v;
+	Point3 *p;
+	int i;
 
 	m = e->mdl;
-	for(prim = m->prims; prim < m->prims + m->nprims; prim++)
-	for(v = prim->v; v < prim->v + prim->type+1; v++){
+	lastprim = itemarrayget(m->prims, m->prims->nitems-1);
+	for(prim = m->prims->items; prim <= lastprim; prim++)
+	for(i = 0; i < prim->type+1; i++){
+		v = itemarrayget(m->verts, prim->v[i]);
+		p = itemarrayget(m->positions, v->p);
 		if(!inited){
-			scenebbox.min = scenebbox.max = addpt3(e->p, v->p);
+			scenebbox.min = scenebbox.max = addpt3(e->p, *p);
 			inited++;
 			continue;
 		}
 
-		scenebbox.min = minpt3(scenebbox.min, addpt3(e->p, v->p));
-		scenebbox.max = maxpt3(scenebbox.max, addpt3(e->p, v->p));
+		scenebbox.min = minpt3(scenebbox.min, addpt3(e->p, *p));
+		scenebbox.max = maxpt3(scenebbox.max, addpt3(e->p, *p));
 	}
 	scenebbox.c = divpt3(addpt3(scenebbox.max, scenebbox.min), 2);
 	scenebbox.r = max(vec3len(scenebbox.min), vec3len(scenebbox.max));
@@ -259,7 +264,7 @@
 renderproc(void *)
 {
 	Material *mtl;
-	Primitive *prim;
+	Primitive *prim, *lastprim;
 	uvlong t0, Δt;
 	int fd;
 	double time;
@@ -267,6 +272,7 @@
 	threadsetname("renderproc");
 
 	fd = -1;
+	mtl = nil;
 	if(inception){
 		fd = open("/dev/screen", OREAD);
 		if(fd < 0)
@@ -278,7 +284,8 @@
 		model->addmaterial(model, *mtl);
 		free(mtl);
 		mtl = &model->materials[model->nmaterials-1];
-		for(prim = model->prims; prim < model->prims+model->nprims; prim++)
+		lastprim = itemarrayget(model->prims, model->prims->nitems-1);
+		for(prim = model->prims->items; prim <= lastprim; prim++)
 			prim->mtl = mtl;
 	}
 
@@ -663,45 +670,40 @@
 mkblendtestscene(void)
 {
 	static Color cols[] = {{1,0,0,0.5}, {0,1,0,0.5}, {0,0,1,0.5}};
+	static Point3 Yaxis = {0,1,0,0};
 	Entity *ent;
-	Model *mdl;
 	Primitive t[2];
-	Point3 p, v1, v2;
-	int i, j, k;
+	Vertex v;
+	Point3 p, n;
+	int i, j;
 
-	memset(t, 0, sizeof t);
-	t[0].type = t[1].type = PTriangle;
+	t[0] = t[1] = mkprim(PTriangle);
+	v = mkvert();
 
-	/* build the first face/quad, facing the positive z axis */
-	p = Vec3(-0.5,-0.5,0);
-	v1 = Vec3(1,0,0);
-	v2 = Vec3(0,1,0);
-	t[0].v[0].p = addpt3(center, p);
-	t[0].v[1].p = addpt3(center, addpt3(p, v1));
-	t[0].v[2].p = addpt3(center, addpt3(p, addpt3(v1, v2)));
-	t[0].v[0].n = t[0].v[1].n = t[0].v[2].n = Vec3(0,0,1);
-	t[1].v[0] = t[0].v[0];
-	t[1].v[1] = t[0].v[2];
-	t[1].v[2].p = addpt3(center, addpt3(p, v2));
-	t[1].v[2].n = Vec3(0,0,1);
+	model = newmodel();
+	ent = newentity(nil, model);
+	scene->addent(scene, ent);
 
+	p = Pt3(-0.5,-0.5,0,1);
+	n = Vec3(0,0,1);
 	for(i = 0; i < nelem(cols); i++){
-		for(j = 0; j < 2; j++)
-			for(k = 0; k < 3; k++){
-				if(i != 0){
-					t[j].v[k].p = qrotate(t[j].v[k].p, Vec3(0,1,0), PI/nelem(cols));
-					t[j].v[k].n = qrotate(t[j].v[k].n, Vec3(0,1,0), PI/nelem(cols));
-				}
-				t[j].v[k].c = cols[i];
-			}
+		v.c = model->addcolor(model, cols[i]);
+		v.n = model->addnormal(model, n);
+		for(j = 0; j < 4; j++){
+			v.p = model->addposition(model, p);
+			model->addvert(model, v);
+			p = qrotate(p, n, PI/2);
+		}
 
-		mdl = newmodel();
-		mdl->addprim(mdl, t[0]);
-		mdl->addprim(mdl, t[1]);
-		ent = newentity(nil, mdl);
-		scene->addent(scene, ent);
-		updatebbox(ent);
+		t[0].v[0] = v.p-3 + 0; t[0].v[1] = v.p-3 + 1; t[0].v[2] = v.p-3 + 2;
+		t[1].v[0] = v.p-3 + 0; t[1].v[1] = v.p-3 + 2; t[1].v[2] = v.p-3 + 3;
+		model->addprim(model, t[0]);
+		model->addprim(model, t[1]);
+
+		p = qrotate(p, Yaxis, PI/nelem(cols));
+		n = qrotate(n, Yaxis, PI/nelem(cols));
 	}
+	updatebbox(ent);
 }
 
 void
@@ -749,7 +751,7 @@
 	Channel *keyc;
 	Entity *subject;
 	Material *tmpmtl;
-	Primitive *prim;
+	Primitive *prim, *lastprim;
 	char *texpath, *mdlpath, *s;
 	int i, fd, fbw, fbh, scale;
 
@@ -799,7 +801,7 @@
 		scene->addent(scene, subject);
 		updatebbox(subject);
 
-fprint(2, "%s: %lud prims\n", mdlpath, model->nprims);
+fprint(2, "%s: %llud prims\n", mdlpath, model->prims->nitems);
 
 		if(argc == 0 && texpath != nil){
 			fd = open(texpath, OREAD);
@@ -813,7 +815,8 @@
 			model->addmaterial(model, *tmpmtl);
 			free(tmpmtl);
 			tmpmtl = &model->materials[model->nmaterials-1];
-			for(prim = model->prims; prim < model->prims+model->nprims; prim++)
+			lastprim = itemarrayget(model->prims, model->prims->nitems-1);
+			for(prim = model->prims->items; prim <= lastprim; prim++)
 				prim->mtl = tmpmtl;
 		}
 	}
--