shithub: libgraphics

Download patch

ref: 03149e5536a831e2ea7a20175f04938343529c99
parent: 05ae0d42a944f6c7d940a5e58eb90b619dadbfdb
author: rodri <rgl@antares-labs.eu>
date: Wed Aug 7 07:47:31 EDT 2024

offset fb during drawing based on viewport config. move OBJ-related procedures to its own unit.

--- a/fb.c
+++ b/fb.c
@@ -93,18 +93,18 @@
 //}
 
 static void
-framebufctl_draw(Framebufctl *ctl, Image *dst)
+framebufctl_draw(Framebufctl *ctl, Image *dst, Point off)
 {
 	Framebuf *fb;
 
 	qlock(ctl);
 	fb = ctl->getfb(ctl);
-	loadimage(dst, rectaddpt(fb->r, dst->r.min), (uchar*)fb->cb, Dx(fb->r)*Dy(fb->r)*4);
+	loadimage(dst, rectaddpt(fb->r, addpt(dst->r.min, off)), (uchar*)fb->cb, Dx(fb->r)*Dy(fb->r)*4);
 	qunlock(ctl);
 }
 
 static void
-framebufctl_upscaledraw(Framebufctl *ctl, Image *dst, Point scale)
+framebufctl_upscaledraw(Framebufctl *ctl, Image *dst, Point off, Point scale)
 {
 	void (*filter)(ulong*, Framebuf*, Point);
 	Framebuf *fb;
@@ -130,8 +130,8 @@
 		break;
 	}
 
-	for(sp.y = fb->r.min.y, dp.y = dst->r.min.y; sp.y < fb->r.max.y; sp.y++, dp.y += scale.y)
-	for(sp.x = fb->r.min.x, dp.x = dst->r.min.x; sp.x < fb->r.max.x; sp.x++, dp.x += scale.x){
+	for(sp.y = fb->r.min.y, dp.y = dst->r.min.y+off.y; sp.y < fb->r.max.y; sp.y++, dp.y += scale.y)
+	for(sp.x = fb->r.min.x, dp.x = dst->r.min.x+off.x; sp.x < fb->r.max.x; sp.x++, dp.x += scale.x){
 		if(filter != nil)
 			filter(blk, fb, sp);
 		else
@@ -143,18 +143,18 @@
 }
 
 static void
-framebufctl_memdraw(Framebufctl *ctl, Memimage *dst)
+framebufctl_memdraw(Framebufctl *ctl, Memimage *dst, Point off)
 {
 	Framebuf *fb;
 
 	qlock(ctl);
 	fb = ctl->getfb(ctl);
-	loadmemimage(dst, rectaddpt(fb->r, dst->r.min), (uchar*)fb->cb, Dx(fb->r)*Dy(fb->r)*4);
+	loadmemimage(dst, rectaddpt(fb->r, addpt(dst->r.min, off)), (uchar*)fb->cb, Dx(fb->r)*Dy(fb->r)*4);
 	qunlock(ctl);
 }
 
 static void
-framebufctl_upscalememdraw(Framebufctl *ctl, Memimage *dst, Point scale)
+framebufctl_upscalememdraw(Framebufctl *ctl, Memimage *dst, Point off, Point scale)
 {
 	void (*filter)(ulong*, Framebuf*, Point);
 	Framebuf *fb;
@@ -180,8 +180,8 @@
 		break;
 	}
 
-	for(sp.y = fb->r.min.y, dp.y = dst->r.min.y; sp.y < fb->r.max.y; sp.y++, dp.y += scale.y)
-	for(sp.x = fb->r.min.x, dp.x = dst->r.min.x; sp.x < fb->r.max.x; sp.x++, dp.x += scale.x){
+	for(sp.y = fb->r.min.y, dp.y = dst->r.min.y+off.y; sp.y < fb->r.max.y; sp.y++, dp.y += scale.y)
+	for(sp.x = fb->r.min.x, dp.x = dst->r.min.x+off.x; sp.x < fb->r.max.x; sp.x++, dp.x += scale.x){
 		if(filter != nil)
 			filter(blk, fb, sp);
 		else
--- a/graphics.h
+++ b/graphics.h
@@ -261,10 +261,10 @@
 	uint idx;		/* front buffer index */
 	uint upfilter;		/* upscaling filter */
 
-	void (*draw)(Framebufctl*, Image*);
-	void (*upscaledraw)(Framebufctl*, Image*, Point);
-	void (*memdraw)(Framebufctl*, Memimage*);
-	void (*upscalememdraw)(Framebufctl*, Memimage*, Point);
+	void (*draw)(Framebufctl*, Image*, Point);
+	void (*upscaledraw)(Framebufctl*, Image*, Point, Point);
+	void (*memdraw)(Framebufctl*, Memimage*, Point);
+	void (*upscalememdraw)(Framebufctl*, Memimage*, Point, Point);
 	void (*drawnormals)(Framebufctl*, Image*);
 	void (*swap)(Framebufctl*);
 	void (*reset)(Framebufctl*);
@@ -283,6 +283,8 @@
 	void (*setscale)(Viewport*, double, double);
 	void (*setscalefilter)(Viewport*, int);
 	Framebuf *(*getfb)(Viewport*);
+	int (*getwidth)(Viewport*);
+	int (*getheight)(Viewport*);
 };
 
 struct Camera
@@ -344,8 +346,11 @@
 void perspective(Matrix3, double, double, double, double);
 void orthographic(Matrix3, double, double, double, double, double, double);
 
-/* scene */
+/* obj */
 int loadobjmodel(Model*, OBJ*);
+Model *readobjmodel(char*);
+
+/* scene */
 Model *newmodel(void);
 Model *dupmodel(Model*);
 void delmodel(Model*);
--- /dev/null
+++ b/obj.c
@@ -1,0 +1,317 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <geometry.h>
+#include "libobj/obj.h"
+#include "graphics.h"
+#include "internal.h"
+
+/*
+ * fan triangulation.
+ *
+ * TODO check that the polygon is in fact convex
+ * try to adapt if not (by finding a convex
+ * vertex), or discard it.
+ */
+static int
+triangulate(OBJElem **newe, OBJElem *e)
+{
+	OBJIndexArray *newidxtab;
+	OBJIndexArray *idxtab;
+	int i;
+
+	idxtab = &e->indextab[OBJVGeometric];
+	for(i = 0; i < idxtab->nindex-2; i++){
+		idxtab = &e->indextab[OBJVGeometric];
+		newe[i] = emalloc(sizeof **newe);
+		memset(newe[i], 0, sizeof **newe);
+		newe[i]->type = OBJEFace;
+		newe[i]->mtl = e->mtl;
+		newidxtab = &newe[i]->indextab[OBJVGeometric];
+		newidxtab->nindex = 3;
+		newidxtab->indices = emalloc(newidxtab->nindex*sizeof(*newidxtab->indices));
+		newidxtab->indices[0] = idxtab->indices[0];
+		newidxtab->indices[1] = idxtab->indices[i+1];
+		newidxtab->indices[2] = idxtab->indices[i+2];
+		idxtab = &e->indextab[OBJVTexture];
+		if(idxtab->nindex > 0){
+			newidxtab = &newe[i]->indextab[OBJVTexture];
+			newidxtab->nindex = 3;
+			newidxtab->indices = emalloc(newidxtab->nindex*sizeof(*newidxtab->indices));
+			newidxtab->indices[0] = idxtab->indices[0];
+			newidxtab->indices[1] = idxtab->indices[i+1];
+			newidxtab->indices[2] = idxtab->indices[i+2];
+		}
+		idxtab = &e->indextab[OBJVNormal];
+		if(idxtab->nindex > 0){
+			newidxtab = &newe[i]->indextab[OBJVNormal];
+			newidxtab->nindex = 3;
+			newidxtab->indices = emalloc(newidxtab->nindex*sizeof(*newidxtab->indices));
+			newidxtab->indices[0] = idxtab->indices[0];
+			newidxtab->indices[1] = idxtab->indices[i+1];
+			newidxtab->indices[2] = idxtab->indices[i+2];
+		}
+	}
+
+	return i;
+}
+
+typedef struct OBJ2MtlEntry OBJ2MtlEntry;
+typedef struct OBJ2MtlMap OBJ2MtlMap;
+
+struct OBJ2MtlEntry
+{
+	OBJMaterial *objmtl;
+	ulong idx;
+	OBJ2MtlEntry *next;
+};
+
+struct OBJ2MtlMap
+{
+	OBJ2MtlEntry *head;
+	Material *mtls;
+};
+
+static void
+addmtlmap(OBJ2MtlMap *map, OBJMaterial *om, ulong idx)
+{
+	OBJ2MtlEntry *e;
+
+	if(om == nil)
+		return;
+
+	e = emalloc(sizeof *e);
+	memset(e, 0, sizeof *e);
+	e->objmtl = om;
+	e->idx = idx;
+
+	if(map->head == nil){
+		map->head = e;
+		return;
+	}
+
+	e->next = map->head;
+	map->head = e;
+}
+
+static Material *
+getmtlmap(OBJ2MtlMap *map, OBJMaterial *om)
+{
+	OBJ2MtlEntry *e;
+
+	for(e = map->head; e != nil; e = e->next)
+		if(e->objmtl == om)
+			return &map->mtls[e->idx];
+	return nil;
+}
+
+static void
+clrmtlmap(OBJ2MtlMap *map)
+{
+	OBJ2MtlEntry *e, *ne;
+
+	for(e = map->head; e != nil; e = ne){
+		ne = e->next;
+		free(e);
+	}
+}
+
+int
+loadobjmodel(Model *m, OBJ *obj)
+{
+	Primitive *p;
+	OBJVertex *pverts, *tverts, *nverts, *v;	/* geometric, texture and normals vertices */
+	OBJElem **trielems, *e, *ne;
+	OBJObject *o;
+	OBJIndexArray *idxtab;
+	OBJ2MtlMap mtlmap;
+	OBJMaterial *objmtl;
+	Material *mtl;
+	Point3 n;					/* surface normal */
+	int i, idx, nt, maxnt, neednormal, gottaclean;
+
+	if(obj == nil)
+		return 0;
+
+	pverts = obj->vertdata[OBJVGeometric].verts;
+	tverts = obj->vertdata[OBJVTexture].verts;
+	nverts = obj->vertdata[OBJVNormal].verts;
+	trielems = nil;
+	maxnt = 0;
+
+	if(m->prims != nil){
+		free(m->prims);
+		m->prims = nil;
+	}
+	m->nprims = 0;
+
+	mtlmap.head = nil;
+	for(i = 0; obj->materials != nil && i < nelem(obj->materials->mattab); i++)
+		for(objmtl = obj->materials->mattab[i]; objmtl != nil; objmtl = objmtl->next){
+			mtlmap.mtls = m->materials = erealloc(m->materials, ++m->nmaterials*sizeof(*m->materials));
+			mtl = &m->materials[m->nmaterials-1];
+			memset(mtl, 0, sizeof *mtl);
+
+			if(objmtl->name != nil){
+				mtl->name = strdup(objmtl->name);
+				if(mtl->name == nil)
+					sysfatal("strdup: %r");
+			}
+			mtl->ambient = Pt3(objmtl->Ka.r, objmtl->Ka.g, objmtl->Ka.b, 1);
+			mtl->diffuse = Pt3(objmtl->Kd.r, objmtl->Kd.g, objmtl->Kd.b, 1);
+			mtl->specular = Pt3(objmtl->Ks.r, objmtl->Ks.g, objmtl->Ks.b, 1);
+			mtl->shininess = objmtl->Ns;
+
+			if(objmtl->map_Kd != nil){
+				mtl->diffusemap = alloctexture(sRGBTexture, nil);
+				mtl->diffusemap->image = dupmemimage(objmtl->map_Kd);
+			}
+
+			if(objmtl->norm != nil){
+				mtl->normalmap = alloctexture(RAWTexture, nil);
+				mtl->normalmap->image = dupmemimage(objmtl->norm);
+			}
+
+			addmtlmap(&mtlmap, objmtl, m->nmaterials-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){
+				ne = e->next;
+
+				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);
+
+					idxtab = &e->indextab[OBJVGeometric];
+					v = &pverts[idxtab->indices[0]];
+					p->v[0].p = Pt3(v->x, v->y, v->z, v->w);
+
+					idxtab = &e->indextab[OBJVTexture];
+					if(idxtab->nindex == 1){
+						v = &tverts[idxtab->indices[0]];
+						p->v[0].uv = Pt2(v->u, v->v, 1);
+					}
+					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);
+
+					for(idx = 0; idx < 2; idx++){
+						idxtab = &e->indextab[OBJVGeometric];
+						v = &pverts[idxtab->indices[idx]];
+						p->v[idx].p = Pt3(v->x, v->y, v->z, v->w);
+
+						idxtab = &e->indextab[OBJVTexture];
+						if(idxtab->nindex == 2){
+							v = &tverts[idxtab->indices[idx]];
+							p->v[idx].uv = Pt2(v->u, v->v, 1);
+						}
+					}
+					break;
+				case OBJEFace:
+					idxtab = &e->indextab[OBJVGeometric];
+					assert(idxtab->nindex >= 3);
+					gottaclean = 0;
+
+					/* it takes n-2 triangles to fill any given n-gon */
+					nt = idxtab->nindex-2;
+					if(nt > maxnt){
+						maxnt = nt;
+						trielems = erealloc(trielems, maxnt*sizeof(*trielems));
+					}
+					if(nt > 1){
+						assert(triangulate(trielems, e) == nt);
+						gottaclean = 1;
+					}else
+						trielems[0] = e;
+
+					while(nt-- > 0){
+						e = trielems[nt];
+						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);
+
+						for(idx = 0; idx < 3; idx++){
+							idxtab = &e->indextab[OBJVGeometric];
+							v = &pverts[idxtab->indices[idx]];
+							p->v[idx].p = Pt3(v->x, v->y, v->z, v->w);
+
+							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
+								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);
+							}
+						}
+						if(p->v[0].uv.w != 0){
+							Point3 e0, e1;
+							Point2 Δ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);
+
+							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);
+						}
+						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;
+						}
+						if(gottaclean){
+							free(e->indextab[OBJVGeometric].indices);
+							free(e->indextab[OBJVNormal].indices);
+							free(e->indextab[OBJVTexture].indices);
+							free(e);
+						}
+					}
+					break;
+				default: continue;
+				}
+			}
+
+	free(trielems);
+	clrmtlmap(&mtlmap);
+	return m->nprims;
+}
+
+Model *
+readobjmodel(char *path)
+{
+	Model *m;
+	OBJ *obj;
+
+	m = newmodel();
+	if((obj = objparse(path)) == nil)
+		sysfatal("objparse: %r");
+	loadobjmodel(m, obj);
+	objfree(obj);
+	return m;
+}
--- a/scene.c
+++ b/scene.c
@@ -8,300 +8,6 @@
 #include "graphics.h"
 #include "internal.h"
 
-/*
- * fan triangulation.
- *
- * TODO check that the polygon is in fact convex
- * try to adapt if not (by finding a convex
- * vertex), or discard it.
- */
-static int
-triangulate(OBJElem **newe, OBJElem *e)
-{
-	OBJIndexArray *newidxtab;
-	OBJIndexArray *idxtab;
-	int i;
-
-	idxtab = &e->indextab[OBJVGeometric];
-	for(i = 0; i < idxtab->nindex-2; i++){
-		idxtab = &e->indextab[OBJVGeometric];
-		newe[i] = emalloc(sizeof **newe);
-		memset(newe[i], 0, sizeof **newe);
-		newe[i]->type = OBJEFace;
-		newe[i]->mtl = e->mtl;
-		newidxtab = &newe[i]->indextab[OBJVGeometric];
-		newidxtab->nindex = 3;
-		newidxtab->indices = emalloc(newidxtab->nindex*sizeof(*newidxtab->indices));
-		newidxtab->indices[0] = idxtab->indices[0];
-		newidxtab->indices[1] = idxtab->indices[i+1];
-		newidxtab->indices[2] = idxtab->indices[i+2];
-		idxtab = &e->indextab[OBJVTexture];
-		if(idxtab->nindex > 0){
-			newidxtab = &newe[i]->indextab[OBJVTexture];
-			newidxtab->nindex = 3;
-			newidxtab->indices = emalloc(newidxtab->nindex*sizeof(*newidxtab->indices));
-			newidxtab->indices[0] = idxtab->indices[0];
-			newidxtab->indices[1] = idxtab->indices[i+1];
-			newidxtab->indices[2] = idxtab->indices[i+2];
-		}
-		idxtab = &e->indextab[OBJVNormal];
-		if(idxtab->nindex > 0){
-			newidxtab = &newe[i]->indextab[OBJVNormal];
-			newidxtab->nindex = 3;
-			newidxtab->indices = emalloc(newidxtab->nindex*sizeof(*newidxtab->indices));
-			newidxtab->indices[0] = idxtab->indices[0];
-			newidxtab->indices[1] = idxtab->indices[i+1];
-			newidxtab->indices[2] = idxtab->indices[i+2];
-		}
-	}
-
-	return i;
-}
-
-typedef struct OBJ2MtlEntry OBJ2MtlEntry;
-typedef struct OBJ2MtlMap OBJ2MtlMap;
-
-struct OBJ2MtlEntry
-{
-	OBJMaterial *objmtl;
-	ulong idx;
-	OBJ2MtlEntry *next;
-};
-
-struct OBJ2MtlMap
-{
-	OBJ2MtlEntry *head;
-	Material *mtls;
-};
-
-static void
-addmtlmap(OBJ2MtlMap *map, OBJMaterial *om, ulong idx)
-{
-	OBJ2MtlEntry *e;
-
-	if(om == nil)
-		return;
-
-	e = emalloc(sizeof *e);
-	memset(e, 0, sizeof *e);
-	e->objmtl = om;
-	e->idx = idx;
-
-	if(map->head == nil){
-		map->head = e;
-		return;
-	}
-
-	e->next = map->head;
-	map->head = e;
-}
-
-static Material *
-getmtlmap(OBJ2MtlMap *map, OBJMaterial *om)
-{
-	OBJ2MtlEntry *e;
-
-	for(e = map->head; e != nil; e = e->next)
-		if(e->objmtl == om)
-			return &map->mtls[e->idx];
-	return nil;
-}
-
-static void
-clrmtlmap(OBJ2MtlMap *map)
-{
-	OBJ2MtlEntry *e, *ne;
-
-	for(e = map->head; e != nil; e = ne){
-		ne = e->next;
-		free(e);
-	}
-}
-
-int
-loadobjmodel(Model *m, OBJ *obj)
-{
-	Primitive *p;
-	OBJVertex *pverts, *tverts, *nverts, *v;	/* geometric, texture and normals vertices */
-	OBJElem **trielems, *e, *ne;
-	OBJObject *o;
-	OBJIndexArray *idxtab;
-	OBJ2MtlMap mtlmap;
-	OBJMaterial *objmtl;
-	Material *mtl;
-	Point3 n;					/* surface normal */
-	int i, idx, nt, maxnt, neednormal, gottaclean;
-
-	if(obj == nil)
-		return 0;
-
-	pverts = obj->vertdata[OBJVGeometric].verts;
-	tverts = obj->vertdata[OBJVTexture].verts;
-	nverts = obj->vertdata[OBJVNormal].verts;
-	trielems = nil;
-	maxnt = 0;
-
-	if(m->prims != nil){
-		free(m->prims);
-		m->prims = nil;
-	}
-	m->nprims = 0;
-
-	mtlmap.head = nil;
-	for(i = 0; obj->materials != nil && i < nelem(obj->materials->mattab); i++)
-		for(objmtl = obj->materials->mattab[i]; objmtl != nil; objmtl = objmtl->next){
-			mtlmap.mtls = m->materials = erealloc(m->materials, ++m->nmaterials*sizeof(*m->materials));
-			mtl = &m->materials[m->nmaterials-1];
-			memset(mtl, 0, sizeof *mtl);
-
-			if(objmtl->name != nil){
-				mtl->name = strdup(objmtl->name);
-				if(mtl->name == nil)
-					sysfatal("strdup: %r");
-			}
-			mtl->ambient = Pt3(objmtl->Ka.r, objmtl->Ka.g, objmtl->Ka.b, 1);
-			mtl->diffuse = Pt3(objmtl->Kd.r, objmtl->Kd.g, objmtl->Kd.b, 1);
-			mtl->specular = Pt3(objmtl->Ks.r, objmtl->Ks.g, objmtl->Ks.b, 1);
-			mtl->shininess = objmtl->Ns;
-
-			if(objmtl->map_Kd != nil){
-				mtl->diffusemap = alloctexture(sRGBTexture, nil);
-				mtl->diffusemap->image = dupmemimage(objmtl->map_Kd);
-			}
-
-			if(objmtl->norm != nil){
-				mtl->normalmap = alloctexture(RAWTexture, nil);
-				mtl->normalmap->image = dupmemimage(objmtl->norm);
-			}
-
-			addmtlmap(&mtlmap, objmtl, m->nmaterials-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){
-				ne = e->next;
-
-				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);
-
-					idxtab = &e->indextab[OBJVGeometric];
-					v = &pverts[idxtab->indices[0]];
-					p->v[0].p = Pt3(v->x, v->y, v->z, v->w);
-
-					idxtab = &e->indextab[OBJVTexture];
-					if(idxtab->nindex == 1){
-						v = &tverts[idxtab->indices[0]];
-						p->v[0].uv = Pt2(v->u, v->v, 1);
-					}
-					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);
-
-					for(idx = 0; idx < 2; idx++){
-						idxtab = &e->indextab[OBJVGeometric];
-						v = &pverts[idxtab->indices[idx]];
-						p->v[idx].p = Pt3(v->x, v->y, v->z, v->w);
-
-						idxtab = &e->indextab[OBJVTexture];
-						if(idxtab->nindex == 2){
-							v = &tverts[idxtab->indices[idx]];
-							p->v[idx].uv = Pt2(v->u, v->v, 1);
-						}
-					}
-					break;
-				case OBJEFace:
-					idxtab = &e->indextab[OBJVGeometric];
-					assert(idxtab->nindex >= 3);
-					gottaclean = 0;
-
-					/* it takes n-2 triangles to fill any given n-gon */
-					nt = idxtab->nindex-2;
-					if(nt > maxnt){
-						maxnt = nt;
-						trielems = erealloc(trielems, maxnt*sizeof(*trielems));
-					}
-					if(nt > 1){
-						assert(triangulate(trielems, e) == nt);
-						gottaclean = 1;
-					}else
-						trielems[0] = e;
-
-					while(nt-- > 0){
-						e = trielems[nt];
-						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);
-
-						for(idx = 0; idx < 3; idx++){
-							idxtab = &e->indextab[OBJVGeometric];
-							v = &pverts[idxtab->indices[idx]];
-							p->v[idx].p = Pt3(v->x, v->y, v->z, v->w);
-
-							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
-								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);
-							}
-						}
-						if(p->v[0].uv.w != 0){
-							Point3 e0, e1;
-							Point2 Δ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);
-
-							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);
-						}
-						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;
-						}
-						if(gottaclean){
-							free(e->indextab[OBJVGeometric].indices);
-							free(e->indextab[OBJVNormal].indices);
-							free(e->indextab[OBJVTexture].indices);
-							free(e);
-						}
-					}
-					break;
-				default: continue;
-				}
-			}
-
-	free(trielems);
-	clrmtlmap(&mtlmap);
-	return m->nprims;
-}
-
 Model *
 newmodel(void)
 {
--- a/viewport.c
+++ b/viewport.c
@@ -11,31 +11,33 @@
 static void
 viewport_draw(Viewport *v, Image *dst)
 {
-	Point scale;
+	Point off, scale;
 
+	off = Pt(v->p.x, v->p.y);
 	/* no downsampling support yet */
 	scale.x = max(min(v->bx.x, Dx(dst->r)/Dx(v->r)), 1);
 	scale.y = max(min(v->by.y, Dy(dst->r)/Dy(v->r)), 1);
 
 	if(scale.x > 1 || scale.y > 1)
-		v->fbctl->upscaledraw(v->fbctl, dst, scale);
+		v->fbctl->upscaledraw(v->fbctl, dst, off, scale);
 	else
-		v->fbctl->draw(v->fbctl, dst);
+		v->fbctl->draw(v->fbctl, dst, off);
 }
 
 static void
 viewport_memdraw(Viewport *v, Memimage *dst)
 {
-	Point scale;
+	Point off, scale;
 
+	off = Pt(v->p.x, v->p.y);
 	/* no downsampling support yet */
 	scale.x = max(min(v->bx.x, Dx(dst->r)/Dx(v->r)), 1);
 	scale.y = max(min(v->by.y, Dy(dst->r)/Dy(v->r)), 1);
 
 	if(scale.x > 1 || scale.y > 1)
-		v->fbctl->upscalememdraw(v->fbctl, dst, scale);
+		v->fbctl->upscalememdraw(v->fbctl, dst, off, scale);
 	else
-		v->fbctl->memdraw(v->fbctl, dst);
+		v->fbctl->memdraw(v->fbctl, dst, off);
 }
 
 static void
@@ -59,6 +61,18 @@
 	return v->fbctl->getfb(v->fbctl);
 }
 
+static int
+viewport_getwidth(Viewport *v)
+{
+	return Dx(v->r)*v->bx.x;
+}
+
+static int
+viewport_getheight(Viewport *v)
+{
+	return Dy(v->r)*v->by.y;
+}
+
 Viewport *
 mkviewport(Rectangle r)
 {
@@ -75,6 +89,8 @@
 	v->setscale = viewport_setscale;
 	v->setscalefilter = viewport_setscalefilter;
 	v->getfb = viewport_getfb;
+	v->getwidth = viewport_getwidth;
+	v->getheight = viewport_getheight;
 	return v;
 }