clray

view src/scene_obj.cc @ 23:51f115e337c2

separated obj loading and vector class
author John Tsiombikas <nuclear@member.fsf.org>
date Fri, 13 Aug 2010 18:20:45 +0100
parents
children 0f174fd60f19
line source
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <ctype.h>
6 #include <limits.h>
7 #include <string>
8 #include <vector>
9 #include <map>
10 #include "scene.h"
11 #include "vector.h"
13 #ifndef PATH_MAX
14 #define PATH_MAX 512
15 #endif
17 #define COMMANDS \
18 CMD(V), \
19 CMD(VN), \
20 CMD(VT), \
21 CMD(F), \
22 CMD(O), \
23 CMD(G), \
24 CMD(MTLLIB), \
25 CMD(USEMTL), \
26 CMD(NEWMTL), \
27 CMD(KA), \
28 CMD(KD), \
29 CMD(KS), \
30 CMD(KR), \
31 CMD(NS), \
32 CMD(NI), \
33 CMD(D), \
34 CMD(TR), \
35 CMD(MAP_KD), \
36 CMD(MAP_KS), \
37 CMD(MAP_NS), \
38 CMD(MAP_D), \
39 CMD(REFL), \
40 CMD(BUMP)
42 #define CMD(x) CMD_##x
43 enum {
44 COMMANDS,
45 CMD_UNK
46 };
47 #undef CMD
49 #define CMD(x) #x
50 static const char *cmd_names[] = {
51 COMMANDS,
52 0
53 };
54 #undef CMD
56 struct obj_face {
57 int elem;
58 int v[4], n[4], t[4];
59 };
61 struct obj_file {
62 std::string cur_obj, cur_mat;
63 std::vector<Vector3> v, vn, vt;
64 std::vector<obj_face> f;
65 };
67 struct obj_mat {
68 std::string name; // newmtl <name>
69 Vector3 ambient, diffuse, specular; // Ka, Kd, Ks
70 float shininess; // Ns
71 float ior; // Ni
72 float alpha; // d, Tr
73 float refl; // Kr (my extesnsion)
75 std::string tex_dif, tex_spec, tex_shin, tex_alpha; // map_Kd, map_Ks, map_Ns, map_d
76 std::string tex_refl; // refl -type sphere|cube file
77 std::string tex_bump; // bump
79 obj_mat() { reset(); }
81 void reset() {
82 ambient = diffuse = Vector3(0.5, 0.5, 0.5);
83 specular = Vector3(0.0, 0.0, 0.0);
84 name = tex_dif = tex_spec = tex_shin = tex_alpha = tex_refl = tex_bump = "";
85 shininess = 0;
86 ior = alpha = 1;
87 refl = 0.0;
88 }
89 };
91 static bool read_materials(FILE *fp, std::vector<obj_mat> *vmtl);
92 static Mesh *cons_mesh(obj_file *obj);
94 static int get_cmd(char *str);
95 static bool is_int(const char *str);
96 static bool is_float(const char *str);
97 static bool parse_vec(Vector3 *vec);
98 static bool parse_color(Vector3 *col);
99 static bool parse_face(obj_face *face);
100 static const char *parse_map();
102 static bool find_file(char *res, int sz, const char *fname, const char *path = ".", const char *mode = "rb");
103 static const char *dirname(const char *str);
105 static std::map<std::string, int> matnames;
107 #define INVALID_IDX INT_MIN
109 #define SEP " \t\n\r\v"
110 #define BUF_SZ 512
113 bool Scene::load(const char *fname)
114 {
115 FILE *fp;
117 if(!(fp = fopen(fname, "rb"))) {
118 fprintf(stderr, "failed to open %s: %s\n", fname, strerror(errno));
119 return false;
120 }
122 bool res = load(fp);
123 fclose(fp);
124 return res;
125 }
127 bool Scene::load(FILE *fp)
128 {
129 static int seq;
130 char cur_name[16];
132 obj_file obj;
134 sprintf(cur_name, "default%02d.obj", seq++);
135 obj.cur_obj = cur_name;
137 int prev_cmd = 0, obj_added = 0;
138 for(;;) {
139 Vector3 vec;
140 obj_face face;
142 char line[BUF_SZ];
143 fgets(line, sizeof line, fp);
144 if(feof(fp)) {
145 break;
146 }
148 char *tok;
149 if(!(tok = strtok(line, SEP))) {
150 continue; // ignore empty lines
151 }
153 int cmd;
154 if((cmd = get_cmd(tok)) == -1) {
155 continue; // ignore unknown commands ...
156 }
158 switch(cmd) {
159 case CMD_V:
160 if(!parse_vec(&vec)) {
161 continue;
162 }
163 obj.v.push_back(vec);
164 break;
166 case CMD_VN:
167 if(!parse_vec(&vec)) {
168 continue;
169 }
170 obj.vn.push_back(vec);
171 break;
173 case CMD_VT:
174 if(!parse_vec(&vec)) {
175 continue;
176 }
177 vec.y = 1.0 - vec.y;
178 obj.vt.push_back(vec);
179 break;
181 case CMD_O:
182 case CMD_G:
183 if(prev_cmd == CMD_O || prev_cmd == CMD_G) {
184 break; // just in case we've got both of them in a row
185 }
186 /* if we have any previous data, group them up, add the object
187 * and continue with the new one...
188 */
189 if(!obj.f.empty()) {
190 Mesh *mesh = cons_mesh(&obj);
191 mesh->matid = matnames[obj.cur_mat];
192 add_mesh(mesh);
193 obj_added++;
195 obj.f.clear(); // clean the face list
196 }
197 if((tok = strtok(0, SEP))) {
198 obj.cur_obj = tok;
199 } else {
200 sprintf(cur_name, "default%02d.obj", seq++);
201 obj.cur_obj = cur_name;
202 }
203 break;
205 case CMD_MTLLIB:
206 if((tok = strtok(0, SEP))) {
207 char path[PATH_MAX];
209 sprintf(path, ".:%s", dirname(tok));
210 if(!find_file(path, PATH_MAX, tok, path)) {
211 fprintf(stderr, "material library not found: %s\n", tok);
212 continue;
213 }
215 FILE *mfile;
216 if(!(mfile = fopen(path, "rb"))) {
217 fprintf(stderr, "failed to open material library: %s\n", path);
218 continue;
219 }
221 // load all materials of the mtl file into a vector
222 std::vector<obj_mat> vmtl;
223 if(!read_materials(mfile, &vmtl)) {
224 continue;
225 }
226 fclose(mfile);
228 // and add them all to the scene
229 for(size_t i=0; i<vmtl.size(); i++) {
230 Material mat;
232 mat.kd[0] = vmtl[i].diffuse.x;
233 mat.kd[1] = vmtl[i].diffuse.y;
234 mat.kd[2] = vmtl[i].diffuse.z;
236 mat.ks[0] = vmtl[i].specular.x;
237 mat.ks[1] = vmtl[i].specular.y;
238 mat.ks[2] = vmtl[i].specular.z;
240 mat.kt = 1.0 - vmtl[i].alpha;
241 mat.kr = vmtl[i].refl;
242 mat.spow = vmtl[i].shininess;
244 matlib.push_back(mat);
245 matnames[vmtl[i].name] = i;
246 }
247 }
248 break;
250 case CMD_USEMTL:
251 if((tok = strtok(0, SEP))) {
252 obj.cur_mat = tok;
253 } else {
254 obj.cur_mat = "";
255 }
256 break;
258 case CMD_F:
259 if(!parse_face(&face)) {
260 continue;
261 }
263 // convert negative indices to regular indices
264 for(int i=0; i<4; i++) {
265 if(face.v[i] < 0 && face.v[i] != INVALID_IDX) {
266 face.v[i] = obj.v.size() + face.v[i];
267 }
268 if(face.n[i] < 0 && face.n[i] != INVALID_IDX) {
269 face.n[i] = obj.vn.size() + face.n[i];
270 }
271 if(face.t[i] < 0 && face.t[i] != INVALID_IDX) {
272 face.t[i] = obj.vt.size() + face.t[i];
273 }
274 }
276 // break quads into triangles if needed
277 obj.f.push_back(face);
278 if(face.elem == 4) {
279 face.v[1] = face.v[2];
280 face.n[1] = face.n[2];
281 face.t[1] = face.t[2];
283 face.v[2] = face.v[3];
284 face.n[2] = face.n[3];
285 face.t[2] = face.t[3];
287 obj.f.push_back(face);
288 }
289 break;
291 default:
292 break; // ignore unknown commands
293 }
295 prev_cmd = cmd;
296 }
298 // reached end of file...
299 if(!obj.f.empty()) {
300 Mesh *mesh = cons_mesh(&obj);
301 mesh->matid = matnames[obj.cur_mat];
302 add_mesh(mesh);
303 obj_added++;
304 }
306 return obj_added > 0;
307 }
309 static Mesh *cons_mesh(obj_file *obj)
310 {
311 Mesh *mesh;
313 // need at least one of each element
314 bool added_norm = false, added_tc = false;
315 if(obj->vn.empty()) {
316 obj->vn.push_back(Vector3(0, 0, 0));
317 added_norm = true;
318 }
319 if(obj->vt.empty()) {
320 obj->vt.push_back(Vector3(0, 0, 0));
321 added_tc = true;
322 }
324 mesh = new Mesh;
326 for(size_t i=0; i<obj->f.size(); i++) {
327 Face face;
328 Vector3 v[3];
330 for(int j=0; j<3; j++) {
331 obj_face *f = &obj->f[i];
333 face.v[j].pos[0] = v[j].x = obj->v[f->v[j]].x;
334 face.v[j].pos[1] = v[j].y = obj->v[f->v[j]].y;
335 face.v[j].pos[2] = v[j].z = obj->v[f->v[j]].z;
336 face.v[j].pos[3] = 0.0;
338 int nidx = f->n[j] < 0 ? 0 : f->n[j];
339 face.v[j].normal[0] = obj->vn[nidx].x;
340 face.v[j].normal[1] = obj->vn[nidx].y;
341 face.v[j].normal[2] = obj->vn[nidx].z;
342 face.v[j].normal[3] = 0.0;
344 int tidx = f->t[j] < 0 ? 0 : f->t[j];
345 face.v[j].tex[0] = obj->vt[tidx].x;
346 face.v[j].tex[1] = obj->vt[tidx].y;
347 }
349 Vector3 a = v[1] - v[0];
350 Vector3 b = v[2] - v[0];
351 Vector3 n = cross(a, b);
352 n.normalize();
354 face.normal[0] = n.x;
355 face.normal[1] = n.y;
356 face.normal[2] = n.z;
357 face.normal[3] = 0.0;
359 mesh->faces.push_back(face);
360 }
362 if(added_norm) {
363 obj->vn.pop_back();
364 }
365 if(added_tc) {
366 obj->vt.pop_back();
367 }
369 return mesh;
370 }
372 static bool read_materials(FILE *fp, std::vector<obj_mat> *vmtl)
373 {
374 obj_mat mat;
376 for(;;) {
377 char line[BUF_SZ];
378 fgets(line, sizeof line, fp);
379 if(feof(fp)) {
380 break;
381 }
383 char *tok;
384 if(!(tok = strtok(line, SEP))) {
385 continue;
386 }
388 int cmd;
389 if((cmd = get_cmd(tok)) == -1) {
390 continue;
391 }
393 switch(cmd) {
394 case CMD_NEWMTL:
395 // add the previous material, and start a new one
396 if(mat.name.length() > 0) {
397 printf("Adding material: %s\n", mat.name.c_str());
398 vmtl->push_back(mat);
399 mat.reset();
400 }
401 if((tok = strtok(0, SEP))) {
402 mat.name = tok;
403 }
404 break;
406 case CMD_KA:
407 parse_color(&mat.ambient);
408 break;
410 case CMD_KD:
411 parse_color(&mat.diffuse);
412 break;
414 case CMD_KS:
415 parse_color(&mat.specular);
416 break;
418 case CMD_KR:
419 if((tok = strtok(0, SEP)) && is_float(tok)) {
420 mat.refl = atof(tok);
421 }
422 break;
424 case CMD_NS:
425 if((tok = strtok(0, SEP)) && is_float(tok)) {
426 mat.shininess = atof(tok);
427 }
428 break;
430 case CMD_NI:
431 if((tok = strtok(0, SEP)) && is_float(tok)) {
432 mat.ior = atof(tok);
433 }
434 break;
436 case CMD_D:
437 case CMD_TR:
438 {
439 Vector3 c;
440 if(parse_color(&c)) {
441 mat.alpha = cmd == CMD_D ? c.x : 1.0 - c.x;
442 }
443 }
444 break;
446 case CMD_MAP_KD:
447 mat.tex_dif = parse_map();
448 break;
450 default:
451 break;
452 }
453 }
455 if(mat.name.length() > 0) {
456 printf("Adding material: %s\n", mat.name.c_str());
457 vmtl->push_back(mat);
458 }
459 return true;
460 }
462 static int get_cmd(char *str)
463 {
464 char *s = str;
465 while((*s = toupper(*s))) s++;
467 for(int i=0; cmd_names[i]; i++) {
468 if(strcmp(str, cmd_names[i]) == 0) {
469 return i;
470 }
471 }
472 return CMD_UNK;
473 }
475 static bool is_int(const char *str)
476 {
477 char *tmp;
478 strtol(str, &tmp, 10);
479 return tmp != str;
480 }
482 static bool is_float(const char *str)
483 {
484 char *tmp;
485 strtod(str, &tmp);
486 return tmp != str;
487 }
489 static bool parse_vec(Vector3 *vec)
490 {
491 for(int i=0; i<3; i++) {
492 char *tok;
494 if(!(tok = strtok(0, SEP)) || !is_float(tok)) {
495 if(i < 2) {
496 return false;
497 }
498 vec->z = 0.0;
499 } else {
500 float v = atof(tok);
502 switch(i) {
503 case 0:
504 vec->x = v;
505 break;
506 case 1:
507 vec->y = v;
508 break;
509 case 2:
510 vec->z = v;
511 break;
512 }
513 }
514 }
515 return true;
516 }
518 static bool parse_color(Vector3 *col)
519 {
520 for(int i=0; i<3; i++) {
521 char *tok;
523 if(!(tok = strtok(0, SEP)) || !is_float(tok)) {
524 col->y = col->z = col->x;
525 return i > 0 ? true : false;
526 }
528 float v = atof(tok);
529 switch(i) {
530 case 0:
531 col->x = v;
532 break;
533 case 1:
534 col->y = v;
535 break;
536 case 2:
537 col->z = v;
538 break;
539 }
540 }
541 return true;
542 }
544 static bool parse_face(obj_face *face)
545 {
546 char *tok[] = {0, 0, 0, 0};
547 face->elem = 0;
549 for(int i=0; i<4; i++) {
550 if((!(tok[i] = strtok(0, SEP)) || !is_int(tok[i]))) {
551 if(i < 3) return false; // less than 3 verts? not a polygon
552 } else {
553 face->elem++;
554 }
555 }
557 for(int i=0; i<4; i++) {
558 char *subtok = tok[i];
560 if(!subtok || !*subtok || !is_int(subtok)) {
561 if(i < 3) {
562 return false;
563 }
564 face->v[i] = INVALID_IDX;
565 } else {
566 face->v[i] = atoi(subtok);
567 if(face->v[i] > 0) face->v[i]--; /* convert to 0-based */
568 }
570 while(subtok && *subtok && *subtok != '/') {
571 subtok++;
572 }
573 if(subtok && *subtok && *++subtok && is_int(subtok)) {
574 face->t[i] = atoi(subtok);
575 if(face->t[i] > 0) face->t[i]--; /* convert to 0-based */
576 } else {
577 face->t[i] = INVALID_IDX;
578 }
580 while(subtok && *subtok && *subtok != '/') {
581 subtok++;
582 }
583 if(subtok && *subtok && *++subtok && is_int(subtok)) {
584 face->n[i] = atoi(subtok);
585 if(face->n[i] > 0) face->n[i]--; /* convert to 0-based */
586 } else {
587 face->n[i] = INVALID_IDX;
588 }
589 }
591 return true;
592 }
594 static const char *parse_map()
595 {
596 char *tok, *prev = 0;
598 while((tok = strtok(0, SEP))) {
599 prev = tok;
600 }
602 return prev ? prev : "";
603 }
605 static bool find_file(char *res, int sz, const char *fname, const char *path, const char *mode)
606 {
607 FILE *fp;
608 const char *beg, *end;
609 int fnamelen = strlen(fname);
611 beg = path;
612 while(beg && *beg) {
613 end = beg;
614 while(*end && *end != ':') {
615 end++;
616 }
618 int res_len = end - beg;
619 char *pathname = (char*)alloca(res_len + fnamelen + 2);
620 memcpy(pathname, beg, res_len);
621 pathname[res_len] = 0;
622 if(res_len) {
623 strcat(pathname, "/");
624 }
625 strcat(pathname, fname);
627 if((fp = fopen(pathname, mode))) {
628 fclose(fp);
629 strncpy(res, pathname, sz);
630 return true;
631 }
633 beg += res_len;
634 if(*beg == ':') beg++;
635 }
636 return false;
637 }
639 static const char *dirname(const char *str)
640 {
641 static char buf[PATH_MAX];
643 if(!str || !*str) {
644 strcpy(buf, ".");
645 } else {
646 strncpy(buf, str, PATH_MAX);
647 char *ptr = strrchr(buf, '/');
649 if(ptr && *ptr) {
650 *ptr = 0;
651 } else {
652 strcpy(buf, ".");
653 }
654 }
655 return buf;
656 }