//----------------------------------------------------------------------------- // Product: OpenCTM tools // File: obj.cpp // Description: Implementation of the OBJ file format importer/exporter. //----------------------------------------------------------------------------- // Copyright (c) 2009-2010 Marcus Geelnard // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. //----------------------------------------------------------------------------- #include #include #include #include #include #include #include #include "obj.h" #include "common.h" using namespace std; class OBJFaceNode { public: OBJFaceNode() { v = vt = vn = 0; } void Set(int aIndex, int aValue) { if(aIndex == 0) v = aValue; else if(aIndex == 1) vt = aValue; else vn = aValue; } int v, vt, vn; }; // OBJ file face description class (three triangle corners, with one vertex, // texcoord and normal index each). class OBJFace { public: OBJFace() { } // Contruct a face (one triangle) from an OBJ face description string OBJFace(const string aStr) { // Start by finding the first and last non-whitespace char (trim) size_t l = aStr.size(); size_t pos = 0, strEnd = l - 1; while((pos < strEnd) && ((aStr[pos] == ' ') || (aStr[pos] == '\t'))) ++ pos; while((strEnd > pos) && ((aStr[strEnd] == ' ') || (aStr[strEnd] == '\t'))) -- strEnd; // Extract three face corners (one triangle) while((pos <= strEnd) && (aStr[pos] != ' ') && (aStr[pos] != '\t')) { // Extract three /-separated strings (v/vt/vn) string v_s[3]; int j = 0; while((pos <= strEnd) && (aStr[pos] != ' ') && (aStr[pos] != '\t') && (j < 3)) { if(aStr[pos] != '/') v_s[j] += aStr[pos]; else ++ j; ++ pos; } // Skip whitespaces while((pos <= strEnd) && ((aStr[pos] == ' ') || (aStr[pos] == '\t'))) ++ pos; // Convert the strings to integers mNodes.push_back(OBJFaceNode()); OBJFaceNode &n = mNodes.back(); for(int j = 0; j < 3; ++ j) { int value = 0; if(v_s[j].size() > 0) { istringstream ss(v_s[j]); ss >> value; if(value > 0) value --; else if(value < 0) throw runtime_error("Negative vertex references in OBJ files are not supported."); else throw runtime_error("Invalid index (zero) in OBJ file."); } n.Set(j, value); } } } list mNodes; }; // Parse a 2 x float string as a Vector2 static Vector2 ParseVector2(const string aString) { Vector2 result; istringstream sstr(aString); sstr >> result.u; sstr >> result.v; return result; } // Parse a 3 x float string as a Vector3 static Vector3 ParseVector3(const string aString) { Vector3 result; istringstream sstr(aString); sstr >> result.x; sstr >> result.y; sstr >> result.z; return result; } /// Import a mesh from an OBJ file. void Import_OBJ(const char * aFileName, Mesh * aMesh) { // Clear the mesh aMesh->Clear(); // Open the input file ifstream inFile(aFileName, ios::in); if(inFile.fail()) throw runtime_error("Could not open input file."); // Mesh description - parsed from the OBJ file list vertices; list texCoords; list normals; list faces; // Parse the file while(!inFile.eof()) { // Read one line from the file (concatenate lines that end with "\") string line; getline(inFile, line); while((line.size() > 0) && (line[line.size() - 1] == '\\') && !inFile.eof()) { string nextLine; getline(inFile, nextLine); line = line.substr(0, line.size() - 1) + string(" ") + nextLine; } // Parse the line, if it is non-empty if(line.size() >= 1) { if(line.substr(0, 2) == string("v ")) vertices.push_back(ParseVector3(line.substr(2))); else if(line.substr(0, 3) == string("vt ")) texCoords.push_back(ParseVector2(line.substr(3))); else if(line.substr(0, 3) == string("vn ")) normals.push_back(ParseVector3(line.substr(3))); else if(line.substr(0, 2) == string("f ")) faces.push_back(OBJFace(line.substr(2))); } } // Convert lists to vectors (for random element access) vector verticesArray(vertices.begin(), vertices.end()); vector texCoordsArray(texCoords.begin(), texCoords.end()); vector normalsArray(normals.begin(), normals.end()); // Prepare vertices aMesh->mVertices.resize(verticesArray.size()); if(texCoordsArray.size() > 0) aMesh->mTexCoords.resize(verticesArray.size()); if(normalsArray.size() > 0) aMesh->mNormals.resize(verticesArray.size()); // Prepare indices int triCount = 0; for(list::iterator i = faces.begin(); i != faces.end(); ++ i) { int nodeCount = (*i).mNodes.size(); if(nodeCount >= 3) triCount += (nodeCount - 2); } aMesh->mIndices.resize(triCount * 3); // Iterate faces and extract vertex data unsigned int idx = 0; for(list::iterator i = faces.begin(); i != faces.end(); ++ i) { OBJFace &f = (*i); int nodes[3][3]; int nodeCount = 0; for(list::iterator n = f.mNodes.begin(); n != f.mNodes.end(); ++ n) { // Collect polygon nodes for this face, turning it into triangles if(nodeCount < 3) { nodes[nodeCount][0] = (*n).v; nodes[nodeCount][1] = (*n).vt; nodes[nodeCount][2] = (*n).vn; } else { nodes[1][0] = nodes[2][0]; nodes[1][1] = nodes[2][1]; nodes[1][2] = nodes[2][2]; nodes[2][0] = (*n).v; nodes[2][1] = (*n).vt; nodes[2][2] = (*n).vn; } ++ nodeCount; // Emit one triangle? if(nodeCount >= 3) { aMesh->mIndices[idx ++] = nodes[0][0]; aMesh->mIndices[idx ++] = nodes[1][0]; aMesh->mIndices[idx ++] = nodes[2][0]; aMesh->mVertices[nodes[0][0]] = verticesArray[nodes[0][0]]; aMesh->mVertices[nodes[1][0]] = verticesArray[nodes[1][0]]; aMesh->mVertices[nodes[2][0]] = verticesArray[nodes[2][0]]; if(texCoordsArray.size() > 0) { aMesh->mTexCoords[nodes[0][0]] = texCoordsArray[nodes[0][1]]; aMesh->mTexCoords[nodes[1][0]] = texCoordsArray[nodes[1][1]]; aMesh->mTexCoords[nodes[2][0]] = texCoordsArray[nodes[2][1]]; } if(normalsArray.size() > 0) { aMesh->mNormals[nodes[0][0]] = normalsArray[nodes[0][2]]; aMesh->mNormals[nodes[1][0]] = normalsArray[nodes[1][2]]; aMesh->mNormals[nodes[2][0]] = normalsArray[nodes[2][2]]; } } } } // Close the input file inFile.close(); } /// Export a mesh to an OBJ file. void Export_OBJ(const char * aFileName, Mesh * aMesh, Options &aOptions) { // Open the output file ofstream f(aFileName, ios::out); if(f.fail()) throw runtime_error("Could not open output file."); // What should we export? bool exportTexCoords = aMesh->HasTexCoords() && !aOptions.mNoTexCoords; bool exportNormals = aMesh->HasNormals() && !aOptions.mNoNormals; // Set floating point precision f << setprecision(8); // Write comment if(aMesh->mComment.size() > 0) { stringstream sstr(aMesh->mComment); sstr.seekg(0); while(!sstr.eof()) { string line; getline(sstr, line); line = TrimString(line); if(line.size() > 0) f << "# " << line << endl; } } // Write vertices for(unsigned int i = 0; i < aMesh->mVertices.size(); ++ i) f << "v " << aMesh->mVertices[i].x << " " << aMesh->mVertices[i].y << " " << aMesh->mVertices[i].z << endl; // Write UV coordinates if(exportTexCoords) { for(unsigned int i = 0; i < aMesh->mTexCoords.size(); ++ i) f << "vt " << aMesh->mTexCoords[i].u << " " << aMesh->mTexCoords[i].v << endl; } // Write normals if(exportNormals) { for(unsigned int i = 0; i < aMesh->mNormals.size(); ++ i) f << "vn " << aMesh->mNormals[i].x << " " << aMesh->mNormals[i].y << " " << aMesh->mNormals[i].z << endl; } // Write faces unsigned int triCount = aMesh->mIndices.size() / 3; f << "s 1" << endl; // Put all faces in the same smoothing group for(unsigned int i = 0; i < triCount; ++ i) { unsigned int idx = aMesh->mIndices[i * 3] + 1; f << "f " << idx << "/"; if(exportTexCoords) f << idx; f << "/"; if(exportNormals) f << idx; idx = aMesh->mIndices[i * 3 + 1] + 1; f << " " << idx << "/"; if(exportTexCoords) f << idx; f << "/"; if(exportNormals) f << idx; idx = aMesh->mIndices[i * 3 + 2] + 1; f << " " << idx << "/"; if(exportTexCoords) f << idx; f << "/"; if(exportNormals) f << idx; f << endl; } // Close the output file f.close(); }