mesh, obj loading, sorting, shitty normals...
[dos_low3d] / src / mesh.c
diff --git a/src/mesh.c b/src/mesh.c
new file mode 100644 (file)
index 0000000..f7771d8
--- /dev/null
@@ -0,0 +1,356 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "mesh.h"
+
+static char *parse_facev(char *s, int *vidx, int *nidx, int *tidx,
+               int nverts, int nnorm, int nuv);
+static int resizearr(void **pptr, int *sz, int elemsz);
+static char *clean_line(char *s);
+
+int load_mesh(struct mesh *m, const char *fname)
+{
+       float fx, fy, fz;
+       FILE *fp;
+       char buf[128], *line;
+       struct g3d_vertex *vptr;
+       struct meshface *face;
+       int i, maxverts, maxfaces;
+       int vidx, tidx, nidx;
+
+       memset(m, 0, sizeof *m);
+
+       if(!(fp = fopen(fname, "rb"))) {
+               fprintf(stderr, "failed to open mesh: %s\n", fname);
+               return -1;
+       }
+
+       printf("Loading mesh: %s\n", fname);
+
+       m->nverts = 0;
+       maxverts = 32;
+       if(!(m->varr = malloc(maxverts * sizeof *m->varr))) {
+               fprintf(stderr, "failed to allocate vertex array\n");
+               goto err;
+       }
+
+       m->nfaces = 0;
+       maxfaces = 32;
+       if(!(m->faces = malloc(maxfaces * sizeof *m->faces))) {
+               fprintf(stderr, "failed to allocate mesh face array\n");
+               goto err;
+       }
+
+
+       while(fgets(buf, sizeof buf, fp)) {
+               line = clean_line(buf);
+               if(!*line) continue;
+
+               switch(line[0]) {
+               case 'v':
+                       if(!isspace(line[1])) continue;
+                       if(sscanf(line + 2, "%f %f %f", &fx, &fy, &fz) != 3) {
+                               continue;
+                       }
+                       if(m->nverts >= maxverts) {
+                               if(resizearr(&m->varr, &maxverts, sizeof *m->varr) == -1) {
+                                       goto err;
+                               }
+                       }
+                       vptr = m->varr + m->nverts++;
+                       vptr->x = (int32_t)(fx * 65536.0f);
+                       vptr->y = (int32_t)(fy * 65536.0f);
+                       vptr->z = (int32_t)(fz * 65536.0f);
+                       vptr->w = 0x10000;
+                       putchar('v'); fflush(stdout);
+                       break;
+
+               case 'f':
+                       if(m->nfaces >= maxfaces) {
+                               if(resizearr(&m->faces, &maxfaces, sizeof *m->faces) == -1) {
+                                       goto err;
+                               }
+                       }
+
+                       line += 2;
+                       face = m->faces + m->nfaces;
+                       for(i=0; i<4; i++) {
+                               if(!(line = parse_facev(line, &vidx, &nidx, &tidx, m->nverts, 0, 0))) {
+                                       if(i < 3) {
+                                               fprintf(stderr, "invalid face definition\n");
+                                               goto err;
+                                       }
+                                       break;
+                               }
+                               if(vidx < 0 || vidx >= m->nverts) {
+                                       fprintf(stderr, "invalid face definition, vertex out of range\n");
+                                       goto err;
+                               }
+                               face->vidx[i] = vidx;
+                       }
+                       face->prim = i;
+                       m->nfaces++;
+                       putchar('f'); fflush(stdout);
+                       break;
+
+               default:
+                       break;
+               }
+       }
+
+       calc_face_normals(m);
+
+       for(i=0; i<m->nfaces; i++) {
+               int32_t dot;
+               face = m->faces + i;
+               dot = face->norm.z > 0 ? face->norm.z : 0;
+               face->color = dot >> 8;
+       }
+
+       putchar('\n');
+
+       fclose(fp);
+       return 0;
+
+err:
+       if(fp) fclose(fp);
+       free(m->varr);
+       free(m->faces);
+       return -1;
+}
+
+static char *parse_idx(char *s, int *idx, int arrsz)
+{
+       char *endp;
+       int val = strtol(s, &endp, 10);
+       if(endp == s) return 0;
+
+       if(val < 0) {   /* convert negative indices */
+               *idx = arrsz + val;
+       } else {
+               *idx = val - 1; /* conv to 0-based */
+       }
+       return endp;
+}
+
+static char *parse_facev(char *s, int *vidx, int *nidx, int *tidx,
+               int nverts, int nnorm, int nuv)
+{
+       if(!(s = parse_idx(s, vidx, nverts))) {
+               return 0;
+       }
+       if(*s != '/') return (!*s || isspace(*s)) ? s : 0;
+
+       if(*++s == '/') {       /* no texcoord */
+               *tidx = -1;
+               s++;
+       } else {
+               if(!(s = parse_idx(s, tidx, nuv))) {
+                       return 0;
+               }
+               if(*s != '/') return (!*s || isspace(*s)) ? s : 0;
+               s++;
+       }
+
+       if(!(s = parse_idx(s, nidx, nnorm))) {
+               return 0;
+       }
+       return (!*s || isspace(*s)) ? s : 0;
+}
+
+static char *clean_line(char *s)
+{
+       char *endp;
+
+       while(*s && isspace(*s)) s++;
+
+       if((endp = strchr(s, '#'))) {
+               *endp-- = 0;
+       } else {
+               endp = s + strlen(s) - 1;
+       }
+       while(endp > s && isspace(*endp)) {
+               *endp-- = 0;
+       }
+       return s;
+}
+
+static int resizearr(void **pptr, int *sz, int elemsz)
+{
+       int newsz = *sz ? *sz * 2 : 16;
+       void *newarr;
+
+       if(!(newarr = realloc(*pptr, newsz * elemsz))) {
+               fprintf(stderr, "failed to resize array to %d\n", newsz);
+               return -1;
+       }
+       *pptr = newarr;
+       *sz = newsz;
+       return 0;
+}
+
+
+void destroy_mesh(struct mesh *m)
+{
+       if(!m) return;
+       free(m->varr);
+       free(m->faces);
+}
+
+void flip_mesh_winding(struct mesh *m)
+{
+       int i, j;
+       unsigned short buf[4];
+       struct meshface *face = m->faces;
+
+       for(i=0; i<m->nfaces; i++) {
+               for(j=0; j<face->prim; j++) {
+                       buf[face->prim - j - 1] = face->vidx[j];
+               }
+               for(j=0; j<face->prim; j++) {
+                       face->vidx[j] = buf[j];
+               }
+               face++;
+       }
+}
+
+int conv_nonidx_mesh(struct mesh *m)
+{
+       struct g3d_vertex *varr, *vptr;
+       struct meshface *face;
+       int i, nverts;
+
+       nverts = m->nfaces * 3;
+       if(!(varr = malloc(nverts * sizeof *varr))) {
+               fprintf(stderr, "failed to allocate nonidx vertex array (%d)\n", nverts);
+               return -1;
+       }
+
+       face = m->faces;
+       vptr = varr;
+       for(i=0; i<m->nfaces; i++) {
+               vptr[0] = m->varr[face->vidx[0]];
+               vptr[1] = m->varr[face->vidx[1]];
+               vptr[2] = m->varr[face->vidx[2]];
+               face++;
+               vptr += 3;
+       }
+
+       free(m->varr);
+       m->varr = varr;
+       m->nverts = nverts;
+       return 0;
+}
+
+static float rsqrt(float x)
+{
+       float xhalf = x * 0.5f;
+       int32_t i = *(int32_t*)&x;
+       i = 0x5f3759df - (i >> 1);
+       x = *(float*)&i;
+       x = x * (1.5f - xhalf * x * x);
+       return x;
+}
+
+void calc_face_normals(struct mesh *m)
+{
+       int i;
+       struct meshface *face;
+       struct g3d_vertex *va, *vb, *vc;
+       int32_t ax, ay, az, bx, by, bz, s;
+       float xsq, ysq, zsq;
+
+       face = m->faces;
+       for(i=0; i<m->nfaces; i++) {
+               va = m->varr + face->vidx[0];
+               vb = m->varr + face->vidx[1];
+               vc = m->varr + face->vidx[2];
+
+               ax = (vc->x - va->x) >> 8;
+               ay = (vc->y - va->y) >> 8;
+               az = (vc->z - va->z) >> 8;
+               bx = (vc->x - vb->x) >> 8;
+               by = (vc->y - vb->y) >> 8;
+               bz = (vc->z - vb->z) >> 8;
+
+               face->norm.x = ay * bz - az * by;
+               face->norm.y = az * bx - ax * bz;
+               face->norm.z = ax * by - ay * bx;
+
+               xsq = (float)face->norm.x; xsq *= xsq;
+               ysq = (float)face->norm.y; ysq *= ysq;
+               zsq = (float)face->norm.z; zsq *= zsq;
+               s = (int32_t)(rsqrt(xsq + ysq + zsq) * 256.0f);
+               face->norm.x = (face->norm.x >> 8) * s;
+               face->norm.y = (face->norm.y >> 8) * s;
+               face->norm.z = (face->norm.z >> 8) * s;
+
+               face++;
+       }
+}
+
+static struct mesh *smesh;
+static const int32_t *smat;
+
+static int zsortcmp(const void *a, const void *b)
+{
+       unsigned short idxa, idxb;
+       struct meshface *fa, *fb;
+       struct g3d_vertex *va, *vb;
+       int32_t za, zb;
+
+       fa = smesh->faces + *(unsigned short*)a;
+       fb = smesh->faces + *(unsigned short*)b;
+
+       va = smesh->varr + fa->vidx[0];
+       vb = smesh->varr + fb->vidx[0];
+
+       za = (smat[8] >> 8) * (va->x >> 8) + (smat[9] >> 8) * (va->y >> 8) +
+               (smat[10] >> 8) * (va->z >> 8);
+       zb = (smat[8] >> 8) * (vb->x >> 8) + (smat[9] >> 8) * (vb->y >> 8) +
+               (smat[10] >> 8) * (vb->z >> 8);
+
+       return za - zb;
+}
+
+void sort_mesh(struct mesh *m, const int32_t *mvmat)
+{
+       int i;
+
+       if(!m->zorder) {
+               if(!(m->zorder = malloc(m->nfaces * sizeof *m->zorder))) {
+                       return;
+               }
+               for(i=0; i<m->nfaces; i++) {
+                       m->zorder[i] = i;
+               }
+       }
+
+       smesh = m;
+       smat = mvmat;
+       qsort(m->zorder, m->nfaces, sizeof *m->zorder, zsortcmp);
+}
+
+void draw_mesh(struct mesh *m)
+{
+       int i;
+       struct meshface *face = m->faces;
+       for(i=0; i<m->nfaces; i++) {
+               g3d_color(face->color);
+               g3d_draw_indexed(face->prim, m->varr, face->vidx, face->prim);
+               face++;
+       }
+}
+
+void draw_mesh_zorder(struct mesh *m)
+{
+       int i;
+       struct meshface *face;
+
+       for(i=0; i<m->nfaces; i++) {
+               face = m->faces + m->zorder[i];
+               g3d_color(face->color);
+               g3d_draw_indexed(face->prim, m->varr, face->vidx, face->prim);
+       }
+}