eqemu
view src/objfile.cc @ 10:819c7ebb1bec
added libimago to avoid the external dependency
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Fri, 18 Jul 2014 05:07:40 +0300 |
parents | 9b5bb05ae53a |
children | 2656099aff12 |
line source
1 #include <stdlib.h>
2 #include <string.h>
3 #include <ctype.h>
4 #include <limits.h>
5 #include <assert.h>
6 #include <vector>
7 #include <map>
8 #include <string>
9 #include <sstream>
10 #include <iomanip>
11 #include "scene.h"
13 using namespace std;
15 #define COMMANDS \
16 CMD(V), \
17 CMD(VN), \
18 CMD(VT), \
19 CMD(F), \
20 CMD(O), \
21 CMD(G), \
22 CMD(MTLLIB), \
23 CMD(USEMTL), \
24 CMD(NEWMTL), \
25 CMD(KE), \
26 CMD(KA), \
27 CMD(KD), \
28 CMD(KS), \
29 CMD(NS), \
30 CMD(NI), \
31 CMD(D), \
32 CMD(TR), \
33 CMD(MAP_KD), \
34 CMD(MAP_REFL), \
35 CMD(MAP_BUMP), \
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
57 struct ObjFace {
58 int elem;
59 int v[4], n[4], t[4];
60 };
62 struct ObjFile {
63 string cur_obj, cur_mat;
64 vector<Vector3> v, vn;
65 vector<Vector2> vt;
66 vector<ObjFace> f;
67 };
69 typedef Vector3 Color;
71 struct ObjMat {
72 string name; // newmtl <name>
73 Color ambient, diffuse, specular; // Ka, Kd, Ks
74 Color emissive; // Ke
75 float shininess; // Ns
76 float ior; // Ni
77 float alpha; // d, Tr
79 string tex_dif, tex_spec, tex_shin, tex_alpha; // map_Kd, map_Ks, map_Ns, map_d
80 string tex_refl; // refl -type sphere|cube file
81 string tex_bump; // bump
83 ObjMat() { reset(); }
85 void reset() {
86 ambient = diffuse = Color(0.5, 0.5, 0.5);
87 specular = Color(0.0, 0.0, 0.0);
88 name = tex_dif = tex_spec = tex_shin = tex_alpha = tex_refl = tex_bump = "";
89 shininess = 0;
90 ior = alpha = 1;
91 }
92 };
94 static bool read_materials(FILE *fp, vector<ObjMat> *vmtl);
95 static Object *cons_object(ObjFile *obj);
97 static int get_cmd(char *str);
98 static bool is_int(const char *str);
99 static bool is_float(const char *str);
100 static bool parse_vec(Vector3 *vec);
101 static bool parse_color(Color *col);
102 static bool parse_face(ObjFace *face);
103 static const char *parse_map();
106 static map<string, Material> matlib;
109 #define INVALID_IDX INT_MIN
111 #define SEP " \t\n\r\v"
112 #define BUF_SZ 512
113 bool Scene::load_obj(FILE *fp)
114 {
115 static int seq;
116 char cur_name[16];
117 stringstream sstr;
119 ObjFile obj;
121 sprintf(cur_name, "default%02d.obj", seq++);
122 obj.cur_obj = cur_name;
124 int prev_cmd = 0, obj_added = 0;
125 for(;;) {
126 Vector3 vec;
127 ObjFace face;
129 char line[BUF_SZ];
130 fgets(line, sizeof line, fp);
131 if(feof(fp)) {
132 break;
133 }
135 char *tok;
136 if(!(tok = strtok(line, SEP))) {
137 continue; // ignore empty lines
138 }
140 int cmd;
141 if((cmd = get_cmd(tok)) == -1) {
142 continue; // ignore unknown commands ...
143 }
145 switch(cmd) {
146 case CMD_V:
147 if(!parse_vec(&vec)) {
148 continue;
149 }
150 obj.v.push_back(vec);
151 break;
153 case CMD_VN:
154 if(!parse_vec(&vec)) {
155 continue;
156 }
157 obj.vn.push_back(vec);
158 break;
160 case CMD_VT:
161 if(!parse_vec(&vec)) {
162 continue;
163 }
164 vec.y = 1.0 - vec.y;
165 obj.vt.push_back(Vector2(vec.x, vec.y));
166 break;
168 case CMD_O:
169 case CMD_G:
170 if(prev_cmd == CMD_O || prev_cmd == CMD_G) {
171 break; // just in case we've got both of them in a row
172 }
173 /* if we have any previous data, group them up, add the object
174 * and continue with the new one...
175 */
176 if(!obj.f.empty()) {
177 Object *robj = cons_object(&obj);
178 robj->mtl = matlib[obj.cur_mat];
179 add_object(robj);
180 obj_added++;
182 obj.f.clear(); // clean the face list
183 }
184 if((tok = strtok(0, SEP))) {
185 obj.cur_obj = tok;
186 } else {
187 sprintf(cur_name, "default%02d.obj", seq++);
188 obj.cur_obj = cur_name;
189 }
190 break;
192 case CMD_MTLLIB:
193 if((tok = strtok(0, SEP))) {
194 FILE *mfile;
195 if(!(mfile = fopen(tok, "rb"))) {
196 fprintf(stderr, "failed to open material library: %s\n", tok);
197 continue;
198 }
200 // load all materials of the mtl file into a vector
201 vector<ObjMat> vmtl;
202 if(!read_materials(mfile, &vmtl)) {
203 continue;
204 }
205 fclose(mfile);
207 // and add them all to the scene
208 for(size_t i=0; i<vmtl.size(); i++) {
209 Material mat;
210 mat.ambient = vmtl[i].ambient;
211 mat.diffuse = vmtl[i].diffuse;
212 mat.specular = vmtl[i].specular;
213 mat.shininess = vmtl[i].shininess;
214 mat.emissive = vmtl[i].emissive;
215 mat.alpha = vmtl[i].alpha;
217 if(vmtl[i].tex_dif.length()) {
218 mat.tex[TEX_DIFFUSE] = load_texture(vmtl[i].tex_dif.c_str());
219 }
220 if(vmtl[i].tex_refl.length()) {
221 mat.tex[TEX_ENVMAP] = load_texture(vmtl[i].tex_refl.c_str());
222 }
224 matlib[vmtl[i].name] = mat;
225 }
226 }
227 break;
229 case CMD_USEMTL:
230 if((tok = strtok(0, SEP))) {
231 obj.cur_mat = tok;
232 } else {
233 obj.cur_mat = "";
234 }
235 break;
237 case CMD_F:
238 if(!parse_face(&face)) {
239 continue;
240 }
242 // convert negative indices to regular indices
243 for(int i=0; i<4; i++) {
244 if(face.v[i] < 0 && face.v[i] != INVALID_IDX) {
245 face.v[i] = obj.v.size() + face.v[i];
246 }
247 if(face.n[i] < 0 && face.n[i] != INVALID_IDX) {
248 face.n[i] = obj.vn.size() + face.n[i];
249 }
250 if(face.t[i] < 0 && face.t[i] != INVALID_IDX) {
251 face.t[i] = obj.vt.size() + face.t[i];
252 }
253 }
255 // break quads into triangles if needed
256 obj.f.push_back(face);
257 if(face.elem == 4) {
258 face.v[1] = face.v[2];
259 face.n[1] = face.n[2];
260 face.t[1] = face.t[2];
262 face.v[2] = face.v[3];
263 face.n[2] = face.n[3];
264 face.t[2] = face.t[3];
266 obj.f.push_back(face);
267 }
268 break;
270 default:
271 break; // ignore unknown commands
272 }
274 prev_cmd = cmd;
275 }
277 // reached end of file...
278 if(!obj.f.empty()) {
279 Object *robj = cons_object(&obj);
280 robj->mtl = matlib[obj.cur_mat];
281 add_object(robj);
282 obj_added++;
283 }
285 return obj_added > 0;
286 }
288 static Object *cons_object(ObjFile *obj)
289 {
290 Object *robj;
291 Vector3 *varr, *narr;
292 Vector2 *tarr;
294 int nelem = obj->f.size() * 3;
296 assert(sizeof(Vector3) == 3 * sizeof(float));
297 assert(sizeof(Vector2) == 2 * sizeof(float));
299 try {
300 robj = new Object;
301 varr = new Vector3[nelem];
302 narr = new Vector3[nelem];
303 tarr = new Vector2[nelem];
304 }
305 catch(...) {
306 return 0;
307 }
308 if(obj->cur_obj.length() > 0) {
309 robj->set_name(obj->cur_obj.c_str());
310 }
312 // need at least one of each element
313 bool added_norm = false, added_tc = false;
314 if(obj->vn.empty()) {
315 obj->vn.push_back(Vector3(0, 0, 0));
316 added_norm = true;
317 }
318 if(obj->vt.empty()) {
319 obj->vt.push_back(Vector2(0, 0));
320 added_tc = true;
321 }
323 for(size_t i=0; i<obj->f.size(); i++) {
324 for(int j=0; j<3; j++) {
325 int idx = i * 3 + j;
326 ObjFace *f = &obj->f[i];
328 varr[idx] = obj->v[f->v[j]];
329 narr[idx] = obj->vn[f->n[j] < 0 ? 0 : f->n[j]];
331 float t = obj->vt[f->t[j] < 0 ? 0 : f->t[j]].x;
332 float s = obj->vt[f->t[j] < 0 ? 0 : f->t[j]].y;
333 tarr[idx] = Vector2(t, s);
334 }
335 }
337 if(added_norm) {
338 obj->vn.pop_back();
339 }
340 if(added_tc) {
341 obj->vt.pop_back();
342 }
344 Mesh *mesh = new Mesh;
345 mesh->set_attrib(MESH_ATTR_VERTEX, nelem, 3, &varr->x);
346 mesh->set_attrib(MESH_ATTR_NORMAL, nelem, 3, &narr->x);
347 mesh->set_attrib(MESH_ATTR_TEXCOORD, nelem, 2, &tarr->x);
348 robj->set_mesh(mesh);
350 printf("loaded object %s: %d faces\n", obj->cur_obj.c_str(), nelem / 3);
352 delete [] varr;
353 delete [] narr;
354 delete [] tarr;
355 return robj;
356 }
358 static bool read_materials(FILE *fp, vector<ObjMat> *vmtl)
359 {
360 ObjMat mat;
362 for(;;) {
363 char line[BUF_SZ];
364 fgets(line, sizeof line, fp);
365 if(feof(fp)) {
366 break;
367 }
369 char *tok;
370 if(!(tok = strtok(line, SEP))) {
371 continue;
372 }
374 int cmd;
375 if((cmd = get_cmd(tok)) == -1) {
376 continue;
377 }
379 switch(cmd) {
380 case CMD_NEWMTL:
381 // add the previous material, and start a new one
382 if(mat.name.length() > 0) {
383 vmtl->push_back(mat);
384 mat.reset();
385 }
386 if((tok = strtok(0, SEP))) {
387 mat.name = tok;
388 }
389 break;
391 case CMD_KE:
392 parse_color(&mat.emissive);
393 break;
395 case CMD_KA:
396 parse_color(&mat.ambient);
397 break;
399 case CMD_KD:
400 parse_color(&mat.diffuse);
401 break;
403 case CMD_KS:
404 parse_color(&mat.specular);
405 break;
407 case CMD_NS:
408 if((tok = strtok(0, SEP)) && is_float(tok)) {
409 mat.shininess = atof(tok);
410 }
411 break;
413 case CMD_NI:
414 if((tok = strtok(0, SEP)) && is_float(tok)) {
415 mat.ior = atof(tok);
416 }
417 break;
419 case CMD_D:
420 case CMD_TR:
421 {
422 Color c;
423 if(parse_color(&c)) {
424 mat.alpha = cmd == CMD_D ? c.x : 1.0 - c.x;
425 }
426 }
427 break;
429 case CMD_MAP_KD:
430 mat.tex_dif = parse_map();
431 break;
433 case CMD_MAP_REFL:
434 mat.tex_refl = parse_map();
435 break;
437 default:
438 break;
439 }
440 }
442 if(mat.name.length() > 0) {
443 vmtl->push_back(mat);
444 }
445 return true;
446 }
448 static int get_cmd(char *str)
449 {
450 char *s = str;
451 while((*s = toupper(*s))) s++;
453 for(int i=0; cmd_names[i]; i++) {
454 if(strcmp(str, cmd_names[i]) == 0) {
455 return i;
456 }
457 }
458 return CMD_UNK;
459 }
461 static bool is_int(const char *str)
462 {
463 char *tmp;
464 strtol(str, &tmp, 10);
465 return tmp != str;
466 }
468 static bool is_float(const char *str)
469 {
470 char *tmp;
471 strtod(str, &tmp);
472 return tmp != str;
473 }
475 static bool parse_vec(Vector3 *vec)
476 {
477 for(int i=0; i<3; i++) {
478 char *tok;
480 if(!(tok = strtok(0, SEP)) || !is_float(tok)) {
481 if(i < 2) {
482 return false;
483 }
484 vec->z = 0.0;
485 } else {
486 (*vec)[i] = atof(tok);
487 }
488 }
489 return true;
490 }
492 static bool parse_color(Color *col)
493 {
494 for(int i=0; i<3; i++) {
495 char *tok;
497 if(!(tok = strtok(0, SEP)) || !is_float(tok)) {
498 col->y = col->z = col->x;
499 return i > 0 ? true : false;
500 }
501 (*col)[i] = atof(tok);
502 }
503 return true;
504 }
506 static bool parse_face(ObjFace *face)
507 {
508 char *tok[] = {0, 0, 0, 0};
509 face->elem = 0;
511 for(int i=0; i<4; i++) {
512 if((!(tok[i] = strtok(0, SEP)) || !is_int(tok[i]))) {
513 if(i < 3) return false; // less than 3 verts? not a polygon
514 } else {
515 face->elem++;
516 }
517 }
519 for(int i=0; i<4; i++) {
520 char *subtok = tok[i];
522 if(!subtok || !*subtok || !is_int(subtok)) {
523 if(i < 3) {
524 return false;
525 }
526 face->v[i] = INVALID_IDX;
527 } else {
528 face->v[i] = atoi(subtok);
529 if(face->v[i] > 0) face->v[i]--; /* convert to 0-based */
530 }
532 while(subtok && *subtok && *subtok != '/') {
533 subtok++;
534 }
535 if(subtok && *subtok && *++subtok && is_int(subtok)) {
536 face->t[i] = atoi(subtok);
537 if(face->t[i] > 0) face->t[i]--; /* convert to 0-based */
538 } else {
539 face->t[i] = INVALID_IDX;
540 }
542 while(subtok && *subtok && *subtok != '/') {
543 subtok++;
544 }
545 if(subtok && *subtok && *++subtok && is_int(subtok)) {
546 face->n[i] = atoi(subtok);
547 if(face->n[i] > 0) face->n[i]--; /* convert to 0-based */
548 } else {
549 face->n[i] = INVALID_IDX;
550 }
551 }
553 return true;
554 }
556 static const char *parse_map()
557 {
558 char *tok, *prev = 0;
560 while((tok = strtok(0, SEP))) {
561 prev = tok;
562 }
564 return prev ? prev : "";
565 }