00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 #include "objloader.h"
00024 #include "common/math/point3.h"
00025 #include "common/misc/progressmonitor.h"
00026 #include "common/misc/stringutils.h"
00027 #include "triangulator.h"
00028
00029 #include <cctype>
00030 #include <IL/il.h>
00031
00032
00033
00034 #ifdef VERBOSE
00035 #include <iostream>
00036 #endif // VERBOSE
00037
00038 using namespace sheep;
00039 using namespace std;
00040
00041 OBJLoader::OBJLoader() {
00042 m_keyword_table.Insert("d", D);
00043 m_keyword_table.Insert("f", F);
00044 m_keyword_table.Insert("g", G);
00045 m_keyword_table.Insert("illum", ILLUM);
00046 m_keyword_table.Insert("Ka", KA);
00047 m_keyword_table.Insert("Kd", KD);
00048 m_keyword_table.Insert("Ks", KS);
00049 m_keyword_table.Insert("map_Kd", MAP_KD);
00050 m_keyword_table.Insert("mtllib", MTLLIB);
00051 m_keyword_table.Insert("newmtl", NEWMTL);
00052 m_keyword_table.Insert("s", S);
00053 m_keyword_table.Insert("Tr", TR);
00054 m_keyword_table.Insert("usemtl", USEMTL);
00055 m_keyword_table.Insert("v", V);
00056 m_keyword_table.Insert("vn", VN);
00057 m_keyword_table.Insert("vt", VT);
00058 }
00059
00060 void OBJLoader::Load(const string &filename,
00061 IMeshBuilder &builder,
00062 int option_mask ,
00063 ProgressMonitor *progmon )
00064 {
00065 m_path = StringUtils::GetPath(filename);
00066
00067 m_geombuilder = builder.GeometryBuilder();
00068 m_matbuilder = builder.MaterialBuilder();
00069
00070 m_option_mask = option_mask;
00071
00072 m_progmon = progmon;
00073
00074 m_file = fopen(filename.c_str(), "r");
00075
00076 if(!m_file)
00077 throw FileNotFoundException(filename);
00078
00079 if(m_progmon) {
00080 fseek(m_file, 0, SEEK_END);
00081 m_progmon->StartJob(0, ftell(m_file));
00082 fseek(m_file, 0, SEEK_SET);
00083 }
00084
00085 m_line = 1;
00086
00087 m_vertices.clear();
00088 m_normals.clear();
00089 m_texcoords.clear();
00090
00091 m_create_submesh = true;
00092 m_set_submesh_material = false;
00093 m_submesh_material_id = 0;
00094 m_inside_submesh_def = false;
00095
00096 parse_file();
00097
00098 if(m_inside_submesh_def)
00099 m_geombuilder->EndSubMesh();
00100
00101 if(m_progmon)
00102 m_progmon->Done();
00103
00104 fclose(m_file);
00105 }
00106
00107 void OBJLoader::parse_error() {
00108
00109 throw ParsingException(m_line);
00110 }
00111
00112 void OBJLoader::eat_leading_blanks() {
00113 assert(m_file);
00114
00115 bool inside_comment = false;
00116
00117 while(true) {
00118 int c = fgetc(m_file);
00119
00120 if(c == EOF)
00121 break;
00122
00123 if(c == '\n')
00124 ++m_line;
00125
00126 if(inside_comment) {
00127 if(c == '\n')
00128 inside_comment = false;
00129 } else {
00130 if(c == '#')
00131 inside_comment = true;
00132 else if(!isspace(c)) {
00133 assert(c != EOF);
00134
00135 if(c == '\n')
00136 --m_line;
00137
00138 ungetc(c, m_file);
00139
00140 break;
00141 }
00142 }
00143 }
00144 }
00145
00146 void OBJLoader::eat_blanks() {
00147 assert(m_file);
00148
00149 while(true) {
00150 const int c = fgetc(m_file);
00151
00152 if(c == EOF)
00153 break;
00154
00155 if(c == '\n')
00156 ++m_line;
00157
00158 if(c == '\n' || !isspace(c)) {
00159 assert(c != EOF);
00160
00161 if(c == '\n')
00162 --m_line;
00163
00164 ungetc(c, m_file);
00165
00166 break;
00167 }
00168 }
00169 }
00170
00171 void OBJLoader::eat_line() {
00172 assert(m_file);
00173
00174 while(true) {
00175 const int c = fgetc(m_file);
00176
00177 if(c == EOF)
00178 break;
00179
00180 if(c == '\n') {
00181 ++m_line;
00182 break;
00183 }
00184 }
00185 }
00186
00187 int OBJLoader::accept_integer() {
00188 assert(m_file);
00189
00190 const int c = look_ahead();
00191
00192 if(c == EOF || isspace(c))
00193 parse_error();
00194
00195 int n;
00196
00197 if(fscanf(m_file, "%d", &n) < 1)
00198 parse_error();
00199
00200 return n;
00201 }
00202
00203 double OBJLoader::accept_double() {
00204 assert(m_file);
00205
00206 const int c = look_ahead();
00207
00208 if(c == EOF || isspace(c))
00209 parse_error();
00210
00211 double d;
00212
00213 if(fscanf(m_file, "%lf", &d) < 1)
00214 parse_error();
00215
00216 return d;
00217 }
00218
00219 string OBJLoader::accept_string() {
00220 assert(m_file);
00221
00222 int c = fgetc(m_file);
00223
00224 if(c == EOF || isspace(c))
00225 parse_error();
00226
00227 string s = "";
00228
00229 while(true) {
00230 s += c;
00231
00232 c = fgetc(m_file);
00233
00234 if(c == EOF)
00235 break;
00236
00237 if(c == '\n')
00238 ++m_line;
00239
00240 if(isspace(c)) {
00241 assert(c != EOF);
00242
00243 if(c == '\n')
00244 --m_line;
00245
00246 ungetc(c, m_file);
00247
00248 break;
00249 }
00250 }
00251
00252 return s;
00253 }
00254
00255 void OBJLoader::select_submesh() {
00256 if(m_create_submesh) {
00257
00258 if(m_inside_submesh_def)
00259 m_geombuilder->EndSubMesh();
00260
00261
00262 #ifdef VERBOSE
00263 cerr << "[OBJLoader] Loading mesh..." << endl;
00264 #endif // VERBOSE
00265 m_geombuilder->BeginSubMesh("");
00266 m_inside_submesh_def = true;
00267
00268
00269 if(m_set_submesh_material) {
00270 m_geombuilder->SetMaterial(m_submesh_material_id);
00271 m_set_submesh_material = false;
00272 m_submesh_material_id = 0;
00273 }
00274
00275 m_global_vertex_id.clear();
00276
00277 m_create_submesh = false;
00278 }
00279 }
00280
00281 void OBJLoader::parse_file() {
00282 int parsed_statements = 0;
00283
00284 while(true) {
00285 eat_leading_blanks();
00286
00287 if(look_ahead() == EOF)
00288 break;
00289
00290 const string keyword = accept_string();
00291
00292 switch(m_keyword_table.GetSymbol(keyword)) {
00293 case F:
00294 select_submesh();
00295 parse_f_statement();
00296 break;
00297 case MTLLIB:
00298 parse_mtllib_statement();
00299 break;
00300 case USEMTL:
00301 parse_usemtl_statement();
00302 break;
00303 case V:
00304 parse_v_statement();
00305 break;
00306 case VN:
00307 parse_vn_statement();
00308 break;
00309 case VT:
00310 parse_vt_statement();
00311 break;
00312 default:
00313 eat_line();
00314 }
00315
00316 if(m_progmon) {
00317
00318 if((parsed_statements & 127) == 0)
00319 m_progmon->SetJobProgress(ftell(m_file));
00320 }
00321
00322 ++parsed_statements;
00323 }
00324 }
00325
00326 namespace {
00327 template<typename T>
00328 int fix_vector_index(const vector<T> &v, int index) {
00329 if(index > 0) {
00330 assert(index >= 1 && index <= v.size());
00331 return index - 1;
00332 } else {
00333 assert(-index >= 1 && -index <= v.size());
00334 return v.size() + index;
00335 }
00336 }
00337 }
00338
00339 void OBJLoader::parse_f_statement() {
00340 vector<IMeshBuilder::FeatureId> vertex_id, texcoord_id, normal_id;
00341
00342 vertex_id.reserve(3);
00343 texcoord_id.reserve(3);
00344 normal_id.reserve(3);
00345
00346 vector<Point3> polygon_vertices;
00347 polygon_vertices.reserve(3);
00348
00349 while(true) {
00350 eat_blanks();
00351
00352 if(is_eol())
00353 break;
00354
00355 bool has_texcoord = false;
00356 bool has_normal = false;
00357 int v, vt, vn;
00358 int state = 0;
00359 bool terminate = false;
00360 int c;
00361
00362 while(!terminate) {
00363 switch(state) {
00364
00365
00366 case 0:
00367 v = accept_integer();
00368 state = 1;
00369 break;
00370
00371
00372 case 1:
00373 c = look_ahead();
00374 if(isspace(c))
00375 goto terminate;
00376 else if(c == '/') {
00377 accept_char('/');
00378 state = 2;
00379 } else parse_error();
00380 break;
00381
00382
00383 case 2:
00384 c = look_ahead();
00385 if(c == '/') {
00386 accept_char('/');
00387 state = 4;
00388 } else {
00389 vt = accept_integer();
00390 has_texcoord = true;
00391 state = 3;
00392 }
00393 break;
00394
00395
00396 case 3:
00397 c = look_ahead();
00398 if(isspace(c))
00399 goto terminate;
00400 else if(c == '/') {
00401 accept_char('/');
00402 state = 4;
00403 } else parse_error();
00404 break;
00405
00406
00407 case 4:
00408 vn = accept_integer();
00409 has_normal = true;
00410 goto terminate;
00411 break;
00412 default:
00413 assert(!"Invalid state.");
00414 }
00415 }
00416
00417 terminate:
00418
00419 v = fix_vector_index(m_vertices, v);
00420
00421 if(m_global_vertex_id.find(v) == m_global_vertex_id.end()) {
00422 const IMeshBuilder::FeatureId id = m_geombuilder->AppendVertex(m_vertices[v]);
00423 m_global_vertex_id[v] = id;
00424 vertex_id.push_back(id);
00425 } else vertex_id.push_back(m_global_vertex_id[v]);
00426
00427 polygon_vertices.push_back(m_vertices[v]);
00428
00429 if(has_texcoord) {
00430 vt = fix_vector_index(m_texcoords, vt);
00431 const IMeshBuilder::FeatureId id = m_geombuilder->AppendTexCoord(m_texcoords[vt]);
00432 texcoord_id.push_back(id);
00433 }
00434
00435 if(has_normal) {
00436 vn = fix_vector_index(m_normals, vn);
00437 const IMeshBuilder::FeatureId id = m_geombuilder->AppendNormal(m_normals[vn]);
00438 normal_id.push_back(id);
00439 }
00440 }
00441
00442 accept_newline();
00443
00444 const int n = vertex_id.size();
00445
00446 if(n < 3) {
00447 if(m_option_mask & STOP_ON_INVALID_FACE) {
00448 m_geombuilder->EndSubMesh();
00449 throw InvalidFaceException(m_line);
00450 } else return;
00451 }
00452
00453 assert(texcoord_id.size() == 0 || texcoord_id.size() == n);
00454 assert(normal_id.size() == 0 || normal_id.size() == n);
00455
00456 const bool has_texcoord = texcoord_id.size() == n;
00457 const bool has_normal = normal_id.size() == n;
00458
00459 vector<int> triangles;
00460
00461 if(polygon_vertices.size() > 3 && !(m_option_mask & DISABLE_TRIANGULATION_BIT)) {
00462 if(!TriangulatePolygon3(polygon_vertices, &triangles)) {
00463
00464
00465 if(m_option_mask & STOP_ON_TRIANGULATION_ERROR_BIT) {
00466 m_geombuilder->EndSubMesh();
00467 throw TriangulationException(m_line);
00468 } else {
00469 #ifdef VERBOSE
00470 cerr << "[OBJLoader] Warning: could not triangulate the following polygon:" << endl;
00471 for(vector<Point3>::const_iterator i = polygon_vertices.begin(), e = polygon_vertices.end(); i != e; ++i) {
00472 cerr << "(" << i->m_x << ", " << i->m_y << ", " << i->m_z << ") ";
00473 }
00474 cerr << endl;
00475 #endif // VERBOSE
00476 return;
00477 }
00478 }
00479 } else {
00480
00481 for(int i = 0; i < polygon_vertices.size(); ++i)
00482 triangles.push_back(i);
00483 }
00484
00485 for(int i = 0; i < triangles.size(); i += 3) {
00486 IMeshBuilder::FeatureId v[3];
00487
00488 v[0] = vertex_id[triangles[i]];
00489 v[1] = vertex_id[triangles[i + 1]];
00490 v[2] = vertex_id[triangles[i + 2]];
00491
00492 IMeshBuilder::FeatureId face_id = m_geombuilder->AppendFace(3, v);
00493
00494 if(has_texcoord) {
00495 IMeshBuilder::FeatureId vt[3];
00496
00497 vt[0] = texcoord_id[triangles[i]];
00498 vt[1] = texcoord_id[triangles[i + 1]];
00499 vt[2] = texcoord_id[triangles[i + 2]];
00500
00501 m_geombuilder->SetFaceTexCoords(face_id, 3, vt);
00502 }
00503
00504 if(has_normal) {
00505 IMeshBuilder::FeatureId vn[3];
00506
00507 vn[0] = normal_id[triangles[i]];
00508 vn[1] = normal_id[triangles[i + 1]];
00509 vn[2] = normal_id[triangles[i + 2]];
00510
00511 m_geombuilder->SetFaceNormals(face_id, 3, vn);
00512 }
00513 }
00514 }
00515
00516 void OBJLoader::parse_mtllib_statement() {
00517 eat_blanks();
00518
00519 string filename = accept_string();
00520
00521 if(m_matbuilder)
00522 load_material_file(filename);
00523
00524 while(true) {
00525 eat_blanks();
00526
00527 if(is_eol())
00528 break;
00529
00530 filename = accept_string();
00531
00532 if(m_matbuilder)
00533 load_material_file(filename);
00534 }
00535
00536 accept_newline();
00537 }
00538
00539 void OBJLoader::parse_usemtl_statement() {
00540 eat_blanks();
00541
00542 if(is_eol()) {
00543
00544
00545
00546 m_create_submesh = true;
00547 m_set_submesh_material = false;
00548 m_submesh_material_id = 0;
00549 } else {
00550 const string material_name = accept_string();
00551 string_to_feature_id_map::const_iterator i = m_material_id.find(material_name);
00552
00553 if(i != m_material_id.end()) {
00554
00555
00556
00557 m_create_submesh = true;
00558 m_set_submesh_material = true;
00559 m_submesh_material_id = i->second;
00560 } else {
00561
00562
00563
00564 m_create_submesh = true;
00565 m_set_submesh_material = false;
00566 m_submesh_material_id = 0;
00567 }
00568 }
00569
00570 eat_blanks();
00571 accept_newline();
00572 }
00573
00574 void OBJLoader::parse_v_statement() {
00575 Vector3 v;
00576
00577 eat_blanks();
00578 v.m_x = accept_double();
00579
00580 eat_blanks();
00581 v.m_y = accept_double();
00582
00583 eat_blanks();
00584 v.m_z = accept_double();
00585
00586 eat_blanks();
00587 if(!is_eol())
00588 accept_double();
00589
00590 eat_blanks();
00591 accept_newline();
00592
00593 m_vertices.push_back(v);
00594 }
00595
00596 void OBJLoader::parse_vt_statement() {
00597 Vector2 v;
00598
00599 eat_blanks();
00600 v.m_x = accept_double();
00601
00602 eat_blanks();
00603 v.m_y = -accept_double();
00604
00605 eat_blanks();
00606 if(!is_eol())
00607 accept_double();
00608
00609 eat_blanks();
00610 accept_newline();
00611
00612 m_texcoords.push_back(v);
00613 }
00614
00615 void OBJLoader::parse_vn_statement() {
00616 Vector3 n;
00617
00618 eat_blanks();
00619 n.m_x = accept_double();
00620
00621 eat_blanks();
00622 n.m_y = accept_double();
00623
00624 eat_blanks();
00625 n.m_z = accept_double();
00626
00627 eat_blanks();
00628 accept_newline();
00629
00630 n.Normalize();
00631
00632 m_normals.push_back(n);
00633 }
00634
00635 void OBJLoader::load_material_file(const string &filename) {
00636
00637 FILE * const file_copy = m_file;
00638 const int line_copy = m_line;
00639
00640 m_file = TryOpeningFile(m_path, filename, "r");
00641
00642 if(m_file) {
00643 m_line = 1;
00644
00645 m_inside_material_def = false;
00646
00647 parse_material_file();
00648
00649 if(m_inside_material_def) {
00650 assert(m_matbuilder);
00651 m_matbuilder->EndMaterial();
00652 }
00653 } else {
00654 if(m_option_mask & STOP_ON_MISSING_FILE_BIT) {
00655
00656 throw FileNotFoundException(filename);
00657 }
00658
00659
00660 }
00661
00662 m_file = file_copy;
00663 m_line = line_copy;
00664 }
00665
00666 void OBJLoader::parse_material_file() {
00667 while(true) {
00668 eat_leading_blanks();
00669
00670 if(look_ahead() == EOF)
00671 break;
00672
00673 const string keyword = accept_string();
00674
00675 switch(m_keyword_table.GetSymbol(keyword)) {
00676 case KA:
00677 parse_ka_statement();
00678 break;
00679 case KD:
00680 parse_kd_statement();
00681 break;
00682 case KS:
00683 parse_ks_statement();
00684 break;
00685 case MAP_KD:
00686 parse_map_kd_statement();
00687 break;
00688 case NEWMTL:
00689 parse_newmtl_statement();
00690 break;
00691 default:
00692 eat_line();
00693 }
00694 }
00695 }
00696
00697 void OBJLoader::parse_ka_statement() {
00698 eat_blanks();
00699 const double r = accept_double();
00700
00701 eat_blanks();
00702 const double g = accept_double();
00703
00704 eat_blanks();
00705 const double b = accept_double();
00706
00707 eat_blanks();
00708 accept_newline();
00709
00710 if(!m_inside_material_def)
00711 parse_error();
00712
00713 assert(m_matbuilder);
00714 m_matbuilder->SetAmbientColor(r, g, b);
00715 }
00716
00717 void OBJLoader::parse_kd_statement() {
00718 eat_blanks();
00719 const double r = accept_double();
00720
00721 eat_blanks();
00722 const double g = accept_double();
00723
00724 eat_blanks();
00725 const double b = accept_double();
00726
00727 eat_blanks();
00728 accept_newline();
00729
00730 if(!m_inside_material_def)
00731 parse_error();
00732
00733 assert(m_matbuilder);
00734 m_matbuilder->SetDiffuseColor(r, g, b);
00735 }
00736
00737 void OBJLoader::parse_ks_statement() {
00738 eat_blanks();
00739 const double r = accept_double();
00740
00741 eat_blanks();
00742 const double g = accept_double();
00743
00744 eat_blanks();
00745 const double b = accept_double();
00746
00747 eat_blanks();
00748 accept_newline();
00749
00750 if(!m_inside_material_def)
00751 parse_error();
00752
00753 assert(m_matbuilder);
00754 m_matbuilder->SetSpecularColor(r, g, b);
00755 }
00756
00757 void OBJLoader::parse_map_kd_statement() {
00758 eat_blanks();
00759
00760 const string filename = accept_string();
00761
00762 eat_blanks();
00763 accept_newline();
00764
00765 if(!m_inside_material_def)
00766 parse_error();
00767
00768 ILuint image_id;
00769
00770 ilGenImages(1, &image_id);
00771 ilBindImage(image_id);
00772
00773 if(!TryLoadingImage(m_path, filename)) {
00774
00775
00776
00777
00778 if(m_option_mask & STOP_ON_MISSING_FILE_BIT) {
00779 m_matbuilder->EndMaterial();
00780 throw FileNotFoundException(filename);
00781 } else return;
00782 }
00783
00784 const int w = ilGetInteger(IL_IMAGE_WIDTH);
00785 const int h = ilGetInteger(IL_IMAGE_HEIGHT);
00786
00787 unsigned char *texels = new unsigned char[w * h * 3];
00788
00789 ilCopyPixels(0, 0, 0, w, h, 1, IL_RGB, IL_UNSIGNED_BYTE, texels);
00790 ilDeleteImages(1, &image_id);
00791
00792 assert(m_matbuilder);
00793 m_matbuilder->SetTexture(w, h, texels);
00794
00795 delete [] texels;
00796 }
00797
00798 void OBJLoader::parse_newmtl_statement() {
00799 eat_blanks();
00800
00801 const string material_name = accept_string();
00802
00803 eat_blanks();
00804 accept_newline();
00805
00806 assert(m_matbuilder);
00807
00808 if(m_inside_material_def)
00809 m_matbuilder->EndMaterial();
00810
00811 #ifdef VERBOSE
00812 cerr << "[OBJLoader] Loading material '" << material_name << "'..." << endl;
00813 #endif // VERBOSE
00814
00815 m_material_id[material_name] = m_matbuilder->BeginMaterial(material_name);
00816 m_inside_material_def = true;
00817 }