shithub: libgraphics

Download patch

ref: 81d07752e717d2dece4e5f9dded3eb379cbe27f2
parent: 8eb98b6c111e148bdf2285016068cbea6110dfbc
author: rodri <rgl@antares-labs.eu>
date: Sat Jun 28 16:15:25 EDT 2025

implement indexed vertex attributes and vertices for Model

now it matches the file format—model(6)—and simplifies
import and export. it also simplifies the conversion
to/from OBJ, and should be the same with other indexed
attributes formats.

--- a/TODO.md
+++ b/TODO.md
@@ -8,19 +8,10 @@
 - [ ] Avoid writing the same texture multiple times under different names in exportmodel(2)
 - [ ] Add wireframe rendering by a reasonable interface and method
 - [ ] Find out why the A-buffer takes so much memory (enough to run OOM on a 32GB term!)
-- [ ] Review the idea of using indexed properties for the vertices
-	- This has become a more pressing matter with 3dee/toobj (the
-	  Model to OBJ conversion tool) because its quite inefficient
-	  and generates very large .obj files with unnecessary
-	  redundancy: e.g., with a box, where instead of storing one
-	  normal per every face, we store the same normal on the six
-	  vertices (three per triangle) that make up the face.
+- [+] Review the idea of using indexed properties for the vertices
 - [+] Create an internal Vertex type
-	- I want to get rid of the Vertex.(Vertexattrs|mtl|tangent)
-	  properties, as they are only used during rasterization.
-	  This will affect the Primitive as well, so it will probably
-	  require an internal Primitive type.
 - [ ] See if prims can be ordered front-to-back before rasterizing (quick Z-buffer discard)
 	- It might be better to add it as a Camera.rendopts flag, for
 	  transparency rendering without the A-buffer.
 - [ ] Implement decals
+- [ ] Implement Model deduplication/compaction routine
--- a/camera.c
+++ b/camera.c
@@ -40,36 +40,59 @@
 static Model *
 mkskyboxmodel(void)
 {
-	static Point3 axes[3] = {{0,1,0,0}, {1,0,0,0}, {0,0,1,0}};
-	static Point3 center = {0,0,0,1};
+	static int indices[] = {
+		/* front */
+		0, 1, 4+1,	0, 4+1, 4+0,
+		/* right */
+		1, 2, 4+2,	1, 4+2, 4+1,
+		/* bottom */
+		0, 3, 2,	0, 2, 1,
+		/* back */
+		3, 4+3, 4+2,	3, 4+2, 2,
+		/* left */
+		0, 4+0, 4+3,	0, 4+3, 3,
+		/* top */
+		4+0, 4+1, 4+2,	4+0, 4+2, 4+3,
+	};
 	Model *m;
-	Primitive t[2];
-	Point3 p, v1, v2;
-	int i, j, k;
+	Primitive t;
+	Vertex v;
+	Point3 p;
+	int i, k;
+//	int f, j;
 
 	m = newmodel();
-	memset(t, 0, sizeof t);
-	t[0].type = t[1].type = PTriangle;
+	t = mkprim(PTriangle);
+	v = mkvert();
 
-	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[1].p = addpt3(center, addpt3(p, v1));
-	t[0].v[2].p = addpt3(center, addpt3(p, addpt3(v1, v2)));
-	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));
+	/* build bottom and top quads around y-axis */
+	p = Pt3(-0.5,-0.5,0.5,1);
+	for(i = 0; i < 8; i++){
+		if(i == 4)
+			p.y++;
+		v.p = m->addposition(m, p);
+		m->addvert(m, v);
+		p = qrotate(p, Vec3(0,1,0), PI/2);
+	}
 
-	for(i = 0; i < 6; 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, axes[i%3], PI/2);
+	for(i = 0; i < nelem(indices); i++){
+//		f = i/6 % 6;
+//		j = i/3 % 2;
+		k = i % 3;
+		t.v[k] = indices[i];
+		if(k == 3-1){
+			m->addprim(m, t);
 
-		m->addprim(m, t[0]);
-		m->addprim(m, t[1]);
+//			Point3 p0, p1;
+//			for(k = 0; k < 3; k++){
+//				p0 = *(Point3*)itemarrayget(m->positions, t.v[k]);
+//				p1 = *(Point3*)itemarrayget(m->positions, t.v[(k+1)%3]);
+//				if(eqpt3(p0, p1))
+//					fprint(2, "face %d disfigured (tri #%d, verts %d %V and %d %V)\n", f, j, k, p0, k+1, p1);
+//			}
+		}
 	}
+
 	return m;
 }
 
--- a/graphics.h
+++ b/graphics.h
@@ -50,8 +50,12 @@
 	/* vertex attribute types */
 	VAPoint = 0,
 	VANumber,
+
+	/* itemarray */
+	NaI	= ~0ULL,	/* not an index */
 };
 
+typedef struct ItemArray ItemArray;
 typedef struct Color Color;
 typedef struct Texture Texture;
 typedef struct Cubemap Cubemap;
@@ -80,6 +84,13 @@
 typedef struct Viewport Viewport;
 typedef struct Camera Camera;
 
+struct ItemArray
+{
+	void *items;
+	usize nitems;
+	usize itemsize;
+};
+
 struct Color
 {
 	double r, g, b, a;
@@ -138,10 +149,10 @@
 
 struct Vertex
 {
-	Point3 p;		/* position */
-	Point3 n;		/* surface normal */
-	Color c;		/* shading color */
-	Point2 uv;		/* texture coordinate */
+	usize p;		/* position */
+	usize n;		/* surface normal */
+	usize uv;		/* texture coordinate */
+	usize c;		/* shading color */
 };
 
 /*
@@ -151,7 +162,10 @@
  */
 struct BVertex
 {
-	Vertex;
+	Point3 p;		/* position */
+	Point3 n;		/* surface normal */
+	Point2 uv;		/* texture coordinate */
+	Color c;		/* shading color */
 	Material *mtl;
 	Point3 tangent;
 	Vertexattrs;		/* attributes (varyings) */
@@ -186,19 +200,30 @@
 struct Primitive
 {
 	int type;
-	Vertex v[3];
+	usize v[3];
+	usize tangent;		/* used for normal mapping */
 	Material *mtl;
-	Point3 tangent;		/* used for normal mapping */
 };
 
 struct Model
 {
-	Primitive *prims;
-	ulong nprims;
+	ItemArray *positions;
+	ItemArray *normals;
+	ItemArray *texcoords;
+	ItemArray *colors;
+	ItemArray *tangents;
+	ItemArray *verts;
+	ItemArray *prims;
 	Material *materials;
 	ulong nmaterials;
 
-	int (*addprim)(Model*, Primitive);
+	usize (*addposition)(Model*, Point3);
+	usize (*addnormal)(Model*, Point3);
+	usize (*addtexcoord)(Model*, Point2);
+	usize (*addcolor)(Model*, Color);
+	usize (*addtangent)(Model*, Point3);
+	usize (*addvert)(Model*, Vertex);
+	usize (*addprim)(Model*, Primitive);
 	int (*addmaterial)(Model*, Material);
 	Material *(*getmaterial)(Model*, char*);
 };
@@ -428,10 +453,12 @@
 
 /* marshal */
 Model *readmodel(int);
-usize writemodel(int, Model*, int);
-int exportmodel(char*, Model*, int);
+usize writemodel(int, Model*);
+int exportmodel(char*, Model*);
 
 /* scene */
+Vertex mkvert(void);
+Primitive mkprim(int);
 Material *newmaterial(char*);
 void delmaterial(Material*);
 Model *newmodel(void);
@@ -475,6 +502,14 @@
 int eqpt3(Point3, Point3);
 Memimage *rgba(ulong);
 Memimage *dupmemimage(Memimage*);
+
+/* itemarray */
+ItemArray *mkitemarray(usize);
+usize itemarrayadd(ItemArray*, void*, int);
+void *itemarrayget(ItemArray*, usize);
+usize copyitemarray(ItemArray*, ItemArray*);
+ItemArray *dupitemarray(ItemArray*);
+void rmitemarray(ItemArray*);
 
 /* color */
 ulong col2ul(Color);
--- a/internal.h
+++ b/internal.h
@@ -24,8 +24,8 @@
 {
 	int type;
 	BVertex v[3];
-	Material *mtl;
 	Point3 tangent;		/* used for normal mapping */
+	Material *mtl;
 };
 
 struct Polygon
--- /dev/null
+++ b/itemarray.c
@@ -1,0 +1,86 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <geometry.h>
+#include "graphics.h"
+#include "internal.h"
+
+ItemArray *
+mkitemarray(usize is)
+{
+	ItemArray *a;
+
+	a = _emalloc(sizeof *a);
+	memset(a, 0, sizeof *a);
+	a->itemsize = is;
+	return a;
+}
+
+usize
+itemarrayadd(ItemArray *a, void *i, int dedup)
+{
+	char *p;
+	usize idx;
+
+	if(dedup){
+		p = a->items;
+		for(idx = 0; idx < a->nitems; idx++)
+			if(memcmp(i, &p[idx*a->itemsize], a->itemsize) == 0)
+				return idx;
+	}
+
+	idx = a->nitems;
+	a->items = _erealloc(a->items, ++a->nitems * a->itemsize);
+	p = a->items;
+	p += idx*a->itemsize;
+	memmove(p, i, a->itemsize);
+	return idx;
+}
+
+void *
+itemarrayget(ItemArray *a, usize idx)
+{
+	char *p;
+
+	if(idx >= a->nitems)
+		return nil;
+
+	p = a->items;
+	p += idx*a->itemsize;
+	return p;
+}
+
+usize
+copyitemarray(ItemArray *d, ItemArray *s)
+{
+	usize len;
+
+	assert(d->itemsize == s->itemsize);
+	len = s->nitems * s->itemsize;
+
+	free(d->items);
+	d->items = _emalloc(len);
+	d->nitems = s->nitems;
+	memmove(d->items, s->items, len);
+	return d->nitems;
+}
+
+ItemArray *
+dupitemarray(ItemArray *a)
+{
+	ItemArray *na;
+
+	na = mkitemarray(a->itemsize);
+	copyitemarray(na, a);
+	return na;
+}
+
+void
+rmitemarray(ItemArray *a)
+{
+	free(a->items);
+	free(a);
+}
--- a/marshal.c
+++ b/marshal.c
@@ -9,7 +9,6 @@
 #include "internal.h"
 
 enum {
-	NaI = ~0ULL,	/* not an index */
 	MTLHTSIZ = 17,
 };
 
@@ -20,32 +19,9 @@
 	usize line;
 };
 
-typedef struct IArray IArray;
-typedef struct Wirevert Wirevert;
-typedef struct Wireprim Wireprim;
 typedef struct Mtlentry Mtlentry;
 typedef struct Mtltab Mtltab;
 
-struct IArray
-{
-	void *items;
-	usize nitems;
-	usize itemsize;
-};
-
-struct Wirevert
-{
-	usize p, n, t, c;
-};
-
-struct Wireprim
-{
-	int nv;
-	usize v[3];
-	usize T;
-	char *mtlname;
-};
-
 struct Mtlentry
 {
 	Material;
@@ -85,58 +61,6 @@
 	werrstr("%s", buf);
 }
 
-static IArray *
-mkitemarray(usize is)
-{
-	IArray *a;
-
-	a = _emalloc(sizeof *a);
-	memset(a, 0, sizeof *a);
-	a->itemsize = is;
-	return a;
-}
-
-static usize
-itemarrayadd(IArray *a, void *i, int dedup)
-{
-	char *p;
-	usize idx;
-
-	if(dedup){
-		p = a->items;
-		for(idx = 0; idx < a->nitems; idx++)
-			if(memcmp(i, &p[idx*a->itemsize], a->itemsize) == 0)
-				return idx;
-	}
-
-	idx = a->nitems;
-	a->items = _erealloc(a->items, ++a->nitems * a->itemsize);
-	p = a->items;
-	p += idx*a->itemsize;
-	memmove(p, i, a->itemsize);
-	return idx;
-}
-
-static void *
-itemarrayget(IArray *a, usize idx)
-{
-	char *p;
-
-	if(idx >= a->nitems)
-		return nil;
-
-	p = a->items;
-	p += idx*a->itemsize;
-	return p;
-}
-
-static void
-rmitemarray(IArray *a)
-{
-	free(a->items);
-	free(a);
-}
-
 static uint
 hash(char *s)
 {
@@ -239,7 +163,7 @@
 readmodel(int fd)
 {
 	Curline curline;
-	IArray *pa, *na, *ta, *ca, *Ta, *va, *Pa;
+	ItemArray *pa, *na, *ta, *ca, *Ta, *va, *Pa;
 	Mtltab *mtltab;
 	Mtlentry *me;
 	Point3 p, n, T;
@@ -246,7 +170,7 @@
 	Point2 t;
 	Color c;
 	Vertex v;
-	Primitive P;
+	Primitive P, *prim;
 	Material mtl;
 	Model *m;
 	Memimage *mi;
@@ -254,7 +178,6 @@
 	void *vp;
 	char *line, *f[10], *s, assets[200], buf[256];
 	usize idx, i;
-	ulong primidx;
 	int nf, nv, inamaterial, texfd;
 
 	n.w = T.w = 0;
@@ -278,9 +201,9 @@
 	if(fd2path(fd, curline.file, sizeof curline.file) != 0)
 		sysfatal("fd2path: %r");
 	if((s = strrchr(curline.file, '/')) != nil){
-		*s = 0;
+		*s++ = 0;
 		snprint(assets, sizeof assets, "%s", curline.file);
-		memmove(curline.file, s+1, strlen(s+1) + 1);
+		memmove(curline.file, s, strlen(s) + 1);
 	}else{
 		assets[0] = '.';
 		assets[1] = 0;
@@ -477,7 +400,7 @@
 				error(&curline, "no position at idx %llud", idx);
 				goto getout;
 			}
-			v.p = *(Point3*)vp;
+			v.p = idx;
 
 			if(strcmp(f[2], "-") != 0){
 				idx = strtoul(f[2], nil, 10);
@@ -486,7 +409,7 @@
 					error(&curline, "no normal at idx %llud", idx);
 					goto getout;
 				}
-				v.n = *(Point3*)vp;
+				v.n = idx;
 			}
 
 			if(strcmp(f[3], "-") != 0){
@@ -496,7 +419,7 @@
 					error(&curline, "no texture at idx %llud", idx);
 					goto getout;
 				}
-				v.uv = *(Point2*)vp;
+				v.uv = idx;
 			}
 
 			if(strcmp(f[4], "-") != 0){
@@ -506,7 +429,7 @@
 					error(&curline, "no color at idx %llud", idx);
 					goto getout;
 				}
-				v.c = *(Color*)vp;
+				v.c = idx;
 			}
 
 			itemarrayadd(va, &v, 0);
@@ -529,7 +452,7 @@
 					error(&curline, "no vertex at idx %llud", idx);
 					goto getout;
 				}
-				P.v[0] = *(Vertex*)vp;
+				P.v[0] = idx;
 
 				/* ignore 4th field (nf == 4) */
 
@@ -548,7 +471,7 @@
 				vp = itemarrayget(va, idx);
 				if(vp == nil)
 					goto novertex;
-				P.v[0] = *(Vertex*)vp;
+				P.v[0] = idx;
 
 				if(nf < 4){
 notenough:
@@ -559,7 +482,7 @@
 				vp = itemarrayget(va, idx);
 				if(vp == nil)
 					goto novertex;
-				P.v[1] = *(Vertex*)vp;
+				P.v[1] = idx;
 
 				/* ignore 5th field (nf == 5) */
 
@@ -578,7 +501,7 @@
 				vp = itemarrayget(va, idx);
 				if(vp == nil)
 					goto novertex;
-				P.v[0] = *(Vertex*)vp;
+				P.v[0] = idx;
 
 				if(nf < 4)
 					goto notenough;
@@ -586,7 +509,7 @@
 				vp = itemarrayget(va, idx);
 				if(vp == nil)
 					goto novertex;
-				P.v[1] = *(Vertex*)vp;
+				P.v[1] = idx;
 
 				if(nf < 5)
 					goto notenough;
@@ -594,7 +517,7 @@
 				vp = itemarrayget(va, idx);
 				if(vp == nil)
 					goto novertex;
-				P.v[2] = *(Vertex*)vp;
+				P.v[2] = idx;
 
 				if(nf < 6){
 					error(&curline, "missing triangle tangent field");
@@ -607,7 +530,7 @@
 						error(&curline, "no tangent at idx %llud", idx);
 						goto getout;
 					}
-					P.tangent = *(Point3*)vp;
+					P.tangent = idx;
 				}
 
 				if(nf == 7){
@@ -646,11 +569,18 @@
 
 	m = newmodel();
 	mtltabloadmodel(m, mtltab);
+	copyitemarray(m->positions, pa);
+	copyitemarray(m->normals, na);
+	copyitemarray(m->texcoords, ta);
+	copyitemarray(m->colors, ca);
+	copyitemarray(m->tangents, Ta);
+	copyitemarray(m->verts, va);
+	copyitemarray(m->prims, Pa);
 	for(i = 0; i < Pa->nitems; i++){
-		primidx = m->addprim(m, *(Primitive*)itemarrayget(Pa, i));
-		if(m->prims[primidx].mtl != nil){
-			me = mtltabget(mtltab, m->prims[primidx].mtl->name);
-			m->prims[primidx].mtl = &m->materials[me->idx];
+		prim = itemarrayget(m->prims, i);
+		if(prim->mtl != nil){
+			me = mtltabget(mtltab, prim->mtl->name);
+			prim->mtl = &m->materials[me->idx];
 		}
 	}
 
@@ -756,13 +686,13 @@
 }
 
 static int
-Bprintv(Biobuf *b, Wirevert *v)
+Bprintv(Biobuf *b, Vertex *v)
 {
 	int n;
 
 	n = Bprint(b, "v %llud", v->p);
 	n += Bprintidx(b, v->n);
-	n += Bprintidx(b, v->t);
+	n += Bprintidx(b, v->uv);
 	n += Bprintidx(b, v->c);
 	n += Bprint(b, "\n");
 	return n;
@@ -769,17 +699,17 @@
 }
 
 static int
-BprintP(Biobuf *b, Wireprim *p)
+BprintP(Biobuf *b, Primitive *p)
 {
 	char *s;
 	int n, i;
 
-	n = Bprint(b, "P %d", p->nv);
-	for(i = 0; i < p->nv; i++)
+	n = Bprint(b, "P %d", p->type+1);
+	for(i = 0; i < p->type+1; i++)
 		n += Bprintidx(b, p->v[i]);
-	n += Bprintidx(b, p->T);
-	if(p->mtlname != nil){
-		s = quotestrdup(p->mtlname);
+	n += Bprintidx(b, p->tangent);
+	if(p->mtl != nil){
+		s = quotestrdup(p->mtl->name);
 		if(s == nil)
 			sysfatal("quotestrdup: %r");
 		n += Bprint(b, " %s", s);
@@ -844,79 +774,34 @@
 }
 
 usize
-writemodel(int fd, Model *m, int dedup)
+writemodel(int fd, Model *m)
 {
-	IArray *pa, *na, *ta, *ca, *Ta, *va, *Pa;
-	Wirevert v;
-	Wireprim P;
-	Primitive *p, *ep;
 	Biobuf *out;
-	usize n;
-	int i;
+	usize n, i;
 
 	out = Bfdopen(fd, OWRITE);
 	if(out == nil)
 		sysfatal("Bfdopen: %r");
 
-	pa = mkitemarray(sizeof(Point3));
-	na = mkitemarray(sizeof(Point3));
-	ta = mkitemarray(sizeof(Point2));
-	ca = mkitemarray(sizeof(Color));
-	Ta = mkitemarray(sizeof(Point3));
-	va = mkitemarray(sizeof(Wirevert));
-	Pa = mkitemarray(sizeof(Wireprim));
-
 	n = 0;
-	p = m->prims;
-	ep = p + m->nprims;
-
-	while(p < ep){
-		memset(&P, 0, sizeof P);
-
-		P.nv = p->type+1;
-		for(i = 0; i < P.nv; i++){
-			v.p = itemarrayadd(pa, &p->v[i].p, dedup);
-			v.n = eqpt3(p->v[i].n, ZP3)?
-				NaI: itemarrayadd(na, &p->v[i].n, dedup);
-			v.t = p->v[i].uv.w != 1?
-				NaI: itemarrayadd(ta, &p->v[i].uv, dedup);
-			v.c = p->v[i].c.a == 0?
-				NaI: itemarrayadd(ca, &p->v[i].c, dedup);
-			P.v[i] = itemarrayadd(va, &v, dedup);
-		}
-		P.T = eqpt3(p->tangent, ZP3)?
-			NaI: itemarrayadd(Ta, &p->tangent, dedup);
-		P.mtlname = p->mtl != nil? p->mtl->name: nil;
-
-		itemarrayadd(Pa, &P, 0);
-		p++;
-	}
-
 	for(i = 0; i < m->nmaterials; i++)
 		n += Bprintmtl(out, &m->materials[i]);
 
-	for(i = 0; i < pa->nitems; i++)
-		n += Bprintp(out, itemarrayget(pa, i));
-	for(i = 0; i < na->nitems; i++)
-		n += Bprintn(out, itemarrayget(na, i));
-	for(i = 0; i < ta->nitems; i++)
-		n += Bprintt(out, itemarrayget(ta, i));
-	for(i = 0; i < ca->nitems; i++)
-		n += Bprintc(out, itemarrayget(ca, i));
-	for(i = 0; i < Ta->nitems; i++)
-		n += BprintT(out, itemarrayget(Ta, i));
-	for(i = 0; i < va->nitems; i++)
-		n += Bprintv(out, itemarrayget(va, i));
-	for(i = 0; i < Pa->nitems; i++)
-		n += BprintP(out, itemarrayget(Pa, i));
+	for(i = 0; i < m->positions->nitems; i++)
+		n += Bprintp(out, itemarrayget(m->positions, i));
+	for(i = 0; i < m->normals->nitems; i++)
+		n += Bprintn(out, itemarrayget(m->normals, i));
+	for(i = 0; i < m->texcoords->nitems; i++)
+		n += Bprintt(out, itemarrayget(m->texcoords, i));
+	for(i = 0; i < m->colors->nitems; i++)
+		n += Bprintc(out, itemarrayget(m->colors, i));
+	for(i = 0; i < m->tangents->nitems; i++)
+		n += BprintT(out, itemarrayget(m->tangents, i));
+	for(i = 0; i < m->verts->nitems; i++)
+		n += Bprintv(out, itemarrayget(m->verts, i));
+	for(i = 0; i < m->prims->nitems; i++)
+		n += BprintP(out, itemarrayget(m->prims, i));
 
-	rmitemarray(pa);
-	rmitemarray(na);
-	rmitemarray(ta);
-	rmitemarray(ca);
-	rmitemarray(Ta);
-	rmitemarray(va);
-	rmitemarray(Pa);
 	Bterm(out);
 	return n;
 }
@@ -941,7 +826,7 @@
 }
 
 int
-exportmodel(char *path, Model *m, int dedup)
+exportmodel(char *path, Model *m)
 {
 	static char Esmallbuf[] = "buf too small to hold path";
 	Material *mtl;
@@ -1004,7 +889,7 @@
 		werrstr("create: %r");
 		return -1;
 	}
-	if(writemodel(fd, m, dedup) == 0){
+	if(writemodel(fd, m) == 0){
 		close(fd);
 		werrstr("writemodel: %r");
 		return -1;
--- a/mkfile
+++ b/mkfile
@@ -18,6 +18,7 @@
 	util.$O\
 	nanosec.$O\
 	marshal.$O\
+	itemarray.$O\
 	`{fn : { test -f $1-$objtype.s\
 			&& echo $1-$objtype.$O\
 			|| echo $1.$O };\
--- a/render.c
+++ b/render.c
@@ -549,6 +549,30 @@
 		wr[nwr-1].max.y = fbr->max.y;
 }
 
+static BPrimitive *
+assembleprim(BPrimitive *d, Primitive *s, Model *m)
+{
+	Vertex *v;
+	void *vp;
+	int i;
+
+	d->type = s->type;
+	for(i = 0; i < s->type+1; i++){
+		v = itemarrayget(m->verts, s->v[i]);
+		d->v[i].p = *(Point3*)itemarrayget(m->positions, v->p);
+		vp = itemarrayget(m->normals, v->n);
+		d->v[i].n = vp != nil? *(Point3*)vp: ZP3;
+		vp = itemarrayget(m->colors, v->c);
+		d->v[i].c = vp != nil? *(Color*)vp: ZP3;
+		vp = itemarrayget(m->texcoords, v->uv);
+		d->v[i].uv = vp != nil? *(Point2*)vp: ZP2;
+	}
+	vp = itemarrayget(m->tangents, s->tangent);
+	d->tangent = vp != nil? *(Point3*)vp: ZP3;
+	d->mtl = s->mtl;
+	return d;
+}
+
 static void
 tiler(void *arg)
 {
@@ -608,12 +632,8 @@
 		for(ep = params->eb; ep != params->ee; ep++){
 			np = 1;	/* start with one. after clipping it might change */
 
-			prim.type = ep->type;
-			for(i = 0; i < prim.type+1; i++)
-				prim.v[i].Vertex = ep->v[i];
-			prim.mtl = ep->mtl;
-			prim.tangent = ep->tangent;
-			p = &prim;
+			p = assembleprim(&prim, ep, params->entity->mdl);
+
 			switch(p->type){
 			case PPoint:
 				p->v[0].mtl = p->mtl;
@@ -831,8 +851,8 @@
 			}
 		}
 
-		eb = params->entity->mdl->prims;
-		nprims = params->entity->mdl->nprims;
+		eb = params->entity->mdl->prims->items;
+		nprims = params->entity->mdl->prims->nitems;
 		ee = eb + nprims;
 
 		if(nprims <= nproc){
--- a/scene.c
+++ b/scene.c
@@ -7,6 +7,24 @@
 #include "graphics.h"
 #include "internal.h"
 
+Vertex
+mkvert(void)
+{
+	return (Vertex){NaI, NaI, NaI, NaI};
+}
+
+Primitive
+mkprim(int type)
+{
+	Primitive prim;
+
+	prim.type = type;
+	prim.v[0] = prim.v[1] = prim.v[2] = NaI;
+	prim.tangent = NaI;
+	prim.mtl = nil;
+	return prim;
+}
+
 Material *
 newmaterial(char *name)
 {
@@ -35,14 +53,48 @@
 	free(mtl->name);
 }
 
-static int
-model_addprim(Model *m, Primitive p)
+static usize
+model_addposition(Model *m, Point3 p)
 {
-	m->prims = _erealloc(m->prims, ++m->nprims*sizeof(*m->prims));
-	m->prims[m->nprims-1] = p;
-	return m->nprims-1;
+	return itemarrayadd(m->positions, &p, 0);
 }
 
+static usize
+model_addnormal(Model *m, Point3 n)
+{
+	return itemarrayadd(m->normals, &n, 0);
+}
+
+static usize
+model_addtexcoord(Model *m, Point2 t)
+{
+	return itemarrayadd(m->texcoords, &t, 0);
+}
+
+static usize
+model_addcolor(Model *m, Color c)
+{
+	return itemarrayadd(m->colors, &c, 0);
+}
+
+static usize
+model_addtangent(Model *m, Point3 T)
+{
+	return itemarrayadd(m->tangents, &T, 0);
+}
+
+static usize
+model_addvert(Model *m, Vertex v)
+{
+	return itemarrayadd(m->verts, &v, 0);
+}
+
+static usize
+model_addprim(Model *m, Primitive P)
+{
+	return itemarrayadd(m->prims, &P, 0);
+}
+
 static int
 model_addmaterial(Model *m, Material mtl)
 {
@@ -69,6 +121,19 @@
 
 	m = _emalloc(sizeof *m);
 	memset(m, 0, sizeof *m);
+	m->positions = mkitemarray(sizeof(Point3));
+	m->normals = mkitemarray(sizeof(Point3));
+	m->texcoords = mkitemarray(sizeof(Point2));
+	m->colors = mkitemarray(sizeof(Color));
+	m->tangents = mkitemarray(sizeof(Point3));
+	m->verts = mkitemarray(sizeof(Vertex));
+	m->prims = mkitemarray(sizeof(Primitive));
+	m->addposition = model_addposition;
+	m->addnormal = model_addnormal;
+	m->addtexcoord = model_addtexcoord;
+	m->addcolor = model_addcolor;
+	m->addtangent = model_addtangent;
+	m->addvert = model_addvert;
 	m->addprim = model_addprim;
 	m->addmaterial = model_addmaterial;
 	m->getmaterial = model_getmaterial;
@@ -79,6 +144,7 @@
 dupmodel(Model *m)
 {
 	Model *nm;
+	Primitive *prim, *nprim;
 	int i;
 
 	if(m == nil)
@@ -98,13 +164,18 @@
 				sysfatal("strdup: %r");
 		}
 	}
-	if(m->nprims > 0){
-		nm->nprims = m->nprims;
-		nm->prims = _emalloc(nm->nprims*sizeof(*nm->prims));
-		for(i = 0; i < m->nprims; i++){
-			nm->prims[i] = m->prims[i];
-			if(nm->nmaterials > 0 && m->prims[i].mtl != nil)
-				nm->prims[i].mtl = &nm->materials[m->prims[i].mtl - m->materials];
+	nm->positions = dupitemarray(m->positions);
+	nm->normals = dupitemarray(m->normals);
+	nm->texcoords = dupitemarray(m->texcoords);
+	nm->colors = dupitemarray(m->colors);
+	nm->tangents = dupitemarray(m->tangents);
+	nm->verts = dupitemarray(m->verts);
+	nm->prims = dupitemarray(m->prims);
+	for(i = 0; i < m->prims->nitems && nm->nmaterials > 0; i++){
+		prim = itemarrayget(m->prims, i);
+		if(prim->mtl != nil){
+			nprim = itemarrayget(nm->prims, i);
+			nprim->mtl = &nm->materials[prim->mtl - m->materials];
 		}
 	}
 	return nm;
@@ -119,7 +190,13 @@
 	while(m->nmaterials--)
 		delmaterial(&m->materials[m->nmaterials]);
 	free(m->materials);
-	free(m->prims);
+	rmitemarray(m->positions);
+	rmitemarray(m->normals);
+	rmitemarray(m->texcoords);
+	rmitemarray(m->colors);
+	rmitemarray(m->tangents);
+	rmitemarray(m->verts);
+	rmitemarray(m->prims);
 	free(m);
 }
 
--