foo
[vrlugburz] / src / scenefile.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <ctype.h>
4 #include <assert.h>
5 #include "cgmath/cgmath.h"
6 #include "scenefile.h"
7 #include "rbtree.h"
8
9 struct facevertex {
10         int vidx, tidx, nidx;
11 };
12
13 struct objmtl {
14         char *name;
15         cgm_vec3 ka, kd, ks, ke;
16         float shin;
17         float alpha;
18         float ior;
19         char *map_kd, *map_ke, *map_alpha;
20         struct objmtl *next;
21 };
22
23 static int proc_facevert(struct mesh *mesh, struct facevertex *fv,
24                 cgm_vec3 *varr, cgm_vec3 *narr, cgm_vec2 *tarr, struct rbtree *rbtree);
25
26 static char *cleanline(char *s);
27 static char *parse_idx(char *ptr, int *idx, int arrsz);
28 static char *parse_face_vert(char *ptr, struct facevertex *fv, int numv, int numt, int numn);
29
30 static int load_mtllib(struct scenefile *scn, const char *path_prefix, const char *mtlfname);
31 static void free_mtllist(struct material *mtl);
32 static void conv_mtl(struct material *mm, struct objmtl *om, const char *path_prefix);
33
34 static int cmp_facevert(const void *ap, const void *bp);
35 static void free_rbnode_key(struct rbnode *n, void *cls);
36
37 #define GROW_ARRAY(arr, sz)     \
38         do { \
39                 int newsz = (sz) ? (sz) * 2 : 16; \
40                 void *tmp = realloc(arr, newsz * sizeof *(arr)); \
41                 if(!tmp) { \
42                         fprintf(stderr, "failed to grow array to %d\n", newsz); \
43                         goto fail; \
44                 } \
45                 arr = tmp; \
46                 sz = newsz; \
47         } while(0)
48
49
50 int load_scenefile(struct scenefile *scn, const char *fname)
51 {
52         int i, nlines, res = -1;
53         FILE *fp;
54         char buf[256], *line, *ptr, *path_prefix;
55         int varr_size, varr_max, narr_size, narr_max, tarr_size, tarr_max;
56         cgm_vec3 v, *varr = 0, *narr = 0;
57         cgm_vec2 *tarr = 0;
58         struct facevertex fv[4];
59         struct mesh *mesh;
60         struct material *mtl = 0;
61         char *sep;
62         struct rbtree *rbtree = 0;
63
64         memset(scn, 0, sizeof *scn);
65
66         varr_size = varr_max = narr_size = narr_max = tarr_size = tarr_max = 0;
67         varr = narr = 0;
68         tarr = 0;
69
70         if(!(fp = fopen(fname, "rb"))) {
71                 fprintf(stderr, "load_scenefile: failed to open %s\n", fname);
72                 return -1;
73         }
74
75         if(!(rbtree = rb_create(cmp_facevert))) {
76                 fprintf(stderr, "load_scenefile: failed to create facevertex search tree\n");
77                 goto fail;
78         }
79         rb_set_delete_func(rbtree, free_rbnode_key, 0);
80
81         strcpy(buf, fname);
82         if((sep = strrchr(buf, '/'))) {
83                 sep[1] = 0;
84         } else {
85                 buf[0] = 0;
86         }
87         path_prefix = alloca(strlen(buf) + 1);
88         strcpy(path_prefix, buf);
89
90         if(sep) {
91                 sep = (char*)fname + (sep - buf);
92         }
93         if(!(scn->fname = strdup(sep ? sep + 1 : fname))) {
94                 fprintf(stderr, "failed to allocate scenefile name buffer\n");
95                 goto fail;
96         }
97
98         if(!(mesh = malloc(sizeof *mesh))) {
99                 fprintf(stderr, "failed to allocate mesh\n");
100                 fclose(fp);
101                 return -1;
102         }
103         init_mesh(mesh);
104
105         scn->meshlist = 0;
106         scn->num_meshes = 0;
107
108         nlines = 0;
109         while(fgets(buf, sizeof buf, fp)) {
110                 nlines++;
111                 if(!(line = cleanline(buf))) {
112                         continue;
113                 }
114
115                 switch(line[0]) {
116                 case 'v':
117                         v.x = v.y = v.z = 0.0f;
118                         if(sscanf(line + 2, "%f %f %f", &v.x, &v.y, &v.z) < 2) {
119                                 break;
120                         }
121                         if(isspace(line[1])) {
122                                 if(varr_size >= varr_max) {
123                                         GROW_ARRAY(varr, varr_max);
124                                 }
125                                 varr[varr_size++] = v;
126                         } else if(line[1] == 't' && isspace(line[2])) {
127                                 if(tarr_size >= tarr_max) {
128                                         GROW_ARRAY(tarr, tarr_max);
129                                 }
130                                 tarr[tarr_size++] = *(cgm_vec2*)&v;
131                         } else if(line[1] == 'n' && isspace(line[2])) {
132                                 if(narr_size >= narr_max) {
133                                         GROW_ARRAY(narr, narr_max);
134                                 }
135                                 narr[narr_size++] = v;
136                         }
137                         break;
138
139                 case 'f':
140                         if(!isspace(line[1])) break;
141
142                         ptr = line + 2;
143
144                         for(i=0; i<3; i++) {
145                                 if(!(ptr = parse_face_vert(ptr, fv + i, varr_size, tarr_size, narr_size))) {
146                                         break;
147                                 }
148                                 if(proc_facevert(mesh, fv + i, varr, narr, tarr, rbtree) == -1) {
149                                         break;
150                                 }
151                         }
152
153                         if(parse_face_vert(ptr, fv + 3, varr_size, tarr_size, narr_size)) {
154                                 proc_facevert(mesh, fv, varr, narr, tarr, rbtree);
155                                 proc_facevert(mesh, fv + 2, varr, narr, tarr, rbtree);
156                                 proc_facevert(mesh, fv + 3, varr, narr, tarr, rbtree);
157                         }
158                         break;
159
160                 case 'o':
161                 case 'g':
162                         if(mesh->num_verts) {
163                                 mesh->mtl = mtl;
164                                 mesh->next = scn->meshlist;
165                                 scn->meshlist = mesh;
166                                 scn->num_meshes++;
167
168                                 if(!(mesh = malloc(sizeof *mesh))) {
169                                         fprintf(stderr, "failed to allocate mesh\n");
170                                         goto fail;
171                                 }
172                                 init_mesh(mesh);
173                                 mesh->name = strdup(cleanline(line + 2));
174                         }
175                         break;
176
177                 case 'm':
178                         if(memcmp(line, "mtllib", 6) == 0 && (line = cleanline(line + 6))) {
179                                 free_mtllist(scn->mtllist);
180                                 load_mtllib(scn, path_prefix, line);
181                         }
182                         break;
183
184                 case 'u':
185                         if(memcmp(line, "usemtl", 6) == 0 && (line = cleanline(line + 6))) {
186                                 mtl = scn->mtllist;
187                                 while(mtl) {
188                                         if(strcmp(mtl->name, line) == 0) {
189                                                 break;
190                                         }
191                                         mtl = mtl->next;
192                                 }
193                         }
194                         break;
195
196                 default:
197                         break;
198                 }
199         }
200
201         if(mesh->num_verts) {
202                 mesh->mtl = mtl;
203                 mesh->next = scn->meshlist;
204                 scn->meshlist = mesh;
205                 scn->num_meshes++;
206         } else {
207                 free(mesh);
208         }
209         mesh = 0;
210
211         printf("load_scenefile %s: loaded %d meshes, %d vertices\n", scn->fname,
212                         scn->num_meshes, varr_size);
213
214         res = 0;
215
216         if(0) {
217 fail:
218                 free(scn->fname);
219         }
220
221         fclose(fp);
222         free(mesh);
223         free(varr);
224         free(narr);
225         free(tarr);
226         rb_free(rbtree);
227         return res;
228 }
229
230 static int proc_facevert(struct mesh *mesh, struct facevertex *fv,
231                 cgm_vec3 *varr, cgm_vec3 *narr, cgm_vec2 *tarr, struct rbtree *rbtree)
232 {
233         struct rbnode *node;
234         unsigned int idx, newidx;
235         struct facevertex *newfv;
236         struct vertex v;
237
238         if((node = rb_find(rbtree, &fv))) {
239                 idx = (unsigned int)node->data;
240                 assert(idx < mesh->num_verts);
241         } else {
242                 newidx = mesh->num_verts;
243
244                 v.pos = varr[fv->vidx];
245                 if(fv->nidx >= 0) {
246                         v.norm = narr[fv->nidx];
247                 }
248                 if(fv->tidx >= 0) {
249                         v.tex = tarr[fv->tidx];
250                 }
251                 add_mesh_vertex(mesh, &v);
252                 add_mesh_index(mesh, newidx);
253         }
254
255         if((newfv = malloc(sizeof *newfv))) {
256                 *newfv = *fv;
257         }
258         if(!newfv || rb_insert(rbtree, newfv, (void*)newidx) == -1) {
259                 fprintf(stderr, "load_scenefile: failed to insert facevertex to rbtree\n");
260                 free(newfv);
261                 return -1;
262         }
263         return 0;
264 }
265
266 void destroy_scenefile(struct scenefile *scn)
267 {
268         struct mesh *m;
269         while(scn->meshlist) {
270                 m = scn->meshlist;
271                 scn->meshlist = scn->meshlist->next;
272                 free(m);
273         }
274 }
275
276 struct mesh *find_mesh_prefix(struct scenefile *scn, const char *prefix)
277 {
278         int len = strlen(prefix);
279         struct mesh *m = scn->meshlist;
280         while(m) {
281                 if(m->name && memcmp(m->name, prefix, len) == 0) {
282                         return m;
283                 }
284                 m = m->next;
285         }
286         return 0;
287 }
288
289 static char *cleanline(char *s)
290 {
291         char *ptr;
292
293         if((ptr = strchr(s, '#'))) *ptr = 0;
294
295         while(*s && isspace(*s)) s++;
296         ptr = s + strlen(s) - 1;
297         while(ptr >= s && isspace(*ptr)) *ptr-- = 0;
298
299         return *s ? s : 0;
300 }
301
302 static char *parse_idx(char *ptr, int *idx, int arrsz)
303 {
304         char *endp;
305         int val = strtol(ptr, &endp, 10);
306         if(endp == ptr) return 0;
307
308         if(val < 0) {   /* convert negative indices */
309                 *idx = arrsz + val;
310         } else {
311                 *idx = val - 1; /* indices in obj are 1-based */
312         }
313         return endp;
314 }
315
316 /* possible face-vertex definitions:
317  * 1. vertex
318  * 2. vertex/texcoord
319  * 3. vertex//normal
320  * 4. vertex/texcoord/normal
321  */
322 static char *parse_face_vert(char *ptr, struct facevertex *fv, int numv, int numt, int numn)
323 {
324         fv->tidx = fv->nidx = -1;
325
326         if(!(ptr = parse_idx(ptr, &fv->vidx, numv)))
327                 return 0;
328         if(*ptr != '/') return (!*ptr || isspace(*ptr)) ? ptr : 0;
329
330         if(*++ptr == '/') {     /* no texcoord */
331                 ++ptr;
332         } else {
333                 if(!(ptr = parse_idx(ptr, &fv->tidx, numt)))
334                         return 0;
335                 if(*ptr != '/') return (!*ptr || isspace(*ptr)) ? ptr : 0;
336                 ++ptr;
337         }
338
339         if(!(ptr = parse_idx(ptr, &fv->nidx, numn)))
340                 return 0;
341         return (!*ptr || isspace(*ptr)) ? ptr : 0;
342 }
343
344 static int load_mtllib(struct scenefile *scn, const char *path_prefix, const char *mtlfname)
345 {
346         FILE *fp;
347         char buf[256], *line;
348         struct objmtl om;
349         struct material *mtl = 0;
350
351         if(path_prefix && *path_prefix) {
352                 sprintf(buf, "%s/%s", path_prefix, mtlfname);
353         } else {
354                 strcpy(buf, mtlfname);
355         }
356
357         if(!(fp = fopen(buf, "rb"))) {
358                 return -1;
359         }
360
361         while(fgets(buf, sizeof buf, fp)) {
362                 if(!(line = cleanline(buf))) {
363                         continue;
364                 }
365
366                 if(memcmp(line, "newmtl", 6) == 0) {
367                         if(mtl) {
368                                 conv_mtl(mtl, &om, path_prefix);
369                                 mtl->next = scn->mtllist;
370                                 scn->mtllist = mtl;
371                         }
372                         mtl = calloc(1, sizeof *mtl);
373
374                         memset(&om, 0, sizeof om);
375
376                         if((line = cleanline(line + 6))) {
377                                 om.name = strdup(line);
378                         }
379
380                 } else if(memcmp(line, "Kd", 2) == 0) {
381                         sscanf(line + 3, "%f %f %f", &om.kd.x, &om.kd.y, &om.kd.z);
382                 } else if(memcmp(line, "Ks", 2) == 0) {
383                         sscanf(line + 3, "%f %f %f", &om.ks.x, &om.ks.y, &om.ks.z);
384                 } else if(memcmp(line, "Ke", 2) == 0) {
385                         sscanf(line + 3, "%f %f %f", &om.ke.x, &om.ke.y, &om.ke.z);
386                 } else if(memcmp(line, "Ni", 2) == 0) {
387                         om.ior = atof(line + 3);
388                 } else if(line[0] == 'd' && isspace(line[1])) {
389                         om.alpha = atof(line + 2);
390                 } else if(memcmp(line, "map_Kd", 6) == 0) {
391                         if((line = cleanline(line + 6))) {
392                                 om.map_kd = strdup(line);
393                         }
394                 } else if(memcmp(line, "map_Ke", 6) == 0) {
395                         if((line = cleanline(line + 6))) {
396                                 om.map_ke = strdup(line);
397                         }
398                 } else if(memcmp(line, "map_d", 5) == 0) {
399                         if((line = cleanline(line + 5))) {
400                                 om.map_alpha = strdup(line);
401                         }
402                 }
403         }
404
405         if(mtl) {
406                 conv_mtl(mtl, &om, path_prefix);
407                 mtl->next = scn->mtllist;
408                 scn->mtllist = mtl;
409         }
410
411         fclose(fp);
412         return 0;
413 }
414
415 static void free_mtllist(struct material *mtl)
416 {
417         while(mtl) {
418                 void *tmp = mtl;
419                 mtl = mtl->next;
420                 free(tmp);
421         }
422 }
423
424 static void conv_mtl(struct material *mm, struct objmtl *om, const char *path_prefix)
425 {
426         char *fname = 0, *suffix = 0;
427         int len, prefix_len, maxlen = 0;
428
429         memset(mm, 0, sizeof *mm);
430         mm->name = om->name;
431         mm->color = om->kd;
432         mm->spec = om->ks;
433         mm->shininess = om->shin;
434
435         if(om->map_kd && (len = strlen(om->map_kd)) > maxlen) maxlen = len;
436         if(om->map_ke && (len = strlen(om->map_ke)) > maxlen) maxlen = len;
437         if(om->map_alpha && (len = strlen(om->map_alpha)) > maxlen) maxlen = len;
438
439         if(maxlen) {
440                 prefix_len = strlen(path_prefix);
441                 fname = alloca(maxlen + prefix_len + 2);
442                 suffix = fname + prefix_len;
443                 strcpy(fname, path_prefix);
444         }
445
446         /*
447         if(om->map_kd) {
448                 strcpy(suffix, om->map_kd);
449                 mm->tex[TEX_DIFFUSE] = get_image(fname);
450         }
451         */
452 }
453
454 static int cmp_facevert(const void *ap, const void *bp)
455 {
456         const struct facevertex *a = ap;
457         const struct facevertex *b = bp;
458
459         if(a->vidx == b->vidx) {
460                 if(a->tidx == b->tidx) {
461                         return a->nidx - b->nidx;
462                 }
463                 return a->tidx - b->tidx;
464         }
465         return a->vidx - b->vidx;
466 }
467
468 static void free_rbnode_key(struct rbnode *n, void *cls)
469 {
470         free(n->key);
471 }