initial commit, eq circuit emulator
[eqemu] / src / objfile.cc
1 /*
2 eqemu - electronic queue system emulator
3 Copyright (C) 2014  John Tsiombikas <nuclear@member.fsf.org>,
4                     Eleni-Maria Stea <eleni@mutantstargoat.com>
5
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.
10
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.
15
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"
30
31 using namespace std;
32
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)
59
60 #define CMD(x)  CMD_##x
61 enum {
62         COMMANDS,
63         CMD_UNK
64 };
65 #undef CMD
66
67 #define CMD(x)  #x
68 static const char *cmd_names[] = {
69         COMMANDS,
70         0
71 };
72 #undef CMD
73
74
75 struct ObjFace {
76         int elem;
77         int v[4], n[4], t[4];
78 };
79
80 struct ObjFile {
81         string cur_obj, cur_mat;
82         vector<Vector3> v, vn;
83         vector<Vector2> vt;
84         vector<ObjFace> f;
85 };
86
87 typedef Vector3 Color;
88
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
96
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
100
101         ObjMat() { reset(); }
102
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 };
111
112 static bool read_materials(FILE *fp, vector<ObjMat> *vmtl);
113 static Object *cons_object(ObjFile *obj);
114
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();
122
123
124 static map<string, Material> matlib;
125
126
127 #define INVALID_IDX             INT_MIN
128
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;
136
137         ObjFile obj;
138
139         sprintf(cur_name, "default%02d.obj", seq++);
140         obj.cur_obj = cur_name;
141
142         int prev_cmd = 0, obj_added = 0;
143         for(;;) {
144                 Vector3 vec;
145                 ObjFace face;
146
147                 char line[BUF_SZ];
148                 fgets(line, sizeof line, fp);
149                 if(feof(fp)) {
150                         break;
151                 }
152
153                 char *tok;
154                 if(!(tok = strtok(line, SEP))) {
155                         continue; // ignore empty lines
156                 }
157
158                 int cmd;
159                 if((cmd = get_cmd(tok)) == -1) {
160                         continue; // ignore unknown commands ...
161                 }
162
163                 switch(cmd) {
164                 case CMD_V:
165                         if(!parse_vec(&vec)) {
166                                 continue;
167                         }
168                         obj.v.push_back(vec);
169                         break;
170
171                 case CMD_VN:
172                         if(!parse_vec(&vec)) {
173                                 continue;
174                         }
175                         obj.vn.push_back(vec);
176                         break;
177
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;
185
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++;
199
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;
209
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                                 }
217
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);
224
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;
234
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                                         }
241
242                                         matlib[vmtl[i].name] = mat;
243                                 }
244                         }
245                         break;
246
247                 case CMD_USEMTL:
248                         if((tok = strtok(0, SEP))) {
249                                 obj.cur_mat = tok;
250                         } else {
251                                 obj.cur_mat = "";
252                         }
253                         break;
254
255                 case CMD_F:
256                         if(!parse_face(&face)) {
257                                 continue;
258                         }
259
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                         }
272
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];
279
280                                 face.v[2] = face.v[3];
281                                 face.n[2] = face.n[3];
282                                 face.t[2] = face.t[3];
283
284                                 obj.f.push_back(face);
285                         }
286                         break;
287
288                 default:
289                         break;  // ignore unknown commands
290                 }
291
292                 prev_cmd = cmd;
293         }
294
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         }
302
303         return obj_added > 0;
304 }
305
306 static Object *cons_object(ObjFile *obj)
307 {
308         Object *robj;
309         Vector3 *varr, *narr;
310         Vector2 *tarr;
311
312         int nelem = obj->f.size() * 3;
313
314         assert(sizeof(Vector3) == 3 * sizeof(float));
315         assert(sizeof(Vector2) == 2 * sizeof(float));
316
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         }
329
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         }
340
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];
345
346                         varr[idx] = obj->v[f->v[j]];
347                         narr[idx] = obj->vn[f->n[j] < 0 ? 0 : f->n[j]];
348
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         }
354
355         if(added_norm) {
356                 obj->vn.pop_back();
357         }
358         if(added_tc) {
359                 obj->vt.pop_back();
360         }
361
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);
367
368         printf("loaded object %s: %d faces\n", obj->cur_obj.c_str(), nelem / 3);
369
370         delete [] varr;
371         delete [] narr;
372         delete [] tarr;
373         return robj;
374 }
375
376 static bool read_materials(FILE *fp, vector<ObjMat> *vmtl)
377 {
378         ObjMat mat;
379
380         for(;;) {
381                 char line[BUF_SZ];
382                 fgets(line, sizeof line, fp);
383                 if(feof(fp)) {
384                         break;
385                 }
386
387                 char *tok;
388                 if(!(tok = strtok(line, SEP))) {
389                         continue;
390                 }
391
392                 int cmd;
393                 if((cmd = get_cmd(tok)) == -1) {
394                         continue;
395                 }
396
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;
408
409                 case CMD_KE:
410                         parse_color(&mat.emissive);
411                         break;
412
413                 case CMD_KA:
414                         parse_color(&mat.ambient);
415                         break;
416
417                 case CMD_KD:
418                         parse_color(&mat.diffuse);
419                         break;
420
421                 case CMD_KS:
422                         parse_color(&mat.specular);
423                         break;
424
425                 case CMD_NS:
426                         if((tok = strtok(0, SEP)) && is_float(tok)) {
427                                 mat.shininess = atof(tok);
428                         }
429                         break;
430
431                 case CMD_NI:
432                         if((tok = strtok(0, SEP)) && is_float(tok)) {
433                                 mat.ior = atof(tok);
434                         }
435                         break;
436
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;
446
447                 case CMD_MAP_KD:
448                         mat.tex_dif = parse_map();
449                         break;
450
451                 case CMD_MAP_REFL:
452                         mat.tex_refl = parse_map();
453                         break;
454
455                 default:
456                         break;
457                 }
458         }
459
460         if(mat.name.length() > 0) {
461                 vmtl->push_back(mat);
462         }
463         return true;
464 }
465
466 static int get_cmd(char *str)
467 {
468         char *s = str;
469         while((*s = toupper(*s))) s++;
470
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 }
478
479 static bool is_int(const char *str)
480 {
481         char *tmp;
482         strtol(str, &tmp, 10);
483         return tmp != str;
484 }
485
486 static bool is_float(const char *str)
487 {
488         char *tmp;
489         strtod(str, &tmp);
490         return tmp != str;
491 }
492
493 static bool parse_vec(Vector3 *vec)
494 {
495         for(int i=0; i<3; i++) {
496                 char *tok;
497
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 }
509
510 static bool parse_color(Color *col)
511 {
512         for(int i=0; i<3; i++) {
513                 char *tok;
514
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 }
523
524 static bool parse_face(ObjFace *face)
525 {
526         char *tok[] = {0, 0, 0, 0};
527         face->elem = 0;
528
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         }
536
537         for(int i=0; i<4; i++) {
538                 char *subtok = tok[i];
539
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                 }
549
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                 }
559
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         }
570
571         return true;
572 }
573
574 static const char *parse_map()
575 {
576         char *tok, *prev = 0;
577
578         while((tok = strtok(0, SEP))) {
579                 prev = tok;
580         }
581
582         return prev ? prev : "";
583 }