first pass at the obj loader
[cyberay] / src / mesh.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <ctype.h>
4 #include <cgmath/cgmath.h>
5 #include "mesh.h"
6
7 struct facevertex {
8         int vidx, tidx, nidx;
9 };
10
11 static char *cleanline(char *s)
12 {
13         char *ptr;
14
15         if((ptr = strchr(s, '#'))) *ptr = 0;
16
17         while(*s && isspace(*s)) s++;
18         ptr = s + strlen(s) - 1;
19         while(ptr >= s && isspace(*s)) *ptr-- = 0;
20
21         return *s ? s : 0;
22 }
23
24 static char *parse_idx(char *ptr, int *idx, int arrsz)
25 {
26         char *endp;
27         int val = strtol(ptr, &endp, 10);
28         if(endp == ptr) return 0;
29
30         if(val < 0) {   /* convert negative indices */
31                 *idx = arrsz + val;
32         } else {
33                 *idx = val - 1; /* indices in obj are 1-based */
34         }
35         return endp;
36 }
37
38 /* possible face-vertex definitions:
39  * 1. vertex
40  * 2. vertex/texcoord
41  * 3. vertex//normal
42  * 4. vertex/texcoord/normal
43  */
44 static char *parse_face_vert(char *ptr, struct facevertex *fv, int numv, int numt, int numn)
45 {
46         if(!(ptr = parse_idx(ptr, &fv->vidx, numv)))
47                 return 0;
48         if(*ptr != '/') return (!*ptr || isspace(*ptr)) ? ptr : 0;
49
50         if(*++ptr == '/') {     /* no texcoord */
51                 fv->tidx = -1;
52                 ++ptr;
53         } else {
54                 if(!(ptr = parse_idx(ptr, &fv->tidx, numt)))
55                         return 0;
56                 if(*ptr != '/') return (!*ptr || isspace(*ptr)) ? ptr : 0;
57                 ++ptr;
58         }
59
60         if(!(ptr = parse_idx(ptr, &fv->nidx, numn)))
61                 return 0;
62         return (!*ptr || isspace(*ptr)) ? ptr : 0;
63 }
64
65
66 #define APPEND(prefix)  \
67         do { \
68                 if(prefix##count >= prefix##max) { \
69                         int newsz = prefix##max ? prefix##max * 2 : 8; \
70                         void *ptr = realloc(prefix##arr, newsz * sizeof(cgm_vec3)); \
71                         if(!ptr) { \
72                                 fprintf(stderr, "load_mesh: failed to resize array to %d elements\n", newsz); \
73                                 return -1; \
74                         } \
75                         prefix##arr = ptr; \
76                         prefix##max = newsz; \
77                 } \
78         } while(0)
79
80
81 int load_mesh(struct mesh *m, const char *fname)
82 {
83         int i, num, nline, sidx, didx, res = -1;
84         FILE *fp;
85         cgm_vec3 v, va, vb, fnorm;
86         cgm_vec3 *varr, *narr, *tarr;
87         int vcount, ncount, tcount, vmax, nmax, tmax, max_faces, newsz;
88         char linebuf[256], *line, *ptr;
89         struct facevertex fv[4];
90         struct triangle *tri;
91         void *tmpptr;
92         static const int quadidx[] = { 0, 1, 2, 0, 1, 3 };
93
94         varr = narr = tarr = 0;
95         vcount = ncount = tcount = vmax = nmax = tmax = 0;
96
97         m->faces = 0;
98         m->num_faces = 0;
99         max_faces = 0;
100
101         if(!(fp = fopen(fname, "rb"))) {
102                 fprintf(stderr, "load_mesh: failed to open: %s\n", fname);
103                 return -1;
104         }
105
106         nline = 0;
107         while(fgets(linebuf, sizeof linebuf, fp)) {
108                 nline++;
109                 if(!(line = cleanline(linebuf))) {
110                         continue;
111                 }
112
113                 switch(line[0]) {
114                 case 'v':
115                         v.y = v.z = 0;
116                         if((num = sscanf(line + 2, "%f %f %f", &v.x, &v.y, &v.z)) < 2) {
117 verr:                   fprintf(stderr, "load_mesh: ignoring malformed attribute at %d: %s\n", nline, line);
118                                 continue;
119                         }
120                         if(isspace(line[1])) {
121                                 APPEND(v);
122                                 varr[vcount++] = v;
123                         } else if(line[1] == 'n' && isspace(line[2])) {
124                                 APPEND(n);
125                                 narr[ncount++] = v;
126                         } else if(line[1] == 't' && isspace(line[2])) {
127                                 APPEND(t);
128                                 tarr[tcount].x = v.x;
129                                 tarr[tcount++].y = v.y;
130                         } else {
131                                 goto verr;
132                         }
133                         break;
134
135                 case 'f':
136                         if(!isspace(line[1])) {
137                                 break;
138                         }
139                         ptr = line + 2;
140                         for(i=0; i<4; i++) {
141                                 fv[i].nidx = fv[i].tidx = -1;
142                                 if(!(ptr = parse_face_vert(ptr, fv + i, vcount, tcount, ncount))) {
143                                         break;
144                                 }
145                         }
146
147                         if(i < 2) {
148                                 fprintf(stderr, "load_mesh: invalid face definition at %d: %s\n", nline, line);
149                                 break;
150                         }
151
152                         va = varr[fv[1].vidx];
153                         cgm_vsub(&va, varr + fv[0].vidx);
154                         vb = varr[fv[2].vidx];
155                         cgm_vsub(&vb, varr + fv[0].vidx);
156                         cgm_vcross(&fnorm, &va, &vb);
157                         cgm_vnormalize(&fnorm);
158
159                         if(m->num_faces >= max_faces - 1) {
160                                 newsz = max_faces ? max_faces * 2 : 16;
161                                 if(!(tmpptr = realloc(m->faces, newsz * sizeof *m->faces))) {
162                                         fprintf(stderr, "load_mesh: failed to resize faces array to %d\n", newsz);
163                                         goto end;
164                                 }
165                                 m->faces = tmpptr;
166                                 max_faces = newsz;
167                         }
168
169                         num = i > 3 ? 6 : 3;
170                         for(i=0; i<num; i++) {
171                                 if(i % 3 == 0) {
172                                         tri = m->faces + m->num_faces++;
173                                         tri->norm = fnorm;
174                                         tri->mtl = &m->mtl;
175                                 }
176                                 sidx = quadidx[i];
177                                 didx = i >= 3 ? i - 3 : i;
178                                 tri->v[didx].pos = varr[fv[sidx].vidx];
179                                 tri->v[didx].norm = fv[sidx].nidx >= 0 ? varr[fv[sidx].nidx] : fnorm;
180                                 if(fv[sidx].tidx >= 0) {
181                                         tri->v[didx].tex.x = tarr[fv[sidx].tidx].x;
182                                         tri->v[didx].tex.y = tarr[fv[sidx].tidx].y;
183                                 } else {
184                                         tri->v[didx].tex.x = tri->v[sidx].tex.y = 0;
185                                 }
186                         }
187                         break;
188                 }
189
190         }
191
192         res = 0;
193 end:
194         free(varr);
195         free(narr);
196         free(tarr);
197         fclose(fp);
198         return res;
199 }
200
201 void destroy_mesh(struct mesh *m)
202 {
203         free(m->faces);
204         m->faces = 0;
205 }
206
207 void draw_mesh(struct mesh *m)
208 {
209         int i;
210
211         glBegin(GL_TRIANGLES);
212         for(i=0; i<m->num_faces; i++) {
213                 glNormal3fv(&m->faces[i].v[0].norm);
214                 glVertex3fv(&m->faces[i].v[0].pos);
215
216                 glNormal3fv(&m->faces[i].v[1].norm);
217                 glVertex3fv(&m->faces[i].v[1].pos);
218
219                 glNormal3fv(&m->faces[i].v[2].norm);
220                 glVertex3fv(&m->faces[i].v[2].pos);
221         }
222         glEnd();
223 }