eqemu
view src/objfile.cc @ 12:2656099aff12
added copyright notices and license
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Fri, 18 Jul 2014 07:04:21 +0300 |
parents | 977bc1cb055b |
children |
line source
1 /*
2 eqemu - electronic queue system emulator
3 Copyright (C) 2014 John Tsiombikas <nuclear@member.fsf.org>,
4 Eleni-Maria Stea <eleni@mutantstargoat.com>
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include <stdlib.h>
20 #include <string.h>
21 #include <ctype.h>
22 #include <limits.h>
23 #include <assert.h>
24 #include <vector>
25 #include <map>
26 #include <string>
27 #include <sstream>
28 #include <iomanip>
29 #include "scene.h"
31 using namespace std;
33 #define COMMANDS \
34 CMD(V), \
35 CMD(VN), \
36 CMD(VT), \
37 CMD(F), \
38 CMD(O), \
39 CMD(G), \
40 CMD(MTLLIB), \
41 CMD(USEMTL), \
42 CMD(NEWMTL), \
43 CMD(KE), \
44 CMD(KA), \
45 CMD(KD), \
46 CMD(KS), \
47 CMD(NS), \
48 CMD(NI), \
49 CMD(D), \
50 CMD(TR), \
51 CMD(MAP_KD), \
52 CMD(MAP_REFL), \
53 CMD(MAP_BUMP), \
54 CMD(MAP_KS), \
55 CMD(MAP_NS), \
56 CMD(MAP_D), \
57 CMD(REFL), \
58 CMD(BUMP)
60 #define CMD(x) CMD_##x
61 enum {
62 COMMANDS,
63 CMD_UNK
64 };
65 #undef CMD
67 #define CMD(x) #x
68 static const char *cmd_names[] = {
69 COMMANDS,
70 0
71 };
72 #undef CMD
75 struct ObjFace {
76 int elem;
77 int v[4], n[4], t[4];
78 };
80 struct ObjFile {
81 string cur_obj, cur_mat;
82 vector<Vector3> v, vn;
83 vector<Vector2> vt;
84 vector<ObjFace> f;
85 };
87 typedef Vector3 Color;
89 struct ObjMat {
90 string name; // newmtl <name>
91 Color ambient, diffuse, specular; // Ka, Kd, Ks
92 Color emissive; // Ke
93 float shininess; // Ns
94 float ior; // Ni
95 float alpha; // d, Tr
97 string tex_dif, tex_spec, tex_shin, tex_alpha; // map_Kd, map_Ks, map_Ns, map_d
98 string tex_refl; // refl -type sphere|cube file
99 string tex_bump; // bump
101 ObjMat() { reset(); }
103 void reset() {
104 ambient = diffuse = Color(0.5, 0.5, 0.5);
105 specular = Color(0.0, 0.0, 0.0);
106 name = tex_dif = tex_spec = tex_shin = tex_alpha = tex_refl = tex_bump = "";
107 shininess = 0;
108 ior = alpha = 1;
109 }
110 };
112 static bool read_materials(FILE *fp, vector<ObjMat> *vmtl);
113 static Object *cons_object(ObjFile *obj);
115 static int get_cmd(char *str);
116 static bool is_int(const char *str);
117 static bool is_float(const char *str);
118 static bool parse_vec(Vector3 *vec);
119 static bool parse_color(Color *col);
120 static bool parse_face(ObjFace *face);
121 static const char *parse_map();
124 static map<string, Material> matlib;
127 #define INVALID_IDX INT_MIN
129 #define SEP " \t\n\r\v"
130 #define BUF_SZ 512
131 bool Scene::load_obj(FILE *fp)
132 {
133 static int seq;
134 char cur_name[16];
135 stringstream sstr;
137 ObjFile obj;
139 sprintf(cur_name, "default%02d.obj", seq++);
140 obj.cur_obj = cur_name;
142 int prev_cmd = 0, obj_added = 0;
143 for(;;) {
144 Vector3 vec;
145 ObjFace face;
147 char line[BUF_SZ];
148 fgets(line, sizeof line, fp);
149 if(feof(fp)) {
150 break;
151 }
153 char *tok;
154 if(!(tok = strtok(line, SEP))) {
155 continue; // ignore empty lines
156 }
158 int cmd;
159 if((cmd = get_cmd(tok)) == -1) {
160 continue; // ignore unknown commands ...
161 }
163 switch(cmd) {
164 case CMD_V:
165 if(!parse_vec(&vec)) {
166 continue;
167 }
168 obj.v.push_back(vec);
169 break;
171 case CMD_VN:
172 if(!parse_vec(&vec)) {
173 continue;
174 }
175 obj.vn.push_back(vec);
176 break;
178 case CMD_VT:
179 if(!parse_vec(&vec)) {
180 continue;
181 }
182 vec.y = 1.0 - vec.y;
183 obj.vt.push_back(Vector2(vec.x, vec.y));
184 break;
186 case CMD_O:
187 case CMD_G:
188 if(prev_cmd == CMD_O || prev_cmd == CMD_G) {
189 break; // just in case we've got both of them in a row
190 }
191 /* if we have any previous data, group them up, add the object
192 * and continue with the new one...
193 */
194 if(!obj.f.empty()) {
195 Object *robj = cons_object(&obj);
196 robj->mtl = matlib[obj.cur_mat];
197 add_object(robj);
198 obj_added++;
200 obj.f.clear(); // clean the face list
201 }
202 if((tok = strtok(0, SEP))) {
203 obj.cur_obj = tok;
204 } else {
205 sprintf(cur_name, "default%02d.obj", seq++);
206 obj.cur_obj = cur_name;
207 }
208 break;
210 case CMD_MTLLIB:
211 if((tok = strtok(0, SEP))) {
212 FILE *mfile;
213 if(!(mfile = fopen(tok, "rb"))) {
214 fprintf(stderr, "failed to open material library: %s\n", tok);
215 continue;
216 }
218 // load all materials of the mtl file into a vector
219 vector<ObjMat> vmtl;
220 if(!read_materials(mfile, &vmtl)) {
221 continue;
222 }
223 fclose(mfile);
225 // and add them all to the scene
226 for(size_t i=0; i<vmtl.size(); i++) {
227 Material mat;
228 mat.ambient = vmtl[i].ambient;
229 mat.diffuse = vmtl[i].diffuse;
230 mat.specular = vmtl[i].specular;
231 mat.shininess = vmtl[i].shininess;
232 mat.emissive = vmtl[i].emissive;
233 mat.alpha = vmtl[i].alpha;
235 if(vmtl[i].tex_dif.length()) {
236 mat.tex[TEX_DIFFUSE] = load_texture(vmtl[i].tex_dif.c_str());
237 }
238 if(vmtl[i].tex_refl.length()) {
239 mat.tex[TEX_ENVMAP] = load_texture(vmtl[i].tex_refl.c_str());
240 }
242 matlib[vmtl[i].name] = mat;
243 }
244 }
245 break;
247 case CMD_USEMTL:
248 if((tok = strtok(0, SEP))) {
249 obj.cur_mat = tok;
250 } else {
251 obj.cur_mat = "";
252 }
253 break;
255 case CMD_F:
256 if(!parse_face(&face)) {
257 continue;
258 }
260 // convert negative indices to regular indices
261 for(int i=0; i<4; i++) {
262 if(face.v[i] < 0 && face.v[i] != INVALID_IDX) {
263 face.v[i] = obj.v.size() + face.v[i];
264 }
265 if(face.n[i] < 0 && face.n[i] != INVALID_IDX) {
266 face.n[i] = obj.vn.size() + face.n[i];
267 }
268 if(face.t[i] < 0 && face.t[i] != INVALID_IDX) {
269 face.t[i] = obj.vt.size() + face.t[i];
270 }
271 }
273 // break quads into triangles if needed
274 obj.f.push_back(face);
275 if(face.elem == 4) {
276 face.v[1] = face.v[2];
277 face.n[1] = face.n[2];
278 face.t[1] = face.t[2];
280 face.v[2] = face.v[3];
281 face.n[2] = face.n[3];
282 face.t[2] = face.t[3];
284 obj.f.push_back(face);
285 }
286 break;
288 default:
289 break; // ignore unknown commands
290 }
292 prev_cmd = cmd;
293 }
295 // reached end of file...
296 if(!obj.f.empty()) {
297 Object *robj = cons_object(&obj);
298 robj->mtl = matlib[obj.cur_mat];
299 add_object(robj);
300 obj_added++;
301 }
303 return obj_added > 0;
304 }
306 static Object *cons_object(ObjFile *obj)
307 {
308 Object *robj;
309 Vector3 *varr, *narr;
310 Vector2 *tarr;
312 int nelem = obj->f.size() * 3;
314 assert(sizeof(Vector3) == 3 * sizeof(float));
315 assert(sizeof(Vector2) == 2 * sizeof(float));
317 try {
318 robj = new Object;
319 varr = new Vector3[nelem];
320 narr = new Vector3[nelem];
321 tarr = new Vector2[nelem];
322 }
323 catch(...) {
324 return 0;
325 }
326 if(obj->cur_obj.length() > 0) {
327 robj->set_name(obj->cur_obj.c_str());
328 }
330 // need at least one of each element
331 bool added_norm = false, added_tc = false;
332 if(obj->vn.empty()) {
333 obj->vn.push_back(Vector3(0, 0, 0));
334 added_norm = true;
335 }
336 if(obj->vt.empty()) {
337 obj->vt.push_back(Vector2(0, 0));
338 added_tc = true;
339 }
341 for(size_t i=0; i<obj->f.size(); i++) {
342 for(int j=0; j<3; j++) {
343 int idx = i * 3 + j;
344 ObjFace *f = &obj->f[i];
346 varr[idx] = obj->v[f->v[j]];
347 narr[idx] = obj->vn[f->n[j] < 0 ? 0 : f->n[j]];
349 float t = obj->vt[f->t[j] < 0 ? 0 : f->t[j]].x;
350 float s = obj->vt[f->t[j] < 0 ? 0 : f->t[j]].y;
351 tarr[idx] = Vector2(t, s);
352 }
353 }
355 if(added_norm) {
356 obj->vn.pop_back();
357 }
358 if(added_tc) {
359 obj->vt.pop_back();
360 }
362 Mesh *mesh = new Mesh;
363 mesh->set_attrib(MESH_ATTR_VERTEX, nelem, 3, &varr->x);
364 mesh->set_attrib(MESH_ATTR_NORMAL, nelem, 3, &narr->x);
365 mesh->set_attrib(MESH_ATTR_TEXCOORD, nelem, 2, &tarr->x);
366 robj->set_mesh(mesh);
368 printf("loaded object %s: %d faces\n", obj->cur_obj.c_str(), nelem / 3);
370 delete [] varr;
371 delete [] narr;
372 delete [] tarr;
373 return robj;
374 }
376 static bool read_materials(FILE *fp, vector<ObjMat> *vmtl)
377 {
378 ObjMat mat;
380 for(;;) {
381 char line[BUF_SZ];
382 fgets(line, sizeof line, fp);
383 if(feof(fp)) {
384 break;
385 }
387 char *tok;
388 if(!(tok = strtok(line, SEP))) {
389 continue;
390 }
392 int cmd;
393 if((cmd = get_cmd(tok)) == -1) {
394 continue;
395 }
397 switch(cmd) {
398 case CMD_NEWMTL:
399 // add the previous material, and start a new one
400 if(mat.name.length() > 0) {
401 vmtl->push_back(mat);
402 mat.reset();
403 }
404 if((tok = strtok(0, SEP))) {
405 mat.name = tok;
406 }
407 break;
409 case CMD_KE:
410 parse_color(&mat.emissive);
411 break;
413 case CMD_KA:
414 parse_color(&mat.ambient);
415 break;
417 case CMD_KD:
418 parse_color(&mat.diffuse);
419 break;
421 case CMD_KS:
422 parse_color(&mat.specular);
423 break;
425 case CMD_NS:
426 if((tok = strtok(0, SEP)) && is_float(tok)) {
427 mat.shininess = atof(tok);
428 }
429 break;
431 case CMD_NI:
432 if((tok = strtok(0, SEP)) && is_float(tok)) {
433 mat.ior = atof(tok);
434 }
435 break;
437 case CMD_D:
438 case CMD_TR:
439 {
440 Color c;
441 if(parse_color(&c)) {
442 mat.alpha = cmd == CMD_D ? c.x : 1.0 - c.x;
443 }
444 }
445 break;
447 case CMD_MAP_KD:
448 mat.tex_dif = parse_map();
449 break;
451 case CMD_MAP_REFL:
452 mat.tex_refl = parse_map();
453 break;
455 default:
456 break;
457 }
458 }
460 if(mat.name.length() > 0) {
461 vmtl->push_back(mat);
462 }
463 return true;
464 }
466 static int get_cmd(char *str)
467 {
468 char *s = str;
469 while((*s = toupper(*s))) s++;
471 for(int i=0; cmd_names[i]; i++) {
472 if(strcmp(str, cmd_names[i]) == 0) {
473 return i;
474 }
475 }
476 return CMD_UNK;
477 }
479 static bool is_int(const char *str)
480 {
481 char *tmp;
482 strtol(str, &tmp, 10);
483 return tmp != str;
484 }
486 static bool is_float(const char *str)
487 {
488 char *tmp;
489 strtod(str, &tmp);
490 return tmp != str;
491 }
493 static bool parse_vec(Vector3 *vec)
494 {
495 for(int i=0; i<3; i++) {
496 char *tok;
498 if(!(tok = strtok(0, SEP)) || !is_float(tok)) {
499 if(i < 2) {
500 return false;
501 }
502 vec->z = 0.0;
503 } else {
504 (*vec)[i] = atof(tok);
505 }
506 }
507 return true;
508 }
510 static bool parse_color(Color *col)
511 {
512 for(int i=0; i<3; i++) {
513 char *tok;
515 if(!(tok = strtok(0, SEP)) || !is_float(tok)) {
516 col->y = col->z = col->x;
517 return i > 0 ? true : false;
518 }
519 (*col)[i] = atof(tok);
520 }
521 return true;
522 }
524 static bool parse_face(ObjFace *face)
525 {
526 char *tok[] = {0, 0, 0, 0};
527 face->elem = 0;
529 for(int i=0; i<4; i++) {
530 if((!(tok[i] = strtok(0, SEP)) || !is_int(tok[i]))) {
531 if(i < 3) return false; // less than 3 verts? not a polygon
532 } else {
533 face->elem++;
534 }
535 }
537 for(int i=0; i<4; i++) {
538 char *subtok = tok[i];
540 if(!subtok || !*subtok || !is_int(subtok)) {
541 if(i < 3) {
542 return false;
543 }
544 face->v[i] = INVALID_IDX;
545 } else {
546 face->v[i] = atoi(subtok);
547 if(face->v[i] > 0) face->v[i]--; /* convert to 0-based */
548 }
550 while(subtok && *subtok && *subtok != '/') {
551 subtok++;
552 }
553 if(subtok && *subtok && *++subtok && is_int(subtok)) {
554 face->t[i] = atoi(subtok);
555 if(face->t[i] > 0) face->t[i]--; /* convert to 0-based */
556 } else {
557 face->t[i] = INVALID_IDX;
558 }
560 while(subtok && *subtok && *subtok != '/') {
561 subtok++;
562 }
563 if(subtok && *subtok && *++subtok && is_int(subtok)) {
564 face->n[i] = atoi(subtok);
565 if(face->n[i] > 0) face->n[i]--; /* convert to 0-based */
566 } else {
567 face->n[i] = INVALID_IDX;
568 }
569 }
571 return true;
572 }
574 static const char *parse_map()
575 {
576 char *tok, *prev = 0;
578 while((tok = strtok(0, SEP))) {
579 prev = tok;
580 }
582 return prev ? prev : "";
583 }