shithub: libobj

Download patch

ref: b4a0229a2dadca8c4d3a94d3e7176d022f09ec82
parent: fa3f3d5395f9bf04b3df4f695a43dbfb0655c577
author: rodri <rgl@antares-labs.eu>
date: Sun Feb 18 09:45:08 EST 2024

added support for material (.mtl) files.

--- a/obj.c
+++ b/obj.c
@@ -15,6 +15,7 @@
 };
 
 static Line curline;
+static Line curmtline;
 
 static void
 error(char *fmt, ...)
@@ -29,6 +30,19 @@
 	werrstr("%s\n", buf);
 }
 
+static void
+mterror(char *fmt, ...)
+{
+	va_list va;
+	char buf[ERRMAX], *bp;
+
+	va_start(va, fmt);
+	bp = seprint(buf, buf + sizeof buf, "%s:%lud ", curmtline.file, curmtline.lineno);
+	vseprint(bp, buf + sizeof buf, fmt, va);
+	va_end(va);
+	werrstr("%s\n", buf);
+}
+
 static void *
 emalloc(ulong n)
 {
@@ -54,6 +68,17 @@
 	return nv;
 }
 
+static char *
+estrdup(char *s)
+{
+	char *ns;
+
+	ns = strdup(s);
+	if(ns == nil)
+		sysfatal("strdup: %r");
+	return ns;
+}
+
 static int
 max(int a, int b)
 {
@@ -102,6 +127,7 @@
 
 	e = emalloc(sizeof(OBJElem));
 	e->type = t;
+	e->mtl = nil;
 	return e;
 }
 
@@ -131,7 +157,7 @@
 	OBJObject *o;
 
 	o = emalloc(sizeof(OBJObject));
-	o->name = strdup(n);
+	o->name = estrdup(n);
 	return o;
 }
 
@@ -182,6 +208,205 @@
 	return o;
 }
 
+static OBJMaterial *
+allocmt(char *name)
+{
+	OBJMaterial *m;
+
+	m = emalloc(sizeof *m);
+	memset(m, 0, sizeof *m);
+	m->name = estrdup(name);
+	return m;
+}
+
+static void
+freemt(OBJMaterial *m)
+{
+	free(m->name);
+	free(m);
+}
+
+static OBJMaterlist *
+allocmtl(char *file)
+{
+	OBJMaterlist *ml;
+
+	ml = emalloc(sizeof *ml);
+	memset(ml, 0, sizeof *ml);
+	ml->filename = estrdup(file);
+	return ml;
+}
+
+static void
+addmtl(OBJMaterlist *ml, OBJMaterial *m)
+{
+	OBJMaterial *mp, *prev;
+	uint h;
+
+	prev = nil;
+	h = hash(m->name);
+	for(mp = ml->mattab[h]; mp != nil; prev = mp, mp = mp->next)
+		if(strcmp(mp->name, m->name) == 0){
+			m->next = mp->next;
+			freemt(mp);
+			break;
+		}
+	if(prev == nil){
+		ml->mattab[h] = m;
+		return;
+	}
+	prev->next = m;
+}
+
+static OBJMaterial *
+getmtl(OBJMaterlist *ml, char *name)
+{
+	OBJMaterial *m;
+	uint h;
+
+	h = hash(name);
+	for(m = ml->mattab[h]; m != nil; m = m->next)
+		if(strcmp(m->name, name) == 0)
+			return m;
+	return nil;
+}
+
+OBJMaterlist *
+objmtlparse(char *file)
+{
+	OBJMaterlist *ml;
+	OBJMaterial *m;
+	Biobuf *bin;
+	char *line, *f[10], *p, buf[128];
+	int nf;
+
+	if((p = strrchr(curline.file, '/')) != nil)
+		snprint(buf, sizeof buf, "%.*s/%s", p-curline.file, curline.file, file);
+
+	bin = Bopen(buf, OREAD);
+	if(bin == nil)
+		sysfatal("Bopen: %r");
+
+	ml = allocmtl(file);
+	m = nil;
+	curmtline.file = file;
+	curmtline.lineno = 0;
+
+	while((line = Brdline(bin, '\n')) != nil){
+		curmtline.lineno++;
+		line[Blinelen(bin)-1] = 0;
+
+		nf = tokenize(line, f, nelem(f));
+		if(nf == 2 && strcmp(f[0], "newmtl") == 0){
+			m = allocmt(f[1]);
+			addmtl(ml, m);
+		}
+		if((nf == 2 || nf == 4) && strcmp(f[0], "Ka") == 0){
+			if(m == nil){
+				mterror("no material found");
+				goto error;
+			}
+			if(nf == 2)
+				m->Ka.r = m->Ka.g = m->Ka.b = strtod(f[1], nil);
+			else{
+				m->Ka.r = strtod(f[1], nil);
+				m->Ka.g = strtod(f[2], nil);
+				m->Ka.b = strtod(f[3], nil);
+			}
+		}
+		if((nf == 2 || nf == 4) && strcmp(f[0], "Kd") == 0){
+			if(m == nil){
+				mterror("no material found");
+				goto error;
+			}
+			if(nf == 2)
+				m->Kd.r = m->Kd.g = m->Kd.b = strtod(f[1], nil);
+			else{
+				m->Kd.r = strtod(f[1], nil);
+				m->Kd.g = strtod(f[2], nil);
+				m->Kd.b = strtod(f[3], nil);
+			}
+		}
+		if((nf == 2 || nf == 4) && strcmp(f[0], "Ks") == 0){
+			if(m == nil){
+				mterror("no material found");
+				goto error;
+			}
+			if(nf == 2)
+				m->Ks.r = m->Ks.g = m->Ks.b = strtod(f[1], nil);
+			else{
+				m->Ks.r = strtod(f[1], nil);
+				m->Ks.g = strtod(f[2], nil);
+				m->Ks.b = strtod(f[3], nil);
+			}
+		}
+		if((nf == 2 || nf == 4) && strcmp(f[0], "Ke") == 0){
+			if(m == nil){
+				mterror("no material found");
+				goto error;
+			}
+			if(nf == 2)
+				m->Ke.r = m->Ke.g = m->Ke.b = strtod(f[1], nil);
+			else{
+				m->Ke.r = strtod(f[1], nil);
+				m->Ke.g = strtod(f[2], nil);
+				m->Ke.b = strtod(f[3], nil);
+			}
+		}
+		if(nf == 2 && strcmp(f[0], "Ns") == 0){
+			if(m == nil){
+				mterror("no material found");
+				goto error;
+			}
+			m->Ns = strtod(f[1], nil);
+		}
+		if(nf == 2 && strcmp(f[0], "Ni") == 0){
+			if(m == nil){
+				mterror("no material found");
+				goto error;
+			}
+			m->Ni = strtod(f[1], nil);
+		}
+		if(nf == 2 && strcmp(f[0], "d") == 0){
+			if(m == nil){
+				mterror("no material found");
+				goto error;
+			}
+			m->d = strtod(f[1], nil);
+		}
+		if(nf == 2 && strcmp(f[0], "illum") == 0){
+			if(m == nil){
+				mterror("no material found");
+				goto error;
+			}
+			m->illum = strtol(f[1], nil, 10);
+		}
+	}
+	Bterm(bin);
+	return ml;
+error:
+	objmtlfree(ml);
+	Bterm(bin);
+	return nil;
+}
+
+void
+objmtlfree(OBJMaterlist *ml)
+{
+	OBJMaterial *m, *nm;
+	int i;
+
+	if(ml == nil)
+		return;
+	for(i = 0; i < nelem(ml->mattab); i++)
+		for(m = ml->mattab[i]; m != nil; m = nm){
+			nm = m->next;
+			freemt(m);
+		}
+	free(ml->filename);
+	free(ml);
+}
+
 OBJ *
 objparse(char *file)
 {
@@ -188,6 +413,7 @@
 	Biobuf *bin;
 	OBJ *obj;
 	OBJObject *o;
+	OBJMaterial *m;
 	OBJElem *e;
 	OBJVertex v;
 	double *d;
@@ -194,6 +420,7 @@
 	char c, buf[256], *p;
 	int vtype, idxtab, idx, sign;
 
+	m = nil;
 	o = nil;
 	bin = Bopen(file, OREAD);
 	if(bin == nil)
@@ -480,6 +707,8 @@
 				o = alloco("default");
 				pusho(obj, o);
 			}
+			if(m != nil)
+				e->mtl = m;
 			addelem(o, e);
 			break;
 		case 'm':
@@ -489,7 +718,31 @@
 				*p++ = c;
 			}while(c = Bgetc(bin), isalpha(c) && p-buf < sizeof(buf)-1);
 			*p = 0;
-			if(strcmp(buf, "mtllib") != 0 && strcmp(buf, "usemtl") != 0){
+			if(strcmp(buf, "mtllib") == 0){
+				while(isspace(c))
+					c = Bgetc(bin);
+				p = buf;
+				do{
+					*p++ = c;
+				}while(c = Bgetc(bin), (isalnum(c) || c == '.' || c == '_') && p-buf < sizeof(buf)-1);
+				*p = 0;
+				if((obj->materials = objmtlparse(buf)) == nil){
+					error("objmtlparse: %r");
+					goto error;
+				}
+			}else if(strcmp(buf, "usemtl") == 0){
+				while(isspace(c))
+					c = Bgetc(bin);
+				p = buf;
+				do{
+					*p++ = c;
+				}while(c = Bgetc(bin), (isalnum(c) || c == '.' || c == '_') && p-buf < sizeof(buf)-1);
+				*p = 0;
+				if((m = getmtl(obj->materials, buf)) == nil){
+					error("no material '%s' found", buf);
+					goto error;
+				}
+			}else{
 				error("syntax error");
 				goto error;
 			}
@@ -528,6 +781,8 @@
 
 	if(obj == nil)
 		return;
+	if(obj->materials != nil)
+		objmtlfree(obj->materials);
 	for(i = 0; i < nelem(obj->vertdata); i++)
 		free(obj->vertdata[i].verts);
 	for(i = 0; i < nelem(obj->objtab); i++)
@@ -539,6 +794,33 @@
 }
 
 int
+OBJMaterlistfmt(Fmt *f)
+{
+	OBJMaterlist *ml;
+	OBJMaterial *m;
+	int n, i;
+
+	n = 0;
+	ml = va_arg(f->args, OBJMaterlist*);
+
+	for(i = 0; i < nelem(ml->mattab); i++)
+		for(m = ml->mattab[i]; m != nil; m = m->next){
+			n += fmtprint(f, "newmtl %s\n", m->name);
+			n += fmtprint(f, "Ka %g %g %g\n", m->Ka.r, m->Ka.g, m->Ka.b);
+			n += fmtprint(f, "Kd %g %g %g\n", m->Kd.r, m->Kd.g, m->Kd.b);
+			n += fmtprint(f, "Ks %g %g %g\n", m->Ks.r, m->Ks.g, m->Ks.b);
+			n += fmtprint(f, "Ke %g %g %g\n", m->Ke.r, m->Ke.g, m->Ke.b);
+			n += fmtprint(f, "Ns %g\n", m->Ns);
+			n += fmtprint(f, "Ni %g\n", m->Ni);
+			n += fmtprint(f, "d %g\n", m->d);
+			n += fmtprint(f, "illum %d\n", m->illum);
+			n += fmtprint(f, "\n");
+		}
+
+	return n;
+}
+
+int
 OBJfmt(Fmt *f)
 {
 	OBJ *obj;
@@ -567,6 +849,8 @@
 				break;
 			}
 		}
+	if(obj->materials != nil)
+		n += fmtprint(f, "mtllib %s\n", obj->materials->filename);
 	for(i = 0; i < nelem(obj->objtab); i++)
 		for(o = obj->objtab[i]; o != nil; o = o->next){
 			if(strcmp(o->name, "default") != 0)
@@ -582,6 +866,8 @@
 					n += fmtprint(f, "l");
 					break;
 				case OBJEFace:
+					if(e->mtl != nil)
+						n += fmtprint(f, "usemtl %s\n", e->mtl->name);
 					n += fmtprint(f, "f");
 					break;
 				//case OBJECurve:
--- a/obj.h
+++ b/obj.h
@@ -27,8 +27,11 @@
 };
 
 typedef union OBJVertex OBJVertex;
+typedef struct OBJColor OBJColor;
 typedef struct OBJVertexArray OBJVertexArray;
 typedef struct OBJIndexArray OBJIndexArray;
+typedef struct OBJMaterial OBJMaterial;
+typedef struct OBJMaterlist OBJMaterlist;
 typedef struct OBJElem OBJElem;
 //typedef struct OBJGroup OBJGroup;
 typedef struct OBJObject OBJObject;
@@ -43,6 +46,11 @@
 	struct { double i, j, k; };	/* normal */
 };
 
+struct OBJColor
+{
+	double r, g, b;
+};
+
 struct OBJVertexArray
 {
 	OBJVertex *verts;
@@ -55,10 +63,32 @@
 	int nindex;
 };
 
+struct OBJMaterial
+{
+	char *name;
+	OBJColor Ka;		/* ambient color */
+	OBJColor Kd;		/* diffuse color */
+	OBJColor Ks;		/* specular color */
+	OBJColor Ke;		/* emissive color */
+	double Ns;		/* specular highlight */
+	double Ni;		/* index of refraction */
+	double d;		/* dissolution factor */
+	int illum;		/* illumination model */
+	double map_Kd;		/* color texture file */
+	OBJMaterial *next;
+};
+
+struct OBJMaterlist
+{
+	char *filename;
+	OBJMaterial *mattab[OBJHTSIZE];
+};
+
 struct OBJElem
 {
 	OBJIndexArray indextab[OBJNVERT];
 	int type;
+	OBJMaterial *mtl;
 	OBJElem *next;
 };
 
@@ -88,10 +118,14 @@
 {
 	OBJVertexArray vertdata[OBJNVERT];
 	OBJObject *objtab[OBJHTSIZE];
+	OBJMaterlist *materials;
 };
 
 OBJ *objparse(char*);
 void objfree(OBJ*);
+OBJMaterlist *objmtlparse(char*);
+void objmtlfree(OBJMaterlist*);
 
+int OBJMaterlistfmt(Fmt*);
 int OBJfmt(Fmt*);
 void OBJfmtinstall(void);
--- a/spec
+++ b/spec
@@ -1,2 +1,5 @@
 http://paulbourke.net/dataformats/obj
 https://people.sc.fsu.edu/~jburkardt/data/obj/obj.html
+https://paulbourke.net/dataformats/mtl/
+https://www.loc.gov/preservation/digital/formats/fdd/fdd000508.shtml
+https://people.computing.clemson.edu/~dhouse/courses/405/docs/brief-obj-file-format.html