better obj loading
[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 struct objmtl {
12         char name[64];
13         cgm_vec3 ka, kd, ks;
14         float shin;
15         float alpha;
16         float ior;
17         struct objmtl *next;
18 };
19
20 static void calc_face_normal(struct triangle *tri);
21 static char *cleanline(char *s);
22 static char *parse_idx(char *ptr, int *idx, int arrsz);
23 static char *parse_face_vert(char *ptr, struct facevertex *fv, int numv, int numt, int numn);
24
25 static struct objmtl *load_mtllib(const char *objfname, const char *mtlfname);
26 static void free_mtllist(struct objmtl *mtl);
27 static void conv_mtl(struct material *mm, struct objmtl *om);
28
29 #define GROW_ARRAY(arr, sz)     \
30         do { \
31                 int newsz = (sz) ? (sz) * 2 : 16; \
32                 void *tmp = realloc(arr, newsz * sizeof *(arr)); \
33                 if(!tmp) { \
34                         fprintf(stderr, "failed to grow array to %d\n", newsz); \
35                         goto fail; \
36                 } \
37                 arr = tmp; \
38                 sz = newsz; \
39         } while(0)
40
41
42 int load_scenefile(struct scenefile *scn, const char *fname)
43 {
44         int i, nlines, total_faces = 0, res = -1;
45         FILE *fp;
46         char buf[256], *line, *ptr;
47         int varr_size, varr_max, narr_size, narr_max, tarr_size, tarr_max, max_faces;
48         cgm_vec3 v, *varr = 0, *narr = 0;
49         cgm_vec2 *tarr = 0;
50         struct facevertex fv[4];
51         int numfv;
52         struct mesh *mesh;
53         struct triangle *tri;
54         static const cgm_vec2 def_tc = {0, 0};
55         struct objmtl curmtl, *mtl, *mtllist = 0;
56
57
58         varr_size = varr_max = narr_size = narr_max = tarr_size = tarr_max = 0;
59         varr = narr = 0;
60         tarr = 0;
61
62         if(!(fp = fopen(fname, "rb"))) {
63                 fprintf(stderr, "load_scenefile: failed to open %s\n", fname);
64                 return -1;
65         }
66
67         if(!(mesh = calloc(1, sizeof *mesh))) {
68                 fprintf(stderr, "failed to allocate mesh\n");
69                 fclose(fp);
70                 return -1;
71         }
72         max_faces = 0;
73
74         scn->meshlist = 0;
75         scn->num_meshes = 0;
76
77         /* default material: white diffuse */
78         memset(&curmtl, 0, sizeof curmtl);
79         cgm_vcons(&curmtl.kd, 1.0f, 1.0f, 1.0f);
80         curmtl.alpha = curmtl.ior = 1.0f;
81
82         nlines = 0;
83         while(fgets(buf, sizeof buf, fp)) {
84                 nlines++;
85                 if(!(line = cleanline(buf))) {
86                         continue;
87                 }
88
89                 switch(line[0]) {
90                 case 'v':
91                         v.x = v.y = v.z = 0.0f;
92                         if(sscanf(line + 2, "%f %f %f", &v.x, &v.y, &v.z) < 2) {
93                                 break;
94                         }
95                         if(isspace(line[1])) {
96                                 if(varr_size >= varr_max) {
97                                         GROW_ARRAY(varr, varr_max);
98                                 }
99                                 varr[varr_size++] = v;
100                         } else if(line[1] == 't' && isspace(line[2])) {
101                                 if(tarr_size >= tarr_max) {
102                                         GROW_ARRAY(tarr, tarr_max);
103                                 }
104                                 tarr[tarr_size++] = *(cgm_vec2*)&v;
105                         } else if(line[1] == 'n' && isspace(line[2])) {
106                                 if(narr_size >= narr_max) {
107                                         GROW_ARRAY(narr, narr_max);
108                                 }
109                                 narr[narr_size++] = v;
110                         }
111                         break;
112
113                 case 'f':
114                         if(!isspace(line[1])) break;
115
116                         ptr = line + 2;
117
118                         numfv = 0;
119                         for(i=0; i<4; i++) {
120                                 if(!(ptr = parse_face_vert(ptr, fv + i, varr_size, tarr_size, narr_size))) {
121                                         break;
122                                 }
123                                 numfv++;
124                         }
125                         if(numfv < 3) break;
126
127                         if(mesh->num_faces >= max_faces - 1) {
128                                 GROW_ARRAY(mesh->faces, max_faces);
129                         }
130                         tri = mesh->faces + mesh->num_faces++;
131                         tri->mtl = &mesh->mtl;
132
133                         tri->v[0].pos = varr[fv[0].vidx];
134                         tri->v[1].pos = varr[fv[1].vidx];
135                         tri->v[2].pos = varr[fv[2].vidx];
136                         calc_face_normal(tri);
137                         for(i=0; i<3; i++) {
138                                 tri->v[i].norm = fv[i].nidx >= 0 ? narr[fv[i].nidx] : tri->norm;
139                                 tri->v[i].tex = fv[i].tidx >= 0 ? tarr[fv[i].tidx] : def_tc;
140                         }
141
142                         if(numfv > 3) {
143                                 tri++;
144                                 mesh->num_faces++;
145                                 tri->mtl = &mesh->mtl;
146                                 tri->norm = tri[-1].norm;
147                                 tri->v[0] = tri[-1].v[0];
148                                 tri->v[1] = tri[-1].v[1];
149
150                                 tri->v[2].pos = varr[fv[3].vidx];
151                                 tri->v[2].norm = fv[3].nidx >= 0 ? narr[fv[3].nidx] : tri->norm;
152                                 tri->v[2].tex = fv[3].tidx >= 0 ? tarr[fv[3].tidx] : def_tc;
153                         }
154                         break;
155
156                 case 'o':
157                 case 'g':
158                         if(mesh->num_faces) {
159                                 conv_mtl(&mesh->mtl, &curmtl);
160                                 total_faces += mesh->num_faces;
161                                 mesh->next = scn->meshlist;
162                                 scn->meshlist = mesh;
163                                 scn->num_meshes++;
164
165                                 printf("added mesh with mtl: %s\n", curmtl.name);
166
167                                 if(!(mesh = calloc(1, sizeof *mesh))) {
168                                         fprintf(stderr, "failed to allocate mesh\n");
169                                         goto fail;
170                                 }
171                                 max_faces = 0;
172                         }
173                         break;
174
175                 case 'm':
176                         if(memcmp(line, "mtllib", 6) == 0 && (line = cleanline(line + 6))) {
177                                 free_mtllist(mtllist);
178                                 mtllist = load_mtllib(fname, line);
179                         }
180                         break;
181
182                 case 'u':
183                         if(memcmp(line, "usemtl", 6) == 0 && (line = cleanline(line + 6))) {
184                                 mtl = mtllist;
185                                 while(mtl) {
186                                         if(strcmp(mtl->name, line) == 0) {
187                                                 curmtl = *mtl;
188                                                 break;
189                                         }
190                                         mtl = mtl->next;
191                                 }
192                         }
193                         break;
194
195                 default:
196                         break;
197                 }
198         }
199
200         if(mesh->num_faces) {
201                 conv_mtl(&mesh->mtl, &curmtl);
202                 total_faces += mesh->num_faces;
203                 mesh->next = scn->meshlist;
204                 scn->meshlist = mesh;
205                 scn->num_meshes++;
206
207                 printf("added mesh with mtl: %s\n", curmtl.name);
208         } else {
209                 free(mesh);
210         }
211         mesh = 0;
212
213         printf("load_scenefile: loaded %d meshes, %d vertices, %d triangles\n", scn->num_meshes,
214                         varr_size, total_faces);
215
216         res = 0;
217
218 fail:
219         fclose(fp);
220         free(mesh);
221         free(varr);
222         free(narr);
223         free(tarr);
224         free_mtllist(mtllist);
225         return res;
226 }
227
228 void destroy_scenefile(struct scenefile *scn)
229 {
230         struct mesh *m;
231         while(scn->meshlist) {
232                 m = scn->meshlist;
233                 scn->meshlist = scn->meshlist->next;
234                 free(m);
235         }
236 }
237
238 void destroy_mesh(struct mesh *m)
239 {
240         free(m->faces);
241         m->faces = 0;
242 }
243
244 void draw_mesh(struct mesh *m)
245 {
246         int i;
247
248         glBegin(GL_TRIANGLES);
249         for(i=0; i<m->num_faces; i++) {
250                 glNormal3fv((float*)&m->faces[i].v[0].norm);
251                 glVertex3fv((float*)&m->faces[i].v[0].pos);
252
253                 glNormal3fv((float*)&m->faces[i].v[1].norm);
254                 glVertex3fv((float*)&m->faces[i].v[1].pos);
255
256                 glNormal3fv((float*)&m->faces[i].v[2].norm);
257                 glVertex3fv((float*)&m->faces[i].v[2].pos);
258         }
259         glEnd();
260 }
261
262 static void calc_face_normal(struct triangle *tri)
263 {
264         cgm_vec3 va, vb;
265
266         va = tri->v[1].pos;
267         cgm_vsub(&va, &tri->v[0].pos);
268         vb = tri->v[2].pos;
269         cgm_vsub(&vb, &tri->v[0].pos);
270
271         cgm_vcross(&tri->norm, &va, &vb);
272         cgm_vnormalize(&tri->norm);
273 }
274
275 static char *cleanline(char *s)
276 {
277         char *ptr;
278
279         if((ptr = strchr(s, '#'))) *ptr = 0;
280
281         while(*s && isspace(*s)) s++;
282         ptr = s + strlen(s) - 1;
283         while(ptr >= s && isspace(*ptr)) *ptr-- = 0;
284
285         return *s ? s : 0;
286 }
287
288 static char *parse_idx(char *ptr, int *idx, int arrsz)
289 {
290         char *endp;
291         int val = strtol(ptr, &endp, 10);
292         if(endp == ptr) return 0;
293
294         if(val < 0) {   /* convert negative indices */
295                 *idx = arrsz + val;
296         } else {
297                 *idx = val - 1; /* indices in obj are 1-based */
298         }
299         return endp;
300 }
301
302 /* possible face-vertex definitions:
303  * 1. vertex
304  * 2. vertex/texcoord
305  * 3. vertex//normal
306  * 4. vertex/texcoord/normal
307  */
308 static char *parse_face_vert(char *ptr, struct facevertex *fv, int numv, int numt, int numn)
309 {
310         fv->tidx = fv->nidx = -1;
311
312         if(!(ptr = parse_idx(ptr, &fv->vidx, numv)))
313                 return 0;
314         if(*ptr != '/') return (!*ptr || isspace(*ptr)) ? ptr : 0;
315
316         if(*++ptr == '/') {     /* no texcoord */
317                 ++ptr;
318         } else {
319                 if(!(ptr = parse_idx(ptr, &fv->tidx, numt)))
320                         return 0;
321                 if(*ptr != '/') return (!*ptr || isspace(*ptr)) ? ptr : 0;
322                 ++ptr;
323         }
324
325         if(!(ptr = parse_idx(ptr, &fv->nidx, numn)))
326                 return 0;
327         return (!*ptr || isspace(*ptr)) ? ptr : 0;
328 }
329
330 static struct objmtl *load_mtllib(const char *objfname, const char *mtlfname)
331 {
332         FILE *fp;
333         char *sep;
334         char buf[256], *line;
335         struct objmtl *mlist = 0, *m = 0;
336
337         strcpy(buf, objfname);
338         if((sep = strrchr(buf, '/'))) {
339                 sep[1] = 0;
340         } else {
341                 buf[0] = 0;
342         }
343         strcat(buf, mtlfname);
344
345         if(!(fp = fopen(buf, "rb"))) {
346                 return 0;
347         }
348
349         while(fgets(buf, sizeof buf, fp)) {
350                 if(!(line = cleanline(buf))) {
351                         continue;
352                 }
353
354                 if(memcmp(line, "newmtl", 6) == 0) {
355                         if(m) {
356                                 m->next = mlist;
357                                 mlist = m;
358                         }
359                         if((m = calloc(1, sizeof *m))) {
360                                 if((line = cleanline(line + 6))) {
361                                         strcpy(m->name, line);
362                                 }
363                         }
364                 } else if(memcmp(line, "Kd", 2) == 0) {
365                         if(m) {
366                                 sscanf(line + 3, "%f %f %f", &m->kd.x, &m->kd.y, &m->kd.z);
367                         }
368                 }
369         }
370
371         if(m) {
372                 m->next = mlist;
373                 mlist = m;
374         }
375
376         fclose(fp);
377         return mlist;
378 }
379
380 static void free_mtllist(struct objmtl *mtl)
381 {
382         while(mtl) {
383                 void *tmp = mtl;
384                 mtl = mtl->next;
385                 free(tmp);
386         }
387 }
388
389 static void conv_mtl(struct material *mm, struct objmtl *om)
390 {
391         mm->color = om->kd;
392         /* TODO */
393 }