summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--alloc.c1
-rw-r--r--camera.c1
-rw-r--r--clip.c7
-rw-r--r--color.c1
-rw-r--r--fb.c1
-rw-r--r--graphics.h8
-rw-r--r--internal.h5
-rw-r--r--marshal.c939
-rw-r--r--mkfile16
-rw-r--r--model.6.txt18
-rw-r--r--obj.c317
-rw-r--r--readme6
-rw-r--r--render.c7
-rw-r--r--scene.c5
-rw-r--r--shadeop.c1
-rw-r--r--texture.c1
-rw-r--r--util.c13
-rw-r--r--vertex.c1
-rw-r--r--viewport.c1
-rw-r--r--xform.c1
20 files changed, 987 insertions, 363 deletions
diff --git a/alloc.c b/alloc.c
index 4154c83..3de73a9 100644
--- a/alloc.c
+++ b/alloc.c
@@ -4,7 +4,6 @@
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
-#include "libobj/obj.h"
#include "graphics.h"
#include "internal.h"
diff --git a/camera.c b/camera.c
index 439c596..f89640f 100644
--- a/camera.c
+++ b/camera.c
@@ -4,7 +4,6 @@
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
-#include "libobj/obj.h"
#include "graphics.h"
#include "internal.h"
diff --git a/clip.c b/clip.c
index 7548b36..14fdc7f 100644
--- a/clip.c
+++ b/clip.c
@@ -4,7 +4,6 @@
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
-#include "libobj/obj.h"
#include "graphics.h"
#include "internal.h"
@@ -52,12 +51,6 @@ fprintpoly(int fd, Polygon *p)
fprint(fd, "%d/%lud p %V\n", i, p->n, p->v[i].p);
}
-static int
-eqpt3(Point3 a, Point3 b)
-{
- return vec3len(subpt3(a, b)) < 1e-6;
-}
-
/*
* references:
* - “Clipping Using Homogeneous Coordinates”, James F. Blinn, Martin E. Newell, SIGGRAPH '78, pp. 245-251
diff --git a/color.c b/color.c
index 6a6023d..16a3f9a 100644
--- a/color.c
+++ b/color.c
@@ -4,7 +4,6 @@
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
-#include "libobj/obj.h"
#include "graphics.h"
#include "internal.h"
diff --git a/fb.c b/fb.c
index 457af7e..64d2915 100644
--- a/fb.c
+++ b/fb.c
@@ -4,7 +4,6 @@
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
-#include "libobj/obj.h"
#include "graphics.h"
#include "internal.h"
diff --git a/graphics.h b/graphics.h
index 2fd516e..e64354c 100644
--- a/graphics.h
+++ b/graphics.h
@@ -403,9 +403,9 @@ Point3 world2model(Entity*, Point3);
void perspective(Matrix3, double, double, double, double);
void orthographic(Matrix3, double, double, double, double, double, double);
-/* obj */
-int loadobjmodel(Model*, OBJ*);
-Model *readobjmodel(char*);
+/* marshal */
+Model *readmodel(int);
+usize writemodel(int, Model*);
/* scene */
Model *newmodel(void);
@@ -441,9 +441,11 @@ Point maxpt(Point, Point);
Point2 modulapt2(Point2, Point2);
Point2 minpt2(Point2, Point2);
Point2 maxpt2(Point2, Point2);
+int eqpt2(Point2, Point2);
Point3 modulapt3(Point3, Point3);
Point3 minpt3(Point3, Point3);
Point3 maxpt3(Point3, Point3);
+int eqpt3(Point3, Point3);
Memimage *rgba(ulong);
Memimage *dupmemimage(Memimage*);
diff --git a/internal.h b/internal.h
index 4fc197a..cbea04a 100644
--- a/internal.h
+++ b/internal.h
@@ -1,3 +1,8 @@
+enum {
+ ε1 = 1e-5,
+ ε2 = 1e-6,
+};
+
typedef struct Polygon Polygon;
typedef struct Entityparam Entityparam;
typedef struct Tilerparam Tilerparam;
diff --git a/marshal.c b/marshal.c
new file mode 100644
index 0000000..15e4760
--- /dev/null
+++ b/marshal.c
@@ -0,0 +1,939 @@
+#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"
+
+enum {
+ NaI = ~0ULL, /* not an index */
+ MTLHTSIZ = 17,
+};
+
+typedef struct Curline Curline;
+struct Curline
+{
+ char file[256];
+ 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;
+ ulong idx;
+ Mtlentry *next;
+};
+
+struct Mtltab
+{
+ Mtlentry *mtls[MTLHTSIZ];
+ ulong nmtls;
+ int loaded; /* was the table loaded into a model already? */
+};
+
+static void
+error(Curline *l, char *fmt, ...)
+{
+ va_list va;
+ char buf[ERRMAX], *bp;
+
+ bp = seprint(buf, buf + sizeof buf, "%s:%llud ", l->file, l->line);
+
+ va_start(va, fmt);
+ vseprint(bp, buf + sizeof buf, fmt, va);
+ va_end(va);
+
+ 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)
+{
+ uint h;
+
+ h = 0x811c9dc5;
+ while(*s != 0)
+ h = (h^(uchar)*s++) * 0x1000193;
+ return h % MTLHTSIZ;
+}
+
+static Mtltab *
+mkmtltab(void)
+{
+ Mtltab *t;
+
+ t = emalloc(sizeof *t);
+ memset(t, 0, sizeof *t);
+ return t;
+}
+
+static void
+freemtlentry(Mtlentry *m)
+{
+ freetexture(m->normalmap);
+ freetexture(m->specularmap);
+ freetexture(m->diffusemap);
+ free(m->name);
+ free(m);
+}
+
+static Mtlentry *
+mtltabadd(Mtltab *t, Material *m)
+{
+ Mtlentry *nm, *mp, *prev;
+ uint h;
+
+ nm = emalloc(sizeof *nm);
+ memset(nm, 0, sizeof *nm);
+ nm->Material = *m;
+ nm->next = nil;
+
+ prev = nil;
+ h = hash(nm->name);
+ for(mp = t->mtls[h]; mp != nil; prev = mp, mp = mp->next)
+ if(strcmp(mp->name, nm->name) == 0){
+ werrstr("material already exists");
+ return nil;
+ }
+ if(prev == nil){
+ t->mtls[h] = nm;
+ t->nmtls++;
+ return nm;
+ }
+ prev->next = nm;
+ t->nmtls++;
+ return nm;
+}
+
+static Mtlentry *
+mtltabget(Mtltab *t, char *name)
+{
+ Mtlentry *m;
+ uint h;
+
+ h = hash(name);
+ for(m = t->mtls[h]; m != nil; m = m->next)
+ if(strcmp(m->name, name) == 0)
+ break;
+ return m;
+}
+
+static void
+mtltabloadmodel(Model *m, Mtltab *t)
+{
+ Mtlentry *e;
+ int i;
+
+ for(i = 0; i < nelem(t->mtls); i++)
+ for(e = t->mtls[i]; e != nil; e = e->next)
+ e->idx = m->addmaterial(m, *e);
+ t->loaded++;
+}
+
+static void
+rmmtltab(Mtltab *t)
+{
+ Mtlentry *m, *nm;
+ int i;
+
+ for(i = 0; i < nelem(t->mtls); i++)
+ for(m = t->mtls[i]; m != nil; m = nm){
+ nm = m->next;
+ if(t->loaded)
+ free(m);
+ else
+ freemtlentry(m);
+ }
+}
+
+/*
+ * TODO if materials are inserted between primitive declarations
+ * references to those materials from early primitives can cause
+ * out-of-bounds accesses. find a solution.
+ *
+ * example:
+ *
+ * mtl A {
+ * diffuse: 1 0 0
+ * }
+ * prim ... A
+ * mtl B {
+ * diffuse: 1 0 0
+ * }
+ * prim ... B
+ *
+ * now the reference to A is probably wrong because of realloc.
+ */
+Model *
+readmodel(int fd)
+{
+ Curline curline;
+ IArray *pa, *na, *ta, *ca, *Ta, *va, *prima;
+ Mtltab *mtltab;
+ Mtlentry *me;
+ Point3 p, n, T;
+ Point2 t;
+ Color c;
+ Vertex v;
+ Primitive prim;
+ Material mtl;
+ Model *m;
+ Memimage *mi;
+ Biobuf *bin;
+ 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;
+ t.w = 1;
+ m = nil;
+
+ bin = Bfdopen(fd, OREAD);
+ if(bin == nil)
+ sysfatal("Bfdopen: %r");
+
+ pa = mkitemarray(sizeof(p));
+ na = mkitemarray(sizeof(n));
+ ta = mkitemarray(sizeof(t));
+ ca = mkitemarray(sizeof(c));
+ Ta = mkitemarray(sizeof(T));
+ va = mkitemarray(sizeof(v));
+ prima = mkitemarray(sizeof(prim));
+ mtltab = mkmtltab();
+
+ memset(&curline, 0, sizeof curline);
+ if(fd2path(fd, curline.file, sizeof curline.file) != 0)
+ sysfatal("fd2path: %r");
+ if((s = strrchr(curline.file, '/')) != nil){
+ *s = 0;
+ snprint(assets, sizeof assets, "%s", curline.file);
+ memmove(curline.file, s+1, strlen(s));
+ }else{
+ assets[0] = '.';
+ assets[1] = 0;
+ }
+ inamaterial = 0;
+
+ while((line = Brdline(bin, '\n')) != nil){
+ line[Blinelen(bin)-1] = 0;
+ curline.line++;
+
+ nf = tokenize(line, f, nelem(f));
+ if(nf < 1)
+ continue;
+
+ if(inamaterial){
+ if((s = strchr(f[0], ':')) != nil)
+ *s = 0;
+
+ if(strcmp(f[0], "}") == 0){
+ if(mtltabadd(mtltab, &mtl) == nil){
+ error(&curline, "mtltabadd: %r");
+ goto getout;
+ }
+ inamaterial--;
+ }else if(strcmp(f[0], "ambient") == 0){
+ if(nf != 4 && nf != 5){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ mtl.ambient.r = strtod(f[1], nil);
+ mtl.ambient.g = strtod(f[2], nil);
+ mtl.ambient.b = strtod(f[3], nil);
+ mtl.ambient.a = nf == 5? strtod(f[4], nil): 1;
+ }else if(strcmp(f[0], "diffuse") == 0){
+ if(nf != 4 && nf != 5){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+
+ mtl.diffuse.r = strtod(f[1], nil);
+ mtl.diffuse.g = strtod(f[2], nil);
+ mtl.diffuse.b = strtod(f[3], nil);
+ mtl.diffuse.a = nf == 5? strtod(f[4], nil): 1;
+ }else if(strcmp(f[0], "diffusemap") == 0){
+ if(nf != 2){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ if(mtl.diffusemap != nil){
+ error(&curline, "there is already a diffuse map");
+ goto getout;
+ }
+
+ snprint(buf, sizeof buf, "%s/%s", assets, f[1]);
+ texfd = open(buf, OREAD);
+ if(texfd < 0){
+notexture:
+ error(&curline, "could not read texture '%s'", f[1]);
+ goto getout;
+ }
+ mi = readmemimage(texfd);
+ if(mi == nil){
+ close(texfd);
+ goto notexture;
+ }
+ mtl.diffusemap = alloctexture(sRGBTexture, mi);
+ close(texfd);
+ }else if(strcmp(f[0], "specular") == 0){
+ if(nf != 4 && nf != 5){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+
+ mtl.specular.r = strtod(f[1], nil);
+ mtl.specular.g = strtod(f[2], nil);
+ mtl.specular.b = strtod(f[3], nil);
+ mtl.specular.a = nf == 5? strtod(f[4], nil): 1;
+ }else if(strcmp(f[0], "specularmap") == 0){
+ if(nf != 2){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ if(mtl.specularmap != nil){
+ error(&curline, "there is already a specular map");
+ goto getout;
+ }
+
+ snprint(buf, sizeof buf, "%s/%s", assets, f[1]);
+ texfd = open(buf, OREAD);
+ if(texfd < 0)
+ goto notexture;
+ mi = readmemimage(texfd);
+ if(mi == nil){
+ close(texfd);
+ goto notexture;
+ }
+ mtl.specularmap = alloctexture(RAWTexture, mi);
+ close(texfd);
+ }else if(strcmp(f[0], "shininess") == 0){
+ if(nf != 2){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ mtl.shininess = strtod(f[1], nil);
+ }else if(strcmp(f[0], "normals") == 0){
+ if(nf != 2){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ if(mtl.normalmap != nil){
+ error(&curline, "there is already a normal map");
+ goto getout;
+ }
+
+ snprint(buf, sizeof buf, "%s/%s", assets, f[1]);
+ texfd = open(buf, OREAD);
+ if(texfd < 0)
+ goto notexture;
+ mi = readmemimage(texfd);
+ if(mi == nil){
+ close(texfd);
+ goto notexture;
+ }
+ mtl.normalmap = alloctexture(RAWTexture, mi);
+ close(texfd);
+ }else{
+ error(&curline, "unknown mtl parameter '%s'", f[0]);
+ goto getout;
+ }
+
+ continue;
+ }
+
+ if(strcmp(f[0], "p") == 0){
+ if(nf != 4 && nf != 5){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ p.x = strtod(f[1], nil);
+ p.y = strtod(f[2], nil);
+ p.z = strtod(f[3], nil);
+ p.w = nf == 5? strtod(f[4], nil): 1;
+ itemarrayadd(pa, &p, 0);
+ }else if(strcmp(f[0], "n") == 0){
+ if(nf != 4){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ n.x = strtod(f[1], nil);
+ n.y = strtod(f[2], nil);
+ n.z = strtod(f[3], nil);
+ itemarrayadd(na, &n, 0);
+ }else if(strcmp(f[0], "t") == 0){
+ if(nf != 3){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ t.x = strtod(f[1], nil);
+ t.y = strtod(f[2], nil);
+ itemarrayadd(ta, &t, 0);
+ }else if(strcmp(f[0], "c") == 0){
+ if(nf != 4 && nf != 5){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ c.r = strtod(f[1], nil);
+ c.g = strtod(f[2], nil);
+ c.b = strtod(f[3], nil);
+ c.a = nf == 5? strtod(f[4], nil): 1;
+ itemarrayadd(ca, &c, 0);
+ }else if(strcmp(f[0], "T") == 0){
+ if(nf != 4){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ T.x = strtod(f[1], nil);
+ T.y = strtod(f[2], nil);
+ T.z = strtod(f[3], nil);
+ itemarrayadd(Ta, &T, 0);
+ }else if(strcmp(f[0], "v") == 0){
+ if(nf != 5){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ memset(&v, 0, sizeof v);
+
+ if(strcmp(f[1], "-") == 0){
+ error(&curline, "vertex has no position");
+ goto getout;
+ }
+ idx = strtoul(f[1], nil, 10);
+ vp = itemarrayget(pa, idx);
+ if(vp == nil){
+ error(&curline, "no position at idx %llud", idx);
+ goto getout;
+ }
+ v.p = *(Point3*)vp;
+
+ if(strcmp(f[2], "-") != 0){
+ idx = strtoul(f[2], nil, 10);
+ vp = itemarrayget(na, idx);
+ if(vp == nil){
+ error(&curline, "no normal at idx %llud", idx);
+ goto getout;
+ }
+ v.n = *(Point3*)vp;
+ }
+
+ if(strcmp(f[3], "-") != 0){
+ idx = strtoul(f[3], nil, 10);
+ vp = itemarrayget(ta, idx);
+ if(vp == nil){
+ error(&curline, "no texture at idx %llud", idx);
+ goto getout;
+ }
+ v.uv = *(Point2*)vp;
+ }
+
+ if(strcmp(f[4], "-") != 0){
+ idx = strtoul(f[4], nil, 10);
+ vp = itemarrayget(ca, idx);
+ if(vp == nil){
+ error(&curline, "no color at idx %llud", idx);
+ goto getout;
+ }
+ v.c = *(Color*)vp;
+ }
+
+ itemarrayadd(va, &v, 0);
+ }else if(strcmp(f[0], "prim") == 0){
+ if(nf < 3 || nf > 7){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ memset(&prim, 0, sizeof prim);
+
+ nv = strtoul(f[1], nil, 10);
+ switch(nv-1){
+ case PPoint:
+ prim.type = PPoint;
+
+ idx = strtoul(f[2], nil, 10);
+ vp = itemarrayget(va, idx);
+ if(vp == nil){
+novertex:
+ error(&curline, "no vertex at idx %llud", idx);
+ goto getout;
+ }
+ prim.v[0] = *(Vertex*)vp;
+
+ /* ignore 4th field (nf == 4) */
+
+ if(nf == 5){
+ prim.mtl = mtltabget(mtltab, f[4]);
+ if(prim.mtl == nil){
+ error(&curline, "material '%s' not found", f[4]);
+ goto getout;
+ }
+ }
+ break;
+ case PLine:
+ prim.type = PLine;
+
+ idx = strtoul(f[2], nil, 10);
+ vp = itemarrayget(va, idx);
+ if(vp == nil)
+ goto novertex;
+ prim.v[0] = *(Vertex*)vp;
+
+ if(nf < 4){
+notenough:
+ error(&curline, "not enough prim vertices");
+ goto getout;
+ }
+ idx = strtoul(f[3], nil, 10);
+ vp = itemarrayget(va, idx);
+ if(vp == nil)
+ goto novertex;
+ prim.v[1] = *(Vertex*)vp;
+
+ /* ignore 5th field (nf == 5) */
+
+ if(nf == 6){
+ prim.mtl = mtltabget(mtltab, f[5]);
+ if(prim.mtl == nil){
+ error(&curline, "material '%s' not found", f[5]);
+ goto getout;
+ }
+ }
+ break;
+ case PTriangle:
+ prim.type = PTriangle;
+
+ idx = strtoul(f[2], nil, 10);
+ vp = itemarrayget(va, idx);
+ if(vp == nil)
+ goto novertex;
+ prim.v[0] = *(Vertex*)vp;
+
+ if(nf < 4)
+ goto notenough;
+ idx = strtoul(f[3], nil, 10);
+ vp = itemarrayget(va, idx);
+ if(vp == nil)
+ goto novertex;
+ prim.v[1] = *(Vertex*)vp;
+
+ if(nf < 5)
+ goto notenough;
+ idx = strtoul(f[4], nil, 10);
+ vp = itemarrayget(va, idx);
+ if(vp == nil)
+ goto novertex;
+ prim.v[2] = *(Vertex*)vp;
+
+ if(nf < 6){
+ error(&curline, "missing triangle tangent field");
+ goto getout;
+ }
+ if(strcmp(f[5], "-") != 0){
+ idx = strtoul(f[5], nil, 10);
+ vp = itemarrayget(Ta, idx);
+ if(vp == nil){
+ error(&curline, "no tangent at idx %llud", idx);
+ goto getout;
+ }
+ prim.tangent = *(Point3*)vp;
+ }
+
+ if(nf == 7){
+ prim.mtl = mtltabget(mtltab, f[6]);
+ if(prim.mtl == nil){
+ error(&curline, "material '%s' not found", f[6]);
+ goto getout;
+ }
+ }
+ break;
+ default:
+ error(&curline, "alien primitive detected");
+ goto getout;
+ }
+
+ itemarrayadd(prima, &prim, 0);
+ }else if(strcmp(f[0], "mtl") == 0){
+ if(nf != 3 || strcmp(f[2], "{") != 0){
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ memset(&mtl, 0, sizeof mtl);
+
+ mtl.name = strdup(f[1]);
+ if(mtl.name == nil)
+ sysfatal("strdup: %r");
+ inamaterial++;
+ }else{
+ error(&curline, "syntax error");
+ goto getout;
+ }
+ }
+
+ if(prima->nitems < 1){
+ werrstr("no primitives no model");
+ goto getout;
+ }
+
+ m = newmodel();
+ mtltabloadmodel(m, mtltab);
+ for(i = 0; i < prima->nitems; i++){
+ primidx = m->addprim(m, *(Primitive*)itemarrayget(prima, i));
+ if(m->prims[primidx].mtl != nil){
+ me = mtltabget(mtltab, m->prims[primidx].mtl->name);
+ m->prims[primidx].mtl = &m->materials[me->idx];
+ }
+ }
+
+getout:
+ rmitemarray(pa);
+ rmitemarray(na);
+ rmitemarray(ta);
+ rmitemarray(ca);
+ rmitemarray(Ta);
+ rmitemarray(va);
+ rmitemarray(prima);
+ rmmtltab(mtltab);
+ Bterm(bin);
+ return m;
+}
+
+static int
+Bprintp3(Biobuf *b, Point3 *p)
+{
+ int n;
+
+ n = Bprint(b, "%g %g %g", p->x, p->y, p->z);
+ if(p->w != 1)
+ n += Bprint(b, " %g", p->w);
+ n += Bprint(b, "\n");
+ return n;
+}
+
+static int
+Bprintp2(Biobuf *b, Point2 *p)
+{
+ int n;
+
+ n = Bprint(b, "%g %g", p->x, p->y);
+ if(p->w != 1)
+ n += Bprint(b, " %g", p->w);
+ n += Bprint(b, "\n");
+ return n;
+}
+
+static int
+Bprintn3(Biobuf *b, Point3 *p)
+{
+ return Bprint(b, "%g %g %g\n", p->x, p->y, p->z);
+}
+
+static int
+Bprintp(Biobuf *b, Point3 *p)
+{
+ int n;
+
+ n = Bprint(b, "p ");
+ n += Bprintp3(b, p);
+ return n;
+}
+
+static int
+Bprintn(Biobuf *b, Point3 *p)
+{
+ int n;
+
+ n = Bprint(b, "n ");
+ n += Bprintn3(b, p);
+ return n;
+}
+
+static int
+Bprintt(Biobuf *b, Point2 *p)
+{
+ int n;
+
+ n = Bprint(b, "t ");
+ n += Bprintp2(b, p);
+ return n;
+}
+
+static int
+Bprintc(Biobuf *b, Point3 *p)
+{
+ int n;
+
+ n = Bprint(b, "c ");
+ n += Bprintp3(b, p);
+ return n;
+}
+
+static int
+BprintT(Biobuf *b, Point3 *p)
+{
+ int n;
+
+ n = Bprint(b, "T ");
+ n += Bprintn3(b, p);
+ return n;
+}
+
+static int
+Bprintidx(Biobuf *b, usize idx)
+{
+ if(idx == NaI)
+ return Bprint(b, " -");
+ return Bprint(b, " %llud", idx);
+}
+
+static int
+Bprintv(Biobuf *b, Wirevert *v)
+{
+ int n;
+
+ n = Bprint(b, "v %llud", v->p);
+ n += Bprintidx(b, v->n);
+ n += Bprintidx(b, v->t);
+ n += Bprintidx(b, v->c);
+ n += Bprint(b, "\n");
+ return n;
+}
+
+static int
+Bprintprim(Biobuf *b, Wireprim *p)
+{
+ char *s;
+ int n, i;
+
+ n = Bprint(b, "prim %d", p->nv);
+ for(i = 0; i < p->nv; i++)
+ n += Bprintidx(b, p->v[i]);
+ n += Bprintidx(b, p->T);
+ if(p->mtlname != nil){
+ s = quotestrdup(p->mtlname);
+ if(s == nil)
+ sysfatal("quotestrdup: %r");
+ n += Bprint(b, " %s", s);
+ free(s);
+ }
+ n += Bprint(b, "\n");
+ return n;
+}
+
+/* TODO how do we deal with textures? embedded? keep a path? */
+static int
+Bprintmtl(Biobuf *b, Material *m)
+{
+ char *s;
+ int n;
+
+ s = quotestrdup(m->name);
+ if(s == nil)
+ sysfatal("quotestrdup: %r");
+ n = Bprint(b, "mtl %s {\n", s);
+ free(s);
+
+ if(m->ambient.a > 0){
+ n += Bprint(b, "\tambient: ");
+ n += Bprint(b, "%g %g %g", m->ambient.r, m->ambient.g, m->ambient.b);
+ if(m->ambient.a != 1)
+ n += Bprint(b, " %g", m->ambient.a);
+ n += Bprint(b, "\n");
+ }
+
+ if(m->diffuse.a > 0 || m->diffusemap != nil){
+ n += Bprint(b, "\tdiffuse: ");
+ n += Bprint(b, "%g %g %g", m->diffuse.r, m->diffuse.g, m->diffuse.b);
+ if(m->diffuse.a != 1)
+ n += Bprint(b, " %g", m->diffuse.a);
+ n += Bprint(b, "\n");
+ }
+
+ if(m->specular.a > 0 || m->specularmap != nil){
+ n += Bprint(b, "\tspecular: ");
+ n += Bprint(b, "%g %g %g", m->specular.r, m->specular.g, m->specular.b);
+ if(m->specular.a != 1)
+ n += Bprint(b, " %g", m->specular.a);
+ n += Bprint(b, "\n");
+ }
+
+ if(m->shininess > 0){
+ n += Bprint(b, "\tshininess: ");
+ n += Bprint(b, "%g\n", m->shininess);
+ }
+
+// if(m->diffusemap != nil){
+// n += Bprint("\tdiffusemap: ");
+// n += Bprint(b, "%s\n", m
+// }
+
+// if(m->specularmap != nil){
+// n += Bprint(b, "\tspecularmap: ");
+// n += Bprint(b, "%s\n", m
+// }
+
+// if(m->normalmap != nil)
+// n += Bprint(b, "\tnormals: ");
+
+ n += Bprint(b, "}\n");
+ return n;
+}
+
+usize
+writemodel(int fd, Model *m)
+{
+ IArray *pa, *na, *ta, *ca, *Ta, *va, *prima;
+ Wirevert v;
+ Wireprim prim;
+ Primitive *p, *ep;
+ Biobuf *out;
+ usize n;
+ int 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));
+ prima = mkitemarray(sizeof(Wireprim));
+
+ n = 0;
+ p = m->prims;
+ ep = p + m->nprims;
+
+ while(p < ep){
+ memset(&prim, 0, sizeof prim);
+
+ prim.nv = p->type+1;
+ for(i = 0; i < prim.nv; i++){
+ v.p = itemarrayadd(pa, &p->v[i].p, 1);
+ v.n = eqpt3(p->v[i].n, Vec3(0,0,0))?
+ NaI: itemarrayadd(na, &p->v[i].n, 1);
+ v.t = p->v[i].uv.w != 1?
+ NaI: itemarrayadd(ta, &p->v[i].uv, 1);
+ v.c = p->v[i].c.a == 0?
+ NaI: itemarrayadd(ca, &p->v[i].c, 1);
+ prim.v[i] = itemarrayadd(va, &v, 1);
+ }
+ prim.T = eqpt3(p->tangent, Vec3(0,0,0))?
+ NaI: itemarrayadd(Ta, &p->tangent, 1);
+ prim.mtlname = p->mtl != nil? p->mtl->name: nil;
+
+ itemarrayadd(prima, &prim, 1);
+ 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 < prima->nitems; i++)
+ n += Bprintprim(out, itemarrayget(prima, i));
+
+ rmitemarray(pa);
+ rmitemarray(na);
+ rmitemarray(ta);
+ rmitemarray(ca);
+ rmitemarray(Ta);
+ rmitemarray(va);
+ rmitemarray(prima);
+ Bterm(out);
+ return n;
+}
diff --git a/mkfile b/mkfile
index 558ca9c..0dc0ebb 100644
--- a/mkfile
+++ b/mkfile
@@ -7,7 +7,6 @@ OFILES=\
render.$O\
clip.$O\
xform.$O\
- obj.$O\
scene.$O\
vertex.$O\
texture.$O\
@@ -17,11 +16,11 @@ OFILES=\
color.$O\
util.$O\
nanosec.$O\
+ marshal.$O\
HFILES=\
graphics.h\
internal.h\
- libobj/obj.h
UPDATE=\
mkfile\
@@ -29,16 +28,3 @@ UPDATE=\
${OFILES:%.$O=%.c}\
</sys/src/cmd/mklib
-
-libobj/libobj.a$O:
- cd libobj
- mk install
-
-pulldeps:VQ:
- git/clone git://antares-labs.eu/libobj || \
- git/clone git://shithub.us/rodri/libobj || \
- git/clone https://github.com/sametsisartenep/libobj
-
-clean nuke:V:
- rm -f *.[$OS] $LIB
- @{cd libobj; mk $target}
diff --git a/model.6.txt b/model.6.txt
new file mode 100644
index 0000000..f91527d
--- /dev/null
+++ b/model.6.txt
@@ -0,0 +1,18 @@
+p x y z [w]
+n x y z
+t u v
+c r g b [a]
+v pᵢ [nᵢ|-] [tᵢ|-] [cᵢ|-]
+
+T x y z
+prim nverts vᵢ... [Tᵢ|-] [mtlname]
+
+mtl name {
+ ambient: r g b [a]
+ diffuse: r g b [a]
+ specular: r g b [a]
+ shininess: n
+ diffusemap: path
+ specularmap: path
+ normals: path
+}
diff --git a/obj.c b/obj.c
deleted file mode 100644
index 8c7b13f..0000000
--- a/obj.c
+++ /dev/null
@@ -1,317 +0,0 @@
-#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;
-}
diff --git a/readme b/readme
index c7f2db8..908e1fd 100644
--- a/readme
+++ b/readme
@@ -1,9 +1,7 @@
libgraphics
-Libgraphics provides 3D computer graphics through memdraw(2).
+Libgraphics provides 3D computer graphics through draw(2) and
+memdraw(2).
Still in early stages of research, subject to change, drastically, at
any given time.
-
-It now relies on libobj to manipulate the geometry, so you need to run
-`mk pulldeps` before you build it.
diff --git a/render.c b/render.c
index 5a4f5b6..cc5a201 100644
--- a/render.c
+++ b/render.c
@@ -4,7 +4,6 @@
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
-#include "libobj/obj.h"
#include "graphics.h"
#include "internal.h"
@@ -155,7 +154,7 @@ _barycoords(Triangle2 t, Point2 p)
Point3 v = crossvec3(Vec3(p0p2.x, p0p1.x, pp0.x), Vec3(p0p2.y, p0p1.y, pp0.y));
/* handle degenerate triangles—i.e. the ones where every point lies on the same line */
- if(fabs(v.z) < 1e-5)
+ if(fabs(v.z) < ε1)
return Pt3(-1,-1,-1,1);
return Pt3(1 - (v.x + v.y)/v.z, v.y/v.z, v.x/v.z, 1);
}
@@ -262,7 +261,7 @@ rasterize(Rastertask *task)
/* interpolate z⁻¹ and get actual z */
pcz = flerp(prim->v[0].p.w, prim->v[1].p.w, perc);
- pcz = 1.0/(pcz < 1e-5? 1e-5: pcz);
+ pcz = 1.0/(pcz < ε1? ε1: pcz);
/* perspective-correct attribute interpolation */
perc *= prim->v[0].p.w * pcz;
@@ -314,7 +313,7 @@ discard:
/* interpolate z⁻¹ and get actual z */
pcz = fberp(prim->v[0].p.w, prim->v[1].p.w, prim->v[2].p.w, bc);
- pcz = 1.0/(pcz < 1e-5? 1e-5: pcz);
+ pcz = 1.0/(pcz < ε1? ε1: pcz);
/* perspective-correct attribute interpolation */
bc = modulapt3(bc, Vec3(prim->v[0].p.w*pcz,
diff --git a/scene.c b/scene.c
index 8971c30..1621be2 100644
--- a/scene.c
+++ b/scene.c
@@ -4,7 +4,6 @@
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
-#include "libobj/obj.h"
#include "graphics.h"
#include "internal.h"
@@ -13,7 +12,7 @@ model_addprim(Model *m, Primitive p)
{
m->prims = erealloc(m->prims, ++m->nprims*sizeof(*m->prims));
m->prims[m->nprims-1] = p;
- return 0;
+ return m->nprims-1;
}
static int
@@ -21,7 +20,7 @@ model_addmaterial(Model *m, Material mtl)
{
m->materials = erealloc(m->materials, ++m->nmaterials*sizeof(*m->materials));
m->materials[m->nmaterials-1] = mtl;
- return 0;
+ return m->nmaterials-1;
}
Model *
diff --git a/shadeop.c b/shadeop.c
index c31309e..5d669ef 100644
--- a/shadeop.c
+++ b/shadeop.c
@@ -4,7 +4,6 @@
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
-#include "libobj/obj.h"
#include "graphics.h"
#include "internal.h"
diff --git a/texture.c b/texture.c
index df5089a..260c12e 100644
--- a/texture.c
+++ b/texture.c
@@ -4,7 +4,6 @@
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
-#include "libobj/obj.h"
#include "graphics.h"
#include "internal.h"
diff --git a/util.c b/util.c
index 3ff7323..ff76ab9 100644
--- a/util.c
+++ b/util.c
@@ -4,7 +4,6 @@
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
-#include "libobj/obj.h"
#include "graphics.h"
#include "internal.h"
@@ -52,6 +51,12 @@ maxpt2(Point2 a, Point2 b)
};
}
+int
+eqpt2(Point2 a, Point2 b)
+{
+ return vec2len(subpt2(a, b)) < ε2;
+}
+
Point3
modulapt3(Point3 a, Point3 b)
{
@@ -80,6 +85,12 @@ maxpt3(Point3 a, Point3 b)
};
}
+int
+eqpt3(Point3 a, Point3 b)
+{
+ return vec3len(subpt3(a, b)) < ε2;
+}
+
void
memsetf(void *dp, float v, usize len)
{
diff --git a/vertex.c b/vertex.c
index 98f2ec5..715a4c2 100644
--- a/vertex.c
+++ b/vertex.c
@@ -4,7 +4,6 @@
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
-#include "libobj/obj.h"
#include "graphics.h"
#include "internal.h"
diff --git a/viewport.c b/viewport.c
index 02987e2..c12e14f 100644
--- a/viewport.c
+++ b/viewport.c
@@ -4,7 +4,6 @@
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
-#include "libobj/obj.h"
#include "graphics.h"
#include "internal.h"
diff --git a/xform.c b/xform.c
index 0efbbe3..60bc77b 100644
--- a/xform.c
+++ b/xform.c
@@ -4,7 +4,6 @@
#include <draw.h>
#include <memdraw.h>
#include <geometry.h>
-#include "libobj/obj.h"
#include "graphics.h"
#include "internal.h"