nuclear@3: #include nuclear@3: #include nuclear@3: #include nuclear@3: #include nuclear@4: #include nuclear@3: #include nuclear@3: #include nuclear@3: #include nuclear@3: #include nuclear@3: #include nuclear@3: #include "scene.h" nuclear@3: nuclear@3: using namespace std; nuclear@3: nuclear@3: #define COMMANDS \ nuclear@3: CMD(V), \ nuclear@3: CMD(VN), \ nuclear@3: CMD(VT), \ nuclear@3: CMD(F), \ nuclear@3: CMD(O), \ nuclear@3: CMD(G), \ nuclear@3: CMD(MTLLIB), \ nuclear@3: CMD(USEMTL), \ nuclear@3: CMD(NEWMTL), \ nuclear@3: CMD(KA), \ nuclear@3: CMD(KD), \ nuclear@3: CMD(KS), \ nuclear@3: CMD(NS), \ nuclear@3: CMD(NI), \ nuclear@3: CMD(D), \ nuclear@3: CMD(TR), \ nuclear@3: CMD(MAP_KD), \ nuclear@3: CMD(MAP_KS), \ nuclear@3: CMD(MAP_NS), \ nuclear@3: CMD(MAP_D), \ nuclear@3: CMD(REFL), \ nuclear@3: CMD(BUMP) nuclear@3: nuclear@3: #define CMD(x) CMD_##x nuclear@3: enum { nuclear@3: COMMANDS, nuclear@3: CMD_UNK nuclear@3: }; nuclear@3: #undef CMD nuclear@3: nuclear@3: #define CMD(x) #x nuclear@3: static const char *cmd_names[] = { nuclear@3: COMMANDS, nuclear@3: 0 nuclear@3: }; nuclear@3: #undef CMD nuclear@3: nuclear@3: nuclear@3: struct ObjFace { nuclear@3: int elem; nuclear@3: int v[4], n[4], t[4]; nuclear@3: }; nuclear@3: nuclear@3: struct ObjFile { nuclear@3: string cur_obj, cur_mat; nuclear@3: vector v, vn; nuclear@3: vector vt; nuclear@3: vector f; nuclear@3: }; nuclear@3: nuclear@3: typedef Vector3 Color; nuclear@3: nuclear@3: struct ObjMat { nuclear@3: string name; // newmtl nuclear@3: Color ambient, diffuse, specular; // Ka, Kd, Ks nuclear@3: float shininess; // Ns nuclear@3: float ior; // Ni nuclear@3: float alpha; // d, Tr nuclear@3: nuclear@3: string tex_dif, tex_spec, tex_shin, tex_alpha; // map_Kd, map_Ks, map_Ns, map_d nuclear@3: string tex_refl; // refl -type sphere|cube file nuclear@3: string tex_bump; // bump nuclear@3: nuclear@3: ObjMat() { reset(); } nuclear@3: nuclear@3: void reset() { nuclear@3: ambient = diffuse = Color(0.5, 0.5, 0.5); nuclear@3: specular = Color(0.0, 0.0, 0.0); nuclear@3: name = tex_dif = tex_spec = tex_shin = tex_alpha = tex_refl = tex_bump = ""; nuclear@3: shininess = 0; nuclear@3: ior = alpha = 1; nuclear@3: } nuclear@3: }; nuclear@3: nuclear@3: static bool read_materials(FILE *fp, vector *vmtl); nuclear@3: static Object *cons_object(ObjFile *obj); nuclear@3: nuclear@3: static int get_cmd(char *str); nuclear@3: static bool is_int(const char *str); nuclear@3: static bool is_float(const char *str); nuclear@3: static bool parse_vec(Vector3 *vec); nuclear@3: static bool parse_color(Color *col); nuclear@3: static bool parse_face(ObjFace *face); nuclear@3: static const char *parse_map(); nuclear@3: nuclear@3: nuclear@3: static map matlib; nuclear@3: nuclear@3: nuclear@3: #define INVALID_IDX INT_MIN nuclear@3: nuclear@3: #define SEP " \t\n\r\v" nuclear@3: #define BUF_SZ 512 nuclear@3: bool Scene::load_obj(FILE *fp) nuclear@3: { nuclear@3: static int seq; nuclear@3: char cur_name[16]; nuclear@3: stringstream sstr; nuclear@3: nuclear@3: ObjFile obj; nuclear@3: nuclear@3: sprintf(cur_name, "default%02d.obj", seq++); nuclear@3: obj.cur_obj = cur_name; nuclear@3: nuclear@3: int prev_cmd = 0, obj_added = 0; nuclear@3: for(;;) { nuclear@3: Vector3 vec; nuclear@3: ObjFace face; nuclear@3: nuclear@3: char line[BUF_SZ]; nuclear@3: fgets(line, sizeof line, fp); nuclear@3: if(feof(fp)) { nuclear@3: break; nuclear@3: } nuclear@3: nuclear@3: char *tok; nuclear@3: if(!(tok = strtok(line, SEP))) { nuclear@3: continue; // ignore empty lines nuclear@3: } nuclear@3: nuclear@3: int cmd; nuclear@3: if((cmd = get_cmd(tok)) == -1) { nuclear@3: continue; // ignore unknown commands ... nuclear@3: } nuclear@3: nuclear@3: switch(cmd) { nuclear@3: case CMD_V: nuclear@3: if(!parse_vec(&vec)) { nuclear@3: continue; nuclear@3: } nuclear@3: obj.v.push_back(vec); nuclear@3: break; nuclear@3: nuclear@3: case CMD_VN: nuclear@3: if(!parse_vec(&vec)) { nuclear@3: continue; nuclear@3: } nuclear@3: obj.vn.push_back(vec); nuclear@3: break; nuclear@3: nuclear@3: case CMD_VT: nuclear@3: if(!parse_vec(&vec)) { nuclear@3: continue; nuclear@3: } nuclear@3: vec.y = 1.0 - vec.y; nuclear@3: obj.vt.push_back(Vector2(vec.x, vec.y)); nuclear@3: break; nuclear@3: nuclear@3: case CMD_O: nuclear@3: case CMD_G: nuclear@3: if(prev_cmd == CMD_O || prev_cmd == CMD_G) { nuclear@3: break; // just in case we've got both of them in a row nuclear@3: } nuclear@3: /* if we have any previous data, group them up, add the object nuclear@3: * and continue with the new one... nuclear@3: */ nuclear@3: if(!obj.f.empty()) { nuclear@3: Object *robj = cons_object(&obj); nuclear@3: robj->mtl = matlib[obj.cur_mat]; nuclear@3: add_object(robj); nuclear@3: obj_added++; nuclear@3: nuclear@3: obj.f.clear(); // clean the face list nuclear@3: } nuclear@3: if((tok = strtok(0, SEP))) { nuclear@3: obj.cur_obj = tok; nuclear@3: } else { nuclear@3: sprintf(cur_name, "default%02d.obj", seq++); nuclear@3: obj.cur_obj = cur_name; nuclear@3: } nuclear@3: break; nuclear@3: nuclear@3: case CMD_MTLLIB: nuclear@3: if((tok = strtok(0, SEP))) { nuclear@3: FILE *mfile; nuclear@3: if(!(mfile = fopen(tok, "rb"))) { nuclear@3: fprintf(stderr, "failed to open material library: %s\n", tok); nuclear@3: continue; nuclear@3: } nuclear@3: nuclear@3: // load all materials of the mtl file into a vector nuclear@3: vector vmtl; nuclear@3: if(!read_materials(mfile, &vmtl)) { nuclear@3: continue; nuclear@3: } nuclear@3: fclose(mfile); nuclear@3: nuclear@3: // and add them all to the scene nuclear@3: for(size_t i=0; imtl = matlib[obj.cur_mat]; nuclear@3: add_object(robj); nuclear@3: obj_added++; nuclear@3: } nuclear@3: nuclear@3: return obj_added > 0; nuclear@3: } nuclear@3: nuclear@3: static Object *cons_object(ObjFile *obj) nuclear@3: { nuclear@3: Object *robj; nuclear@3: Vector3 *varr, *narr; nuclear@3: Vector2 *tarr; nuclear@3: nuclear@3: int nelem = obj->f.size() * 3; nuclear@3: nuclear@4: assert(sizeof(Vector3) == 3 * sizeof(float)); nuclear@4: assert(sizeof(Vector2) == 2 * sizeof(float)); nuclear@4: nuclear@3: try { nuclear@3: robj = new Object; nuclear@3: varr = new Vector3[nelem]; nuclear@3: narr = new Vector3[nelem]; nuclear@3: tarr = new Vector2[nelem]; nuclear@3: } nuclear@3: catch(...) { nuclear@3: return 0; nuclear@3: } nuclear@4: if(obj->cur_obj.length() > 0) { nuclear@3: robj->set_name(obj->cur_obj.c_str()); nuclear@4: } nuclear@3: nuclear@3: // need at least one of each element nuclear@3: bool added_norm = false, added_tc = false; nuclear@3: if(obj->vn.empty()) { nuclear@3: obj->vn.push_back(Vector3(0, 0, 0)); nuclear@4: added_norm = true; nuclear@3: } nuclear@3: if(obj->vt.empty()) { nuclear@3: obj->vt.push_back(Vector2(0, 0)); nuclear@3: added_tc = true; nuclear@3: } nuclear@3: nuclear@3: for(size_t i=0; if.size(); i++) { nuclear@3: for(int j=0; j<3; j++) { nuclear@3: int idx = i * 3 + j; nuclear@3: ObjFace *f = &obj->f[i]; nuclear@3: nuclear@3: varr[idx] = obj->v[f->v[j]]; nuclear@3: narr[idx] = obj->vn[f->n[j] < 0 ? 0 : f->n[j]]; nuclear@3: nuclear@3: float t = obj->vt[f->t[j] < 0 ? 0 : f->t[j]].x; nuclear@3: float s = obj->vt[f->t[j] < 0 ? 0 : f->t[j]].y; nuclear@3: tarr[idx] = Vector2(t, s); nuclear@3: } nuclear@3: } nuclear@3: nuclear@3: if(added_norm) { nuclear@3: obj->vn.pop_back(); nuclear@3: } nuclear@3: if(added_tc) { nuclear@3: obj->vt.pop_back(); nuclear@3: } nuclear@3: nuclear@4: Mesh *mesh = new Mesh; nuclear@3: mesh->set_attrib(MESH_ATTR_VERTEX, nelem, 3, &varr->x); nuclear@3: mesh->set_attrib(MESH_ATTR_NORMAL, nelem, 3, &narr->x); nuclear@3: mesh->set_attrib(MESH_ATTR_TEXCOORD, nelem, 2, &tarr->x); nuclear@4: robj->set_mesh(mesh); nuclear@4: nuclear@4: printf("loaded object %s: %d faces\n", obj->cur_obj.c_str(), nelem / 3); nuclear@3: nuclear@3: delete [] varr; nuclear@3: delete [] narr; nuclear@3: delete [] tarr; nuclear@3: return robj; nuclear@3: } nuclear@3: nuclear@3: static bool read_materials(FILE *fp, vector *vmtl) nuclear@3: { nuclear@3: ObjMat mat; nuclear@3: nuclear@3: for(;;) { nuclear@3: char line[BUF_SZ]; nuclear@3: fgets(line, sizeof line, fp); nuclear@3: if(feof(fp)) { nuclear@3: break; nuclear@3: } nuclear@3: nuclear@3: char *tok; nuclear@3: if(!(tok = strtok(line, SEP))) { nuclear@3: continue; nuclear@3: } nuclear@3: nuclear@3: int cmd; nuclear@3: if((cmd = get_cmd(tok)) == -1) { nuclear@3: continue; nuclear@3: } nuclear@3: nuclear@3: switch(cmd) { nuclear@3: case CMD_NEWMTL: nuclear@3: // add the previous material, and start a new one nuclear@3: if(mat.name.length() > 0) { nuclear@3: vmtl->push_back(mat); nuclear@3: mat.reset(); nuclear@3: } nuclear@3: if((tok = strtok(0, SEP))) { nuclear@3: mat.name = tok; nuclear@3: } nuclear@3: break; nuclear@3: nuclear@3: case CMD_KA: nuclear@3: parse_color(&mat.ambient); nuclear@3: break; nuclear@3: nuclear@3: case CMD_KD: nuclear@3: parse_color(&mat.diffuse); nuclear@3: break; nuclear@3: nuclear@3: case CMD_KS: nuclear@3: parse_color(&mat.specular); nuclear@3: break; nuclear@3: nuclear@3: case CMD_NS: nuclear@3: if((tok = strtok(0, SEP)) && is_float(tok)) { nuclear@3: mat.shininess = atof(tok); nuclear@3: } nuclear@3: break; nuclear@3: nuclear@3: case CMD_NI: nuclear@3: if((tok = strtok(0, SEP)) && is_float(tok)) { nuclear@3: mat.ior = atof(tok); nuclear@3: } nuclear@3: break; nuclear@3: nuclear@3: case CMD_D: nuclear@3: case CMD_TR: nuclear@3: { nuclear@3: Color c; nuclear@3: if(parse_color(&c)) { nuclear@3: mat.alpha = cmd == CMD_D ? c.x : 1.0 - c.x; nuclear@3: } nuclear@3: } nuclear@3: break; nuclear@3: nuclear@3: case CMD_MAP_KD: nuclear@3: mat.tex_dif = parse_map(); nuclear@3: break; nuclear@3: nuclear@3: default: nuclear@3: break; nuclear@3: } nuclear@3: } nuclear@3: nuclear@3: if(mat.name.length() > 0) { nuclear@3: vmtl->push_back(mat); nuclear@3: } nuclear@3: return true; nuclear@3: } nuclear@3: nuclear@3: static int get_cmd(char *str) nuclear@3: { nuclear@3: char *s = str; nuclear@3: while((*s = toupper(*s))) s++; nuclear@3: nuclear@3: for(int i=0; cmd_names[i]; i++) { nuclear@3: if(strcmp(str, cmd_names[i]) == 0) { nuclear@3: return i; nuclear@3: } nuclear@3: } nuclear@3: return CMD_UNK; nuclear@3: } nuclear@3: nuclear@3: static bool is_int(const char *str) nuclear@3: { nuclear@3: char *tmp; nuclear@3: strtol(str, &tmp, 10); nuclear@3: return tmp != str; nuclear@3: } nuclear@3: nuclear@3: static bool is_float(const char *str) nuclear@3: { nuclear@3: char *tmp; nuclear@3: strtod(str, &tmp); nuclear@3: return tmp != str; nuclear@3: } nuclear@3: nuclear@3: static bool parse_vec(Vector3 *vec) nuclear@3: { nuclear@3: for(int i=0; i<3; i++) { nuclear@3: char *tok; nuclear@3: nuclear@3: if(!(tok = strtok(0, SEP)) || !is_float(tok)) { nuclear@3: if(i < 2) { nuclear@3: return false; nuclear@3: } nuclear@3: vec->z = 0.0; nuclear@3: } else { nuclear@3: (*vec)[i] = atof(tok); nuclear@3: } nuclear@3: } nuclear@3: return true; nuclear@3: } nuclear@3: nuclear@3: static bool parse_color(Color *col) nuclear@3: { nuclear@3: for(int i=0; i<3; i++) { nuclear@3: char *tok; nuclear@3: nuclear@3: if(!(tok = strtok(0, SEP)) || !is_float(tok)) { nuclear@3: col->y = col->z = col->x; nuclear@3: return i > 0 ? true : false; nuclear@3: } nuclear@3: (*col)[i] = atof(tok); nuclear@3: } nuclear@3: return true; nuclear@3: } nuclear@3: nuclear@3: static bool parse_face(ObjFace *face) nuclear@3: { nuclear@3: char *tok[] = {0, 0, 0, 0}; nuclear@3: face->elem = 0; nuclear@3: nuclear@3: for(int i=0; i<4; i++) { nuclear@3: if((!(tok[i] = strtok(0, SEP)) || !is_int(tok[i]))) { nuclear@3: if(i < 3) return false; // less than 3 verts? not a polygon nuclear@3: } else { nuclear@3: face->elem++; nuclear@3: } nuclear@3: } nuclear@3: nuclear@3: for(int i=0; i<4; i++) { nuclear@3: char *subtok = tok[i]; nuclear@3: nuclear@3: if(!subtok || !*subtok || !is_int(subtok)) { nuclear@3: if(i < 3) { nuclear@3: return false; nuclear@3: } nuclear@3: face->v[i] = INVALID_IDX; nuclear@3: } else { nuclear@3: face->v[i] = atoi(subtok); nuclear@3: if(face->v[i] > 0) face->v[i]--; /* convert to 0-based */ nuclear@3: } nuclear@3: nuclear@3: while(subtok && *subtok && *subtok != '/') { nuclear@3: subtok++; nuclear@3: } nuclear@3: if(subtok && *subtok && *++subtok && is_int(subtok)) { nuclear@3: face->t[i] = atoi(subtok); nuclear@3: if(face->t[i] > 0) face->t[i]--; /* convert to 0-based */ nuclear@3: } else { nuclear@3: face->t[i] = INVALID_IDX; nuclear@3: } nuclear@3: nuclear@3: while(subtok && *subtok && *subtok != '/') { nuclear@3: subtok++; nuclear@3: } nuclear@3: if(subtok && *subtok && *++subtok && is_int(subtok)) { nuclear@3: face->n[i] = atoi(subtok); nuclear@3: if(face->n[i] > 0) face->n[i]--; /* convert to 0-based */ nuclear@3: } else { nuclear@3: face->n[i] = INVALID_IDX; nuclear@3: } nuclear@3: } nuclear@3: nuclear@3: return true; nuclear@3: } nuclear@3: nuclear@3: static const char *parse_map() nuclear@3: { nuclear@3: char *tok, *prev = 0; nuclear@3: nuclear@3: while((tok = strtok(0, SEP))) { nuclear@3: prev = tok; nuclear@3: } nuclear@3: nuclear@3: return prev ? prev : ""; nuclear@3: }