clray
diff src/scene_obj.cc @ 23:51f115e337c2
separated obj loading and vector class
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Fri, 13 Aug 2010 18:20:45 +0100 |
parents | |
children | 0f174fd60f19 |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/scene_obj.cc Fri Aug 13 18:20:45 2010 +0100 1.3 @@ -0,0 +1,656 @@ 1.4 +#include <stdio.h> 1.5 +#include <stdlib.h> 1.6 +#include <string.h> 1.7 +#include <errno.h> 1.8 +#include <ctype.h> 1.9 +#include <limits.h> 1.10 +#include <string> 1.11 +#include <vector> 1.12 +#include <map> 1.13 +#include "scene.h" 1.14 +#include "vector.h" 1.15 + 1.16 +#ifndef PATH_MAX 1.17 +#define PATH_MAX 512 1.18 +#endif 1.19 + 1.20 +#define COMMANDS \ 1.21 + CMD(V), \ 1.22 + CMD(VN), \ 1.23 + CMD(VT), \ 1.24 + CMD(F), \ 1.25 + CMD(O), \ 1.26 + CMD(G), \ 1.27 + CMD(MTLLIB), \ 1.28 + CMD(USEMTL), \ 1.29 + CMD(NEWMTL), \ 1.30 + CMD(KA), \ 1.31 + CMD(KD), \ 1.32 + CMD(KS), \ 1.33 + CMD(KR), \ 1.34 + CMD(NS), \ 1.35 + CMD(NI), \ 1.36 + CMD(D), \ 1.37 + CMD(TR), \ 1.38 + CMD(MAP_KD), \ 1.39 + CMD(MAP_KS), \ 1.40 + CMD(MAP_NS), \ 1.41 + CMD(MAP_D), \ 1.42 + CMD(REFL), \ 1.43 + CMD(BUMP) 1.44 + 1.45 +#define CMD(x) CMD_##x 1.46 +enum { 1.47 + COMMANDS, 1.48 + CMD_UNK 1.49 +}; 1.50 +#undef CMD 1.51 + 1.52 +#define CMD(x) #x 1.53 +static const char *cmd_names[] = { 1.54 + COMMANDS, 1.55 + 0 1.56 +}; 1.57 +#undef CMD 1.58 + 1.59 +struct obj_face { 1.60 + int elem; 1.61 + int v[4], n[4], t[4]; 1.62 +}; 1.63 + 1.64 +struct obj_file { 1.65 + std::string cur_obj, cur_mat; 1.66 + std::vector<Vector3> v, vn, vt; 1.67 + std::vector<obj_face> f; 1.68 +}; 1.69 + 1.70 +struct obj_mat { 1.71 + std::string name; // newmtl <name> 1.72 + Vector3 ambient, diffuse, specular; // Ka, Kd, Ks 1.73 + float shininess; // Ns 1.74 + float ior; // Ni 1.75 + float alpha; // d, Tr 1.76 + float refl; // Kr (my extesnsion) 1.77 + 1.78 + std::string tex_dif, tex_spec, tex_shin, tex_alpha; // map_Kd, map_Ks, map_Ns, map_d 1.79 + std::string tex_refl; // refl -type sphere|cube file 1.80 + std::string tex_bump; // bump 1.81 + 1.82 + obj_mat() { reset(); } 1.83 + 1.84 + void reset() { 1.85 + ambient = diffuse = Vector3(0.5, 0.5, 0.5); 1.86 + specular = Vector3(0.0, 0.0, 0.0); 1.87 + name = tex_dif = tex_spec = tex_shin = tex_alpha = tex_refl = tex_bump = ""; 1.88 + shininess = 0; 1.89 + ior = alpha = 1; 1.90 + refl = 0.0; 1.91 + } 1.92 +}; 1.93 + 1.94 +static bool read_materials(FILE *fp, std::vector<obj_mat> *vmtl); 1.95 +static Mesh *cons_mesh(obj_file *obj); 1.96 + 1.97 +static int get_cmd(char *str); 1.98 +static bool is_int(const char *str); 1.99 +static bool is_float(const char *str); 1.100 +static bool parse_vec(Vector3 *vec); 1.101 +static bool parse_color(Vector3 *col); 1.102 +static bool parse_face(obj_face *face); 1.103 +static const char *parse_map(); 1.104 + 1.105 +static bool find_file(char *res, int sz, const char *fname, const char *path = ".", const char *mode = "rb"); 1.106 +static const char *dirname(const char *str); 1.107 + 1.108 +static std::map<std::string, int> matnames; 1.109 + 1.110 +#define INVALID_IDX INT_MIN 1.111 + 1.112 +#define SEP " \t\n\r\v" 1.113 +#define BUF_SZ 512 1.114 + 1.115 + 1.116 +bool Scene::load(const char *fname) 1.117 +{ 1.118 + FILE *fp; 1.119 + 1.120 + if(!(fp = fopen(fname, "rb"))) { 1.121 + fprintf(stderr, "failed to open %s: %s\n", fname, strerror(errno)); 1.122 + return false; 1.123 + } 1.124 + 1.125 + bool res = load(fp); 1.126 + fclose(fp); 1.127 + return res; 1.128 +} 1.129 + 1.130 +bool Scene::load(FILE *fp) 1.131 +{ 1.132 + static int seq; 1.133 + char cur_name[16]; 1.134 + 1.135 + obj_file obj; 1.136 + 1.137 + sprintf(cur_name, "default%02d.obj", seq++); 1.138 + obj.cur_obj = cur_name; 1.139 + 1.140 + int prev_cmd = 0, obj_added = 0; 1.141 + for(;;) { 1.142 + Vector3 vec; 1.143 + obj_face face; 1.144 + 1.145 + char line[BUF_SZ]; 1.146 + fgets(line, sizeof line, fp); 1.147 + if(feof(fp)) { 1.148 + break; 1.149 + } 1.150 + 1.151 + char *tok; 1.152 + if(!(tok = strtok(line, SEP))) { 1.153 + continue; // ignore empty lines 1.154 + } 1.155 + 1.156 + int cmd; 1.157 + if((cmd = get_cmd(tok)) == -1) { 1.158 + continue; // ignore unknown commands ... 1.159 + } 1.160 + 1.161 + switch(cmd) { 1.162 + case CMD_V: 1.163 + if(!parse_vec(&vec)) { 1.164 + continue; 1.165 + } 1.166 + obj.v.push_back(vec); 1.167 + break; 1.168 + 1.169 + case CMD_VN: 1.170 + if(!parse_vec(&vec)) { 1.171 + continue; 1.172 + } 1.173 + obj.vn.push_back(vec); 1.174 + break; 1.175 + 1.176 + case CMD_VT: 1.177 + if(!parse_vec(&vec)) { 1.178 + continue; 1.179 + } 1.180 + vec.y = 1.0 - vec.y; 1.181 + obj.vt.push_back(vec); 1.182 + break; 1.183 + 1.184 + case CMD_O: 1.185 + case CMD_G: 1.186 + if(prev_cmd == CMD_O || prev_cmd == CMD_G) { 1.187 + break; // just in case we've got both of them in a row 1.188 + } 1.189 + /* if we have any previous data, group them up, add the object 1.190 + * and continue with the new one... 1.191 + */ 1.192 + if(!obj.f.empty()) { 1.193 + Mesh *mesh = cons_mesh(&obj); 1.194 + mesh->matid = matnames[obj.cur_mat]; 1.195 + add_mesh(mesh); 1.196 + obj_added++; 1.197 + 1.198 + obj.f.clear(); // clean the face list 1.199 + } 1.200 + if((tok = strtok(0, SEP))) { 1.201 + obj.cur_obj = tok; 1.202 + } else { 1.203 + sprintf(cur_name, "default%02d.obj", seq++); 1.204 + obj.cur_obj = cur_name; 1.205 + } 1.206 + break; 1.207 + 1.208 + case CMD_MTLLIB: 1.209 + if((tok = strtok(0, SEP))) { 1.210 + char path[PATH_MAX]; 1.211 + 1.212 + sprintf(path, ".:%s", dirname(tok)); 1.213 + if(!find_file(path, PATH_MAX, tok, path)) { 1.214 + fprintf(stderr, "material library not found: %s\n", tok); 1.215 + continue; 1.216 + } 1.217 + 1.218 + FILE *mfile; 1.219 + if(!(mfile = fopen(path, "rb"))) { 1.220 + fprintf(stderr, "failed to open material library: %s\n", path); 1.221 + continue; 1.222 + } 1.223 + 1.224 + // load all materials of the mtl file into a vector 1.225 + std::vector<obj_mat> vmtl; 1.226 + if(!read_materials(mfile, &vmtl)) { 1.227 + continue; 1.228 + } 1.229 + fclose(mfile); 1.230 + 1.231 + // and add them all to the scene 1.232 + for(size_t i=0; i<vmtl.size(); i++) { 1.233 + Material mat; 1.234 + 1.235 + mat.kd[0] = vmtl[i].diffuse.x; 1.236 + mat.kd[1] = vmtl[i].diffuse.y; 1.237 + mat.kd[2] = vmtl[i].diffuse.z; 1.238 + 1.239 + mat.ks[0] = vmtl[i].specular.x; 1.240 + mat.ks[1] = vmtl[i].specular.y; 1.241 + mat.ks[2] = vmtl[i].specular.z; 1.242 + 1.243 + mat.kt = 1.0 - vmtl[i].alpha; 1.244 + mat.kr = vmtl[i].refl; 1.245 + mat.spow = vmtl[i].shininess; 1.246 + 1.247 + matlib.push_back(mat); 1.248 + matnames[vmtl[i].name] = i; 1.249 + } 1.250 + } 1.251 + break; 1.252 + 1.253 + case CMD_USEMTL: 1.254 + if((tok = strtok(0, SEP))) { 1.255 + obj.cur_mat = tok; 1.256 + } else { 1.257 + obj.cur_mat = ""; 1.258 + } 1.259 + break; 1.260 + 1.261 + case CMD_F: 1.262 + if(!parse_face(&face)) { 1.263 + continue; 1.264 + } 1.265 + 1.266 + // convert negative indices to regular indices 1.267 + for(int i=0; i<4; i++) { 1.268 + if(face.v[i] < 0 && face.v[i] != INVALID_IDX) { 1.269 + face.v[i] = obj.v.size() + face.v[i]; 1.270 + } 1.271 + if(face.n[i] < 0 && face.n[i] != INVALID_IDX) { 1.272 + face.n[i] = obj.vn.size() + face.n[i]; 1.273 + } 1.274 + if(face.t[i] < 0 && face.t[i] != INVALID_IDX) { 1.275 + face.t[i] = obj.vt.size() + face.t[i]; 1.276 + } 1.277 + } 1.278 + 1.279 + // break quads into triangles if needed 1.280 + obj.f.push_back(face); 1.281 + if(face.elem == 4) { 1.282 + face.v[1] = face.v[2]; 1.283 + face.n[1] = face.n[2]; 1.284 + face.t[1] = face.t[2]; 1.285 + 1.286 + face.v[2] = face.v[3]; 1.287 + face.n[2] = face.n[3]; 1.288 + face.t[2] = face.t[3]; 1.289 + 1.290 + obj.f.push_back(face); 1.291 + } 1.292 + break; 1.293 + 1.294 + default: 1.295 + break; // ignore unknown commands 1.296 + } 1.297 + 1.298 + prev_cmd = cmd; 1.299 + } 1.300 + 1.301 + // reached end of file... 1.302 + if(!obj.f.empty()) { 1.303 + Mesh *mesh = cons_mesh(&obj); 1.304 + mesh->matid = matnames[obj.cur_mat]; 1.305 + add_mesh(mesh); 1.306 + obj_added++; 1.307 + } 1.308 + 1.309 + return obj_added > 0; 1.310 +} 1.311 + 1.312 +static Mesh *cons_mesh(obj_file *obj) 1.313 +{ 1.314 + Mesh *mesh; 1.315 + 1.316 + // need at least one of each element 1.317 + bool added_norm = false, added_tc = false; 1.318 + if(obj->vn.empty()) { 1.319 + obj->vn.push_back(Vector3(0, 0, 0)); 1.320 + added_norm = true; 1.321 + } 1.322 + if(obj->vt.empty()) { 1.323 + obj->vt.push_back(Vector3(0, 0, 0)); 1.324 + added_tc = true; 1.325 + } 1.326 + 1.327 + mesh = new Mesh; 1.328 + 1.329 + for(size_t i=0; i<obj->f.size(); i++) { 1.330 + Face face; 1.331 + Vector3 v[3]; 1.332 + 1.333 + for(int j=0; j<3; j++) { 1.334 + obj_face *f = &obj->f[i]; 1.335 + 1.336 + face.v[j].pos[0] = v[j].x = obj->v[f->v[j]].x; 1.337 + face.v[j].pos[1] = v[j].y = obj->v[f->v[j]].y; 1.338 + face.v[j].pos[2] = v[j].z = obj->v[f->v[j]].z; 1.339 + face.v[j].pos[3] = 0.0; 1.340 + 1.341 + int nidx = f->n[j] < 0 ? 0 : f->n[j]; 1.342 + face.v[j].normal[0] = obj->vn[nidx].x; 1.343 + face.v[j].normal[1] = obj->vn[nidx].y; 1.344 + face.v[j].normal[2] = obj->vn[nidx].z; 1.345 + face.v[j].normal[3] = 0.0; 1.346 + 1.347 + int tidx = f->t[j] < 0 ? 0 : f->t[j]; 1.348 + face.v[j].tex[0] = obj->vt[tidx].x; 1.349 + face.v[j].tex[1] = obj->vt[tidx].y; 1.350 + } 1.351 + 1.352 + Vector3 a = v[1] - v[0]; 1.353 + Vector3 b = v[2] - v[0]; 1.354 + Vector3 n = cross(a, b); 1.355 + n.normalize(); 1.356 + 1.357 + face.normal[0] = n.x; 1.358 + face.normal[1] = n.y; 1.359 + face.normal[2] = n.z; 1.360 + face.normal[3] = 0.0; 1.361 + 1.362 + mesh->faces.push_back(face); 1.363 + } 1.364 + 1.365 + if(added_norm) { 1.366 + obj->vn.pop_back(); 1.367 + } 1.368 + if(added_tc) { 1.369 + obj->vt.pop_back(); 1.370 + } 1.371 + 1.372 + return mesh; 1.373 +} 1.374 + 1.375 +static bool read_materials(FILE *fp, std::vector<obj_mat> *vmtl) 1.376 +{ 1.377 + obj_mat mat; 1.378 + 1.379 + for(;;) { 1.380 + char line[BUF_SZ]; 1.381 + fgets(line, sizeof line, fp); 1.382 + if(feof(fp)) { 1.383 + break; 1.384 + } 1.385 + 1.386 + char *tok; 1.387 + if(!(tok = strtok(line, SEP))) { 1.388 + continue; 1.389 + } 1.390 + 1.391 + int cmd; 1.392 + if((cmd = get_cmd(tok)) == -1) { 1.393 + continue; 1.394 + } 1.395 + 1.396 + switch(cmd) { 1.397 + case CMD_NEWMTL: 1.398 + // add the previous material, and start a new one 1.399 + if(mat.name.length() > 0) { 1.400 + printf("Adding material: %s\n", mat.name.c_str()); 1.401 + vmtl->push_back(mat); 1.402 + mat.reset(); 1.403 + } 1.404 + if((tok = strtok(0, SEP))) { 1.405 + mat.name = tok; 1.406 + } 1.407 + break; 1.408 + 1.409 + case CMD_KA: 1.410 + parse_color(&mat.ambient); 1.411 + break; 1.412 + 1.413 + case CMD_KD: 1.414 + parse_color(&mat.diffuse); 1.415 + break; 1.416 + 1.417 + case CMD_KS: 1.418 + parse_color(&mat.specular); 1.419 + break; 1.420 + 1.421 + case CMD_KR: 1.422 + if((tok = strtok(0, SEP)) && is_float(tok)) { 1.423 + mat.refl = atof(tok); 1.424 + } 1.425 + break; 1.426 + 1.427 + case CMD_NS: 1.428 + if((tok = strtok(0, SEP)) && is_float(tok)) { 1.429 + mat.shininess = atof(tok); 1.430 + } 1.431 + break; 1.432 + 1.433 + case CMD_NI: 1.434 + if((tok = strtok(0, SEP)) && is_float(tok)) { 1.435 + mat.ior = atof(tok); 1.436 + } 1.437 + break; 1.438 + 1.439 + case CMD_D: 1.440 + case CMD_TR: 1.441 + { 1.442 + Vector3 c; 1.443 + if(parse_color(&c)) { 1.444 + mat.alpha = cmd == CMD_D ? c.x : 1.0 - c.x; 1.445 + } 1.446 + } 1.447 + break; 1.448 + 1.449 + case CMD_MAP_KD: 1.450 + mat.tex_dif = parse_map(); 1.451 + break; 1.452 + 1.453 + default: 1.454 + break; 1.455 + } 1.456 + } 1.457 + 1.458 + if(mat.name.length() > 0) { 1.459 + printf("Adding material: %s\n", mat.name.c_str()); 1.460 + vmtl->push_back(mat); 1.461 + } 1.462 + return true; 1.463 +} 1.464 + 1.465 +static int get_cmd(char *str) 1.466 +{ 1.467 + char *s = str; 1.468 + while((*s = toupper(*s))) s++; 1.469 + 1.470 + for(int i=0; cmd_names[i]; i++) { 1.471 + if(strcmp(str, cmd_names[i]) == 0) { 1.472 + return i; 1.473 + } 1.474 + } 1.475 + return CMD_UNK; 1.476 +} 1.477 + 1.478 +static bool is_int(const char *str) 1.479 +{ 1.480 + char *tmp; 1.481 + strtol(str, &tmp, 10); 1.482 + return tmp != str; 1.483 +} 1.484 + 1.485 +static bool is_float(const char *str) 1.486 +{ 1.487 + char *tmp; 1.488 + strtod(str, &tmp); 1.489 + return tmp != str; 1.490 +} 1.491 + 1.492 +static bool parse_vec(Vector3 *vec) 1.493 +{ 1.494 + for(int i=0; i<3; i++) { 1.495 + char *tok; 1.496 + 1.497 + if(!(tok = strtok(0, SEP)) || !is_float(tok)) { 1.498 + if(i < 2) { 1.499 + return false; 1.500 + } 1.501 + vec->z = 0.0; 1.502 + } else { 1.503 + float v = atof(tok); 1.504 + 1.505 + switch(i) { 1.506 + case 0: 1.507 + vec->x = v; 1.508 + break; 1.509 + case 1: 1.510 + vec->y = v; 1.511 + break; 1.512 + case 2: 1.513 + vec->z = v; 1.514 + break; 1.515 + } 1.516 + } 1.517 + } 1.518 + return true; 1.519 +} 1.520 + 1.521 +static bool parse_color(Vector3 *col) 1.522 +{ 1.523 + for(int i=0; i<3; i++) { 1.524 + char *tok; 1.525 + 1.526 + if(!(tok = strtok(0, SEP)) || !is_float(tok)) { 1.527 + col->y = col->z = col->x; 1.528 + return i > 0 ? true : false; 1.529 + } 1.530 + 1.531 + float v = atof(tok); 1.532 + switch(i) { 1.533 + case 0: 1.534 + col->x = v; 1.535 + break; 1.536 + case 1: 1.537 + col->y = v; 1.538 + break; 1.539 + case 2: 1.540 + col->z = v; 1.541 + break; 1.542 + } 1.543 + } 1.544 + return true; 1.545 +} 1.546 + 1.547 +static bool parse_face(obj_face *face) 1.548 +{ 1.549 + char *tok[] = {0, 0, 0, 0}; 1.550 + face->elem = 0; 1.551 + 1.552 + for(int i=0; i<4; i++) { 1.553 + if((!(tok[i] = strtok(0, SEP)) || !is_int(tok[i]))) { 1.554 + if(i < 3) return false; // less than 3 verts? not a polygon 1.555 + } else { 1.556 + face->elem++; 1.557 + } 1.558 + } 1.559 + 1.560 + for(int i=0; i<4; i++) { 1.561 + char *subtok = tok[i]; 1.562 + 1.563 + if(!subtok || !*subtok || !is_int(subtok)) { 1.564 + if(i < 3) { 1.565 + return false; 1.566 + } 1.567 + face->v[i] = INVALID_IDX; 1.568 + } else { 1.569 + face->v[i] = atoi(subtok); 1.570 + if(face->v[i] > 0) face->v[i]--; /* convert to 0-based */ 1.571 + } 1.572 + 1.573 + while(subtok && *subtok && *subtok != '/') { 1.574 + subtok++; 1.575 + } 1.576 + if(subtok && *subtok && *++subtok && is_int(subtok)) { 1.577 + face->t[i] = atoi(subtok); 1.578 + if(face->t[i] > 0) face->t[i]--; /* convert to 0-based */ 1.579 + } else { 1.580 + face->t[i] = INVALID_IDX; 1.581 + } 1.582 + 1.583 + while(subtok && *subtok && *subtok != '/') { 1.584 + subtok++; 1.585 + } 1.586 + if(subtok && *subtok && *++subtok && is_int(subtok)) { 1.587 + face->n[i] = atoi(subtok); 1.588 + if(face->n[i] > 0) face->n[i]--; /* convert to 0-based */ 1.589 + } else { 1.590 + face->n[i] = INVALID_IDX; 1.591 + } 1.592 + } 1.593 + 1.594 + return true; 1.595 +} 1.596 + 1.597 +static const char *parse_map() 1.598 +{ 1.599 + char *tok, *prev = 0; 1.600 + 1.601 + while((tok = strtok(0, SEP))) { 1.602 + prev = tok; 1.603 + } 1.604 + 1.605 + return prev ? prev : ""; 1.606 +} 1.607 + 1.608 +static bool find_file(char *res, int sz, const char *fname, const char *path, const char *mode) 1.609 +{ 1.610 + FILE *fp; 1.611 + const char *beg, *end; 1.612 + int fnamelen = strlen(fname); 1.613 + 1.614 + beg = path; 1.615 + while(beg && *beg) { 1.616 + end = beg; 1.617 + while(*end && *end != ':') { 1.618 + end++; 1.619 + } 1.620 + 1.621 + int res_len = end - beg; 1.622 + char *pathname = (char*)alloca(res_len + fnamelen + 2); 1.623 + memcpy(pathname, beg, res_len); 1.624 + pathname[res_len] = 0; 1.625 + if(res_len) { 1.626 + strcat(pathname, "/"); 1.627 + } 1.628 + strcat(pathname, fname); 1.629 + 1.630 + if((fp = fopen(pathname, mode))) { 1.631 + fclose(fp); 1.632 + strncpy(res, pathname, sz); 1.633 + return true; 1.634 + } 1.635 + 1.636 + beg += res_len; 1.637 + if(*beg == ':') beg++; 1.638 + } 1.639 + return false; 1.640 +} 1.641 + 1.642 +static const char *dirname(const char *str) 1.643 +{ 1.644 + static char buf[PATH_MAX]; 1.645 + 1.646 + if(!str || !*str) { 1.647 + strcpy(buf, "."); 1.648 + } else { 1.649 + strncpy(buf, str, PATH_MAX); 1.650 + char *ptr = strrchr(buf, '/'); 1.651 + 1.652 + if(ptr && *ptr) { 1.653 + *ptr = 0; 1.654 + } else { 1.655 + strcpy(buf, "."); 1.656 + } 1.657 + } 1.658 + return buf; 1.659 +}