Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | File List | Namespace Members | Class Members | File Members | Related Pages

objloader.cpp

Go to the documentation of this file.
00001 /*
00002     Sheep - A Rigid Body Dynamics Engine
00003     Copyright (C) 2001-2004 Francois Beaune
00004     Contact: http://toxicengine.sourceforge.net/
00005 
00006     This file is part of Sheep.
00007 
00008     Sheep is free software; you can redistribute it and/or modify
00009     it under the terms of the GNU General Public License as published by
00010     the Free Software Foundation; either version 2 of the License, or
00011     (at your option) any later version.
00012 
00013     Sheep is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016     GNU General Public License for more details.
00017 
00018     You should have received a copy of the GNU General Public License
00019     along with Sheep; if not, write to the Free Software
00020     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00021 */
00022 
00023 #include "objloader.h"  // include first
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 //#define VERBOSE
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 /*= DEFAULT_CONFIGURATION_BIT*/,
00063                      ProgressMonitor *progmon /*= 0*/)
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     //!\todo Close files and free resources.
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         // Close the current submesh.
00258         if(m_inside_submesh_def)
00259             m_geombuilder->EndSubMesh();
00260 
00261         // Begin a new submesh.
00262 #ifdef VERBOSE
00263         cerr << "[OBJLoader] Loading mesh..." << endl;
00264 #endif  // VERBOSE
00265         m_geombuilder->BeginSubMesh("");
00266         m_inside_submesh_def = true;
00267 
00268         // Set submesh material if a material has been specified via the 'usemtl' statement.
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(); // ignore unknown statements
00314         }
00315 
00316         if(m_progmon) {
00317             //!\todo Fine tune the number of statements to parse between progress updates.
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             // Recognized (epsilon)
00365             // Accept n(/((n(/n)?)|(/n)))?
00366             case 0:
00367                 v = accept_integer();
00368                 state = 1;
00369                 break;
00370             // Recognized n
00371             // Accept (/((n(/n)?)|(/n)))?
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             // Recognized n/
00382             // Accept (n(/n)?)|(/n)
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             // Recognized n/n
00395             // Accept (/n)?
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             // Recognized n//
00406             // Accept n
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;  // ignore invalid faces
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             // Could not triangulate this polygon, because it is either
00464             // self-intersecting or not planar.
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; // ignore problematic polygons
00477             }
00478         }
00479     } else {
00480         // Identity mapping.
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         // No material has been specified: new faces will be added
00544         // to a new submesh, but no material will be applied to this
00545         // new submesh.
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             // The requested material is defined: new faces will be added
00555             // to a new submesh, and the specified material will be applied
00556             // to this new submesh.
00557             m_create_submesh = true;
00558             m_set_submesh_material = true;
00559             m_submesh_material_id = i->second;
00560         } else {
00561             // The requested material is not defined: new faces will be
00562             // added to a new submesh, but no material will be applied to
00563             // this new submesh.
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     //!\todo This is quite dirty, I must admit.
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             //!\todo Close files and free resources.
00656             throw FileNotFoundException(filename);
00657         }
00658 
00659         // Ignore missing material file.
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(); // ignore unknown statements
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         //!\todo Close files and free resources.
00775 
00776         // There is no distinction between a missing texture file
00777         // and an invalid texture file.
00778         if(m_option_mask & STOP_ON_MISSING_FILE_BIT) {
00779             m_matbuilder->EndMaterial();
00780             throw FileNotFoundException(filename);
00781         } else return;  // ignore invalid or missing texture file
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 }

Generated on Tue May 11 01:31:51 2004 for toxic by doxygen 1.3.6