nuclear@6: #include nuclear@6: #include nuclear@6: #include John@15: #include nuclear@6: #include nuclear@8: #include nuclear@6: #include nuclear@6: #include nuclear@6: #include nuclear@6: #include "mesh.h" nuclear@6: John@11: #ifndef PATH_MAX John@11: #define PATH_MAX 512 John@11: #endif John@11: nuclear@6: using namespace std; nuclear@6: nuclear@8: #define COMMANDS \ nuclear@6: CMD(V), \ nuclear@6: CMD(VN), \ nuclear@6: CMD(VT), \ nuclear@6: CMD(F), \ nuclear@6: CMD(O), \ nuclear@6: CMD(G), \ nuclear@6: CMD(MTLLIB), \ nuclear@6: CMD(USEMTL), \ nuclear@6: CMD(NEWMTL), \ nuclear@6: CMD(KA), \ nuclear@6: CMD(KD), \ nuclear@6: CMD(KS), \ nuclear@6: CMD(NS), \ nuclear@6: CMD(NI), \ nuclear@6: CMD(D), \ nuclear@6: CMD(TR), \ nuclear@6: CMD(MAP_KD), \ nuclear@6: CMD(MAP_KS), \ nuclear@6: CMD(MAP_NS), \ nuclear@6: CMD(MAP_D), \ nuclear@6: CMD(REFL), \ nuclear@6: CMD(BUMP) nuclear@6: nuclear@6: #define CMD(x) CMD_##x nuclear@6: enum { nuclear@6: COMMANDS, nuclear@6: CMD_UNK nuclear@6: }; nuclear@6: #undef CMD nuclear@6: nuclear@6: #define CMD(x) #x nuclear@6: static const char *cmd_names[] = { nuclear@6: COMMANDS, nuclear@6: 0 nuclear@6: }; nuclear@6: #undef CMD nuclear@6: nuclear@6: struct Vector3 { nuclear@6: float x, y, z; nuclear@6: nuclear@6: Vector3() { x = y = z = 0.0; } nuclear@6: Vector3(float a, float b, float c) { x = a; y = b; z = c; } nuclear@6: }; nuclear@6: nuclear@6: struct Vector2 { nuclear@6: float x, y; nuclear@6: nuclear@6: Vector2() { x = y = 0.0; } nuclear@6: Vector2(float a, float b) { x = a; y = b; } nuclear@6: }; nuclear@6: nuclear@6: struct obj_face { nuclear@6: int elem; nuclear@6: int v[4], n[4], t[4]; nuclear@6: }; nuclear@6: nuclear@6: struct obj_file { nuclear@6: string cur_obj, cur_mat; nuclear@6: vector v, vn, vt; nuclear@6: vector f; nuclear@6: }; nuclear@6: nuclear@6: struct obj_mat { nuclear@6: string name; // newmtl nuclear@6: Vector3 ambient, diffuse, specular; // Ka, Kd, Ks nuclear@6: float shininess; // Ns nuclear@6: float ior; // Ni nuclear@6: float alpha; // d, Tr nuclear@6: nuclear@6: string tex_dif, tex_spec, tex_shin, tex_alpha; // map_Kd, map_Ks, map_Ns, map_d nuclear@6: string tex_refl; // refl -type sphere|cube file nuclear@6: string tex_bump; // bump nuclear@6: nuclear@6: obj_mat() { reset(); } nuclear@6: nuclear@6: void reset() { nuclear@6: ambient = diffuse = Vector3(0.5, 0.5, 0.5); nuclear@6: specular = Vector3(0.0, 0.0, 0.0); nuclear@6: name = tex_dif = tex_spec = tex_shin = tex_alpha = tex_refl = tex_bump = ""; nuclear@6: shininess = 0; nuclear@6: ior = alpha = 1; nuclear@6: } nuclear@6: }; nuclear@6: nuclear@6: static bool read_materials(FILE *fp, vector *vmtl); nuclear@6: static Mesh *cons_mesh(obj_file *obj); nuclear@6: nuclear@6: static int get_cmd(char *str); nuclear@6: static bool is_int(const char *str); nuclear@6: static bool is_float(const char *str); nuclear@6: static bool parse_vec(Vector3 *vec); nuclear@6: static bool parse_color(Vector3 *col); nuclear@6: static bool parse_face(obj_face *face); nuclear@6: static const char *parse_map(); nuclear@6: nuclear@6: static bool find_file(char *res, int sz, const char *fname, const char *path = ".", const char *mode = "rb"); nuclear@6: static const char *dirname(const char *str); nuclear@6: nuclear@6: static map matnames; nuclear@6: John@15: John@15: #define FEQ(a, b) (fabs((a) - (b)) < 1e-8) John@15: bool Face::operator ==(const Face &f) const John@15: { John@15: for(int i=0; i<3; i++) { John@15: for(int j=0; j<3; j++) { John@15: if(!FEQ(v[i].pos[j], f.v[i].pos[j])) { John@15: return false; John@15: } John@15: if(!FEQ(v[i].normal[j], f.v[i].normal[j])) { John@15: return false; John@15: } John@15: } John@15: if(!FEQ(normal[i], f.normal[i])) { John@15: return false; John@15: } John@15: } John@15: return true; John@15: } John@15: nuclear@13: bool Scene::add_mesh(Mesh *m) nuclear@13: { nuclear@13: // make sure triangles have material ids nuclear@13: for(size_t i=0; ifaces.size(); i++) { nuclear@13: m->faces[i].matid = m->matid; nuclear@13: } nuclear@13: meshes.push_back(m); nuclear@13: return true; nuclear@13: } nuclear@13: John@14: int Scene::get_num_meshes() const John@14: { John@14: return (int)meshes.size(); John@14: } John@14: nuclear@13: int Scene::get_num_faces() const nuclear@13: { nuclear@13: int num_faces = 0; nuclear@13: for(size_t i=0; ifaces.size(); nuclear@13: } nuclear@13: printf("get_num_faces() = %d\n", num_faces); nuclear@13: return num_faces; nuclear@13: } nuclear@13: John@14: int Scene::get_num_materials() const John@14: { John@14: return (int)matlib.size(); John@14: } John@14: John@14: Material *Scene::get_materials() John@14: { John@14: if(matlib.empty()) { John@14: return 0; John@14: } John@14: return &matlib[0]; John@14: } John@14: John@14: const Material *Scene::get_materials() const John@14: { John@14: if(matlib.empty()) { John@14: return 0; John@14: } John@14: return &matlib[0]; John@14: } John@14: nuclear@6: nuclear@6: #define INVALID_IDX INT_MIN nuclear@6: nuclear@6: #define SEP " \t\n\r\v" nuclear@6: #define BUF_SZ 512 nuclear@6: nuclear@6: bool Scene::load(const char *fname) nuclear@6: { nuclear@6: FILE *fp; nuclear@6: nuclear@6: if(!(fp = fopen(fname, "rb"))) { nuclear@6: fprintf(stderr, "failed to open %s: %s\n", fname, strerror(errno)); nuclear@6: return false; nuclear@6: } nuclear@6: nuclear@6: bool res = load(fp); nuclear@6: fclose(fp); nuclear@6: return res; nuclear@6: } nuclear@6: nuclear@6: bool Scene::load(FILE *fp) nuclear@6: { nuclear@6: static int seq; nuclear@6: char cur_name[16]; nuclear@6: nuclear@6: obj_file obj; nuclear@8: nuclear@6: sprintf(cur_name, "default%02d.obj", seq++); nuclear@6: obj.cur_obj = cur_name; nuclear@6: nuclear@6: int prev_cmd = 0, obj_added = 0; nuclear@6: for(;;) { nuclear@6: Vector3 vec; nuclear@6: obj_face face; nuclear@8: nuclear@6: char line[BUF_SZ]; nuclear@6: fgets(line, sizeof line, fp); nuclear@6: if(feof(fp)) { nuclear@6: break; nuclear@6: } nuclear@6: nuclear@6: char *tok; nuclear@6: if(!(tok = strtok(line, SEP))) { nuclear@6: continue; // ignore empty lines nuclear@6: } nuclear@6: nuclear@6: int cmd; nuclear@6: if((cmd = get_cmd(tok)) == -1) { nuclear@6: continue; // ignore unknown commands ... nuclear@6: } nuclear@6: nuclear@6: switch(cmd) { nuclear@6: case CMD_V: nuclear@6: if(!parse_vec(&vec)) { nuclear@6: continue; nuclear@6: } nuclear@6: obj.v.push_back(vec); nuclear@6: break; nuclear@6: nuclear@6: case CMD_VN: nuclear@6: if(!parse_vec(&vec)) { nuclear@6: continue; nuclear@6: } nuclear@6: obj.vn.push_back(vec); nuclear@6: break; nuclear@6: nuclear@6: case CMD_VT: nuclear@6: if(!parse_vec(&vec)) { nuclear@6: continue; nuclear@6: } nuclear@6: vec.y = 1.0 - vec.y; nuclear@6: obj.vt.push_back(vec); nuclear@6: break; nuclear@6: nuclear@6: case CMD_O: nuclear@6: case CMD_G: nuclear@6: if(prev_cmd == CMD_O || prev_cmd == CMD_G) { nuclear@6: break; // just in case we've got both of them in a row nuclear@6: } nuclear@6: /* if we have any previous data, group them up, add the object nuclear@6: * and continue with the new one... nuclear@6: */ nuclear@6: if(!obj.f.empty()) { nuclear@6: Mesh *mesh = cons_mesh(&obj); nuclear@6: mesh->matid = matnames[obj.cur_mat]; nuclear@13: add_mesh(mesh); nuclear@6: obj_added++; nuclear@6: nuclear@6: obj.f.clear(); // clean the face list nuclear@6: } nuclear@6: if((tok = strtok(0, SEP))) { nuclear@6: obj.cur_obj = tok; nuclear@6: } else { nuclear@6: sprintf(cur_name, "default%02d.obj", seq++); nuclear@6: obj.cur_obj = cur_name; nuclear@6: } nuclear@6: break; nuclear@6: nuclear@6: case CMD_MTLLIB: nuclear@6: if((tok = strtok(0, SEP))) { nuclear@6: char path[PATH_MAX]; nuclear@6: nuclear@6: sprintf(path, ".:%s", dirname(tok)); nuclear@6: if(!find_file(path, PATH_MAX, tok, path)) { nuclear@6: fprintf(stderr, "material library not found: %s\n", tok); nuclear@6: continue; nuclear@6: } nuclear@6: nuclear@6: FILE *mfile; nuclear@6: if(!(mfile = fopen(path, "rb"))) { nuclear@6: fprintf(stderr, "failed to open material library: %s\n", path); nuclear@6: continue; nuclear@6: } nuclear@6: nuclear@6: // load all materials of the mtl file into a vector nuclear@6: vector vmtl; nuclear@6: if(!read_materials(mfile, &vmtl)) { nuclear@6: continue; nuclear@6: } nuclear@6: fclose(mfile); nuclear@6: nuclear@6: // and add them all to the scene nuclear@6: for(size_t i=0; imatid = matnames[obj.cur_mat]; nuclear@13: add_mesh(mesh); nuclear@6: obj_added++; nuclear@6: } nuclear@6: nuclear@6: return obj_added > 0; nuclear@6: } nuclear@6: nuclear@6: static Mesh *cons_mesh(obj_file *obj) nuclear@6: { nuclear@6: Mesh *mesh; nuclear@6: nuclear@6: // need at least one of each element nuclear@6: bool added_norm = false, added_tc = false; nuclear@6: if(obj->vn.empty()) { nuclear@6: obj->vn.push_back(Vector3(0, 0, 0)); nuclear@6: added_norm = true; nuclear@6: } nuclear@6: if(obj->vt.empty()) { nuclear@6: obj->vt.push_back(Vector3(0, 0, 0)); nuclear@6: added_tc = true; nuclear@6: } nuclear@6: nuclear@6: mesh = new Mesh; nuclear@6: nuclear@6: for(size_t i=0; if.size(); i++) { nuclear@6: Face face; nuclear@6: nuclear@6: for(int j=0; j<3; j++) { nuclear@6: obj_face *f = &obj->f[i]; nuclear@6: nuclear@6: face.v[j].pos[0] = obj->v[f->v[j]].x; nuclear@6: face.v[j].pos[1] = obj->v[f->v[j]].y; nuclear@6: face.v[j].pos[2] = obj->v[f->v[j]].z; nuclear@13: face.v[j].pos[3] = 0.0; nuclear@6: nuclear@6: int nidx = f->n[j] < 0 ? 0 : f->n[j]; nuclear@6: face.v[j].normal[0] = obj->vn[nidx].x; nuclear@6: face.v[j].normal[1] = obj->vn[nidx].y; nuclear@6: face.v[j].normal[2] = obj->vn[nidx].z; nuclear@13: face.v[j].normal[3] = 0.0; nuclear@6: nuclear@6: int tidx = f->t[j] < 0 ? 0 : f->t[j]; nuclear@6: face.v[j].tex[0] = obj->vt[tidx].x; nuclear@6: face.v[j].tex[1] = obj->vt[tidx].y; nuclear@6: } nuclear@13: nuclear@13: face.normal[0] = face.v[0].normal[0]; nuclear@13: face.normal[1] = face.v[1].normal[1]; nuclear@13: face.normal[2] = face.v[2].normal[2]; nuclear@13: face.normal[3] = 0.0; nuclear@13: nuclear@6: mesh->faces.push_back(face); nuclear@6: } nuclear@6: nuclear@6: if(added_norm) { nuclear@6: obj->vn.pop_back(); nuclear@6: } nuclear@6: if(added_tc) { nuclear@6: obj->vt.pop_back(); nuclear@6: } nuclear@8: nuclear@6: return mesh; nuclear@6: } nuclear@6: nuclear@6: static bool read_materials(FILE *fp, vector *vmtl) nuclear@6: { nuclear@6: obj_mat mat; nuclear@6: nuclear@6: for(;;) { nuclear@6: char line[BUF_SZ]; nuclear@6: fgets(line, sizeof line, fp); nuclear@6: if(feof(fp)) { nuclear@6: break; nuclear@6: } nuclear@6: nuclear@6: char *tok; nuclear@6: if(!(tok = strtok(line, SEP))) { nuclear@6: continue; nuclear@6: } nuclear@6: nuclear@6: int cmd; nuclear@6: if((cmd = get_cmd(tok)) == -1) { nuclear@6: continue; nuclear@6: } nuclear@6: nuclear@6: switch(cmd) { nuclear@6: case CMD_NEWMTL: nuclear@6: // add the previous material, and start a new one nuclear@6: if(mat.name.length() > 0) { nuclear@13: printf("Adding material: %s\n", mat.name.c_str()); nuclear@6: vmtl->push_back(mat); nuclear@6: mat.reset(); nuclear@6: } nuclear@6: if((tok = strtok(0, SEP))) { nuclear@6: mat.name = tok; nuclear@6: } nuclear@6: break; nuclear@6: nuclear@6: case CMD_KA: nuclear@6: parse_color(&mat.ambient); nuclear@6: break; nuclear@6: nuclear@6: case CMD_KD: nuclear@6: parse_color(&mat.diffuse); nuclear@6: break; nuclear@6: nuclear@6: case CMD_KS: nuclear@6: parse_color(&mat.specular); nuclear@6: break; nuclear@6: nuclear@6: case CMD_NS: nuclear@6: if((tok = strtok(0, SEP)) && is_float(tok)) { nuclear@6: mat.shininess = atof(tok); nuclear@6: } nuclear@6: break; nuclear@6: nuclear@6: case CMD_NI: nuclear@6: if((tok = strtok(0, SEP)) && is_float(tok)) { nuclear@6: mat.ior = atof(tok); nuclear@6: } nuclear@6: break; nuclear@6: nuclear@6: case CMD_D: nuclear@6: case CMD_TR: nuclear@6: { nuclear@6: Vector3 c; nuclear@6: if(parse_color(&c)) { nuclear@6: mat.alpha = cmd == CMD_D ? c.x : 1.0 - c.x; nuclear@6: } nuclear@6: } nuclear@6: break; nuclear@6: nuclear@6: case CMD_MAP_KD: nuclear@6: mat.tex_dif = parse_map(); nuclear@6: break; nuclear@6: nuclear@6: default: nuclear@6: break; nuclear@6: } nuclear@6: } nuclear@6: nuclear@6: if(mat.name.length() > 0) { nuclear@13: printf("Adding material: %s\n", mat.name.c_str()); nuclear@6: vmtl->push_back(mat); nuclear@6: } nuclear@6: return true; nuclear@6: } nuclear@6: nuclear@6: static int get_cmd(char *str) nuclear@6: { nuclear@6: char *s = str; nuclear@8: while((*s = toupper(*s))) s++; nuclear@6: nuclear@6: for(int i=0; cmd_names[i]; i++) { nuclear@6: if(strcmp(str, cmd_names[i]) == 0) { nuclear@6: return i; nuclear@6: } nuclear@6: } nuclear@6: return CMD_UNK; nuclear@6: } nuclear@6: nuclear@6: static bool is_int(const char *str) nuclear@6: { nuclear@6: char *tmp; nuclear@6: strtol(str, &tmp, 10); nuclear@6: return tmp != str; nuclear@6: } nuclear@6: nuclear@6: static bool is_float(const char *str) nuclear@6: { nuclear@6: char *tmp; nuclear@6: strtod(str, &tmp); nuclear@6: return tmp != str; nuclear@6: } nuclear@6: nuclear@6: static bool parse_vec(Vector3 *vec) nuclear@6: { nuclear@6: for(int i=0; i<3; i++) { nuclear@6: char *tok; nuclear@6: nuclear@6: if(!(tok = strtok(0, SEP)) || !is_float(tok)) { nuclear@6: if(i < 2) { nuclear@6: return false; nuclear@6: } nuclear@6: vec->z = 0.0; nuclear@6: } else { nuclear@6: float v = atof(tok); nuclear@6: nuclear@6: switch(i) { nuclear@6: case 0: nuclear@6: vec->x = v; nuclear@6: break; nuclear@6: case 1: nuclear@6: vec->y = v; nuclear@6: break; nuclear@6: case 2: nuclear@6: vec->z = v; nuclear@6: break; nuclear@6: } nuclear@6: } nuclear@6: } nuclear@6: return true; nuclear@6: } nuclear@6: nuclear@6: static bool parse_color(Vector3 *col) nuclear@6: { nuclear@6: for(int i=0; i<3; i++) { nuclear@6: char *tok; nuclear@6: nuclear@6: if(!(tok = strtok(0, SEP)) || !is_float(tok)) { nuclear@6: col->y = col->z = col->x; nuclear@6: return i > 0 ? true : false; nuclear@6: } nuclear@6: nuclear@6: float v = atof(tok); nuclear@6: switch(i) { nuclear@6: case 0: nuclear@6: col->x = v; nuclear@6: break; nuclear@6: case 1: nuclear@6: col->y = v; nuclear@6: break; nuclear@6: case 2: nuclear@6: col->z = v; nuclear@6: break; nuclear@6: } nuclear@6: } nuclear@6: return true; nuclear@6: } nuclear@6: nuclear@6: static bool parse_face(obj_face *face) nuclear@6: { nuclear@6: char *tok[] = {0, 0, 0, 0}; nuclear@6: face->elem = 0; nuclear@6: nuclear@6: for(int i=0; i<4; i++) { nuclear@6: if((!(tok[i] = strtok(0, SEP)) || !is_int(tok[i]))) { nuclear@6: if(i < 3) return false; // less than 3 verts? not a polygon nuclear@6: } else { nuclear@6: face->elem++; nuclear@6: } nuclear@6: } nuclear@6: nuclear@6: for(int i=0; i<4; i++) { nuclear@6: char *subtok = tok[i]; nuclear@6: nuclear@6: if(!subtok || !*subtok || !is_int(subtok)) { nuclear@6: if(i < 3) { nuclear@6: return false; nuclear@6: } nuclear@6: face->v[i] = INVALID_IDX; nuclear@6: } else { nuclear@6: face->v[i] = atoi(subtok); nuclear@6: if(face->v[i] > 0) face->v[i]--; /* convert to 0-based */ nuclear@6: } nuclear@6: nuclear@6: while(subtok && *subtok && *subtok != '/') { nuclear@6: subtok++; nuclear@6: } nuclear@6: if(subtok && *subtok && *++subtok && is_int(subtok)) { nuclear@6: face->t[i] = atoi(subtok); nuclear@6: if(face->t[i] > 0) face->t[i]--; /* convert to 0-based */ nuclear@6: } else { nuclear@6: face->t[i] = INVALID_IDX; nuclear@6: } nuclear@6: nuclear@6: while(subtok && *subtok && *subtok != '/') { nuclear@6: subtok++; nuclear@6: } nuclear@6: if(subtok && *subtok && *++subtok && is_int(subtok)) { nuclear@6: face->n[i] = atoi(subtok); nuclear@6: if(face->n[i] > 0) face->n[i]--; /* convert to 0-based */ nuclear@6: } else { nuclear@6: face->n[i] = INVALID_IDX; nuclear@6: } nuclear@6: } nuclear@6: nuclear@6: return true; nuclear@6: } nuclear@6: nuclear@6: static const char *parse_map() nuclear@6: { nuclear@6: char *tok, *prev = 0; nuclear@6: nuclear@6: while((tok = strtok(0, SEP))) { nuclear@6: prev = tok; nuclear@6: } nuclear@6: nuclear@6: return prev ? prev : ""; nuclear@6: } nuclear@6: nuclear@6: static bool find_file(char *res, int sz, const char *fname, const char *path, const char *mode) nuclear@6: { nuclear@6: FILE *fp; nuclear@6: const char *beg, *end; nuclear@6: int fnamelen = strlen(fname); nuclear@6: nuclear@6: beg = path; nuclear@6: while(beg && *beg) { nuclear@6: end = beg; nuclear@6: while(*end && *end != ':') { nuclear@6: end++; nuclear@6: } nuclear@6: nuclear@13: int res_len = end - beg; nuclear@13: char *pathname = (char*)alloca(res_len + fnamelen + 2); nuclear@13: memcpy(pathname, beg, res_len); nuclear@13: pathname[res_len] = 0; John@14: if(res_len) { John@14: strcat(pathname, "/"); John@14: } nuclear@6: strcat(pathname, fname); nuclear@6: nuclear@6: if((fp = fopen(pathname, mode))) { nuclear@6: fclose(fp); nuclear@6: strncpy(res, pathname, sz); nuclear@6: return true; nuclear@6: } nuclear@6: nuclear@13: beg += res_len; John@14: if(*beg == ':') beg++; nuclear@6: } nuclear@6: return false; nuclear@6: } nuclear@6: nuclear@6: static const char *dirname(const char *str) nuclear@6: { nuclear@6: static char buf[PATH_MAX]; nuclear@6: nuclear@6: if(!str || !*str) { nuclear@6: strcpy(buf, "."); nuclear@6: } else { nuclear@6: strncpy(buf, str, PATH_MAX); nuclear@6: char *ptr = strrchr(buf, '/'); nuclear@6: John@14: if(ptr && *ptr) { John@14: *ptr = 0; John@14: } else { John@14: strcpy(buf, "."); John@14: } nuclear@6: } nuclear@6: return buf; nuclear@6: }