/******************************************************************************** * OpenGL-Framework * * Copyright (c) 2013 Daniel Chappuis * ********************************************************************************* * * * 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. * * * ********************************************************************************/ // Libraries #include "MeshReaderWriter.h" #include #include #include #include #include #include using namespace openglframework; using namespace std; // Constructor MeshReaderWriter::MeshReaderWriter() { } // Load a mesh from a file and returns true if the mesh has been sucessfully loaded void MeshReaderWriter::loadMeshFromFile(const std::string& filename, Mesh& meshToCreate) { // Get the extension of the file uint startPosExtension = filename.find_last_of("."); string extension = filename.substr(startPosExtension+1); // Load the file using the correct method if (extension == "obj") { loadOBJFile(filename, meshToCreate); } else { // Display an error message and throw an exception string errorMessage("Error : the MeshReaderWriter class cannot load a file with the extension ."); errorMessage += extension; std::cerr << errorMessage << std::endl; throw std::invalid_argument(errorMessage.c_str()); } } // Write a mesh to a file void MeshReaderWriter::writeMeshToFile(const std::string& filename, const Mesh& meshToWrite) { // Get the extension of the file uint startPosExtension = filename.find_last_of("."); string extension = filename.substr(startPosExtension+1); // Load the file using the correct method if (extension == "obj") { writeOBJFile(filename, meshToWrite); } else { // Display an error message and throw an exception string errorMessage("Error : the MeshReaderWriter class cannot store a mesh file with the extension ."); errorMessage += extension; std::cerr << errorMessage << std::endl; throw std::invalid_argument(errorMessage.c_str()); } } // Load an OBJ file with a triangular or quad mesh void MeshReaderWriter::loadOBJFile(const string &filename, Mesh& meshToCreate) { // Open the file std::ifstream meshFile(filename.c_str()); // If we cannot open the file if(!meshFile.is_open()) { // Throw an exception and display an error message string errorMessage("Error : Cannot open the file " + filename); std::cerr << errorMessage << std::endl; throw runtime_error(errorMessage); } std::string buffer; string line, tmp; int id1, id2, id3, id4; int nId1, nId2, nId3, nId4; int tId1, tId2, tId3, tId4; float v1, v2, v3; size_t found1, found2; std::vector isQuad; std::vector vertices; std::vector normals; std::vector uvs; std::vector verticesIndices; std::vector normalsIndices; std::vector uvsIndices; // ---------- Collect the data from the file ---------- // // For each line of the file while(std::getline(meshFile, buffer)) { std::istringstream lineStream(buffer); std::string word; lineStream >> word; std::transform(word.begin(), word.end(), word.begin(), ::tolower); if(word == "usemtl") { // Material definition // Loading of MTL file is not implemented } else if(word == "v") { // Vertex position sscanf(buffer.c_str(), "%*s %f %f %f", &v1, &v2, &v3); vertices.push_back(Vector3(v1, v2, v3)); } else if(word == "vt") { // Vertex texture coordinate sscanf(buffer.c_str(), "%*s %f %f", &v1, &v2); uvs.push_back(Vector2(v1,v2)); } else if(word == "vn") { // Vertex normal sscanf(buffer.c_str(), "%*s %f %f %f", &v1, &v2, &v3); normals.push_back(Vector3(v1 ,v2, v3)); } else if (word == "f") { // Face line = buffer; found1 = (int)line.find("/"); bool isFaceQuad = false; int foundNext = (int)line.substr(found1+1).find("/"); // If the face definition is of the form "f v1 v2 v3 v4" if(found1 == string::npos) { int nbVertices = sscanf(buffer.c_str(), "%*s %d %d %d %d", &id1, &id2, &id3, &id4); if (nbVertices == 4) isFaceQuad = true; } // If the face definition is of the form "f v1// v2// v3// v4//" else if (foundNext == 0) { int nbVertices = sscanf(buffer.c_str(), "%*s %d// %d// %d// %d//", &id1, &id2, &id3, &id4); if (nbVertices == 4) isFaceQuad = true; } else { // If the face definition contains vertices and texture coordinates //get the part of the string until the second index tmp = line.substr(found1+1); found2 = (int)tmp.find(" "); tmp = tmp.substr(0,found2); found2 = (int)tmp.find("/"); // If the face definition is of the form "f vert1/textcoord1 vert2/textcoord2 ..." if(found2 == string::npos) { int n = sscanf(buffer.c_str(), "%*s %d/%d %d/%d %d/%d %d/%d", &id1, &tId1, &id2, &tId2, &id3, &tId3, &id4, &tId4); if (n == 8) isFaceQuad = true; uvsIndices.push_back(tId1-1); uvsIndices.push_back(tId2-1); uvsIndices.push_back(tId3-1); if (isFaceQuad) uvsIndices.push_back(tId4-1); } else { tmp = line.substr(found1+1); found2 = (int)tmp.find("/"); // If the face definition is of the form "f vert1/normal1 vert2/normal2 ..." if(found2 == 0) { int n = sscanf(buffer.c_str(), "%*s %d//%d %d//%d %d//%d %d//%d", &id1, &nId1, &id2, &nId2, &id3, &nId3, &id4, &nId4); if (n == 8) isFaceQuad = true; } // If the face definition is of the form "f vert1/textcoord1/normal1 ..." else { int n = sscanf(buffer.c_str(), "%*s %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d", &id1, &tId1, &nId1, &id2, &tId2, &nId2, &id3, &tId3, &nId3, &id4, &tId4, &nId4); if (n == 12) isFaceQuad = true; uvsIndices.push_back(tId1-1); uvsIndices.push_back(tId2-1); uvsIndices.push_back(tId3-1); if (isFaceQuad) uvsIndices.push_back(tId4-1); } normalsIndices.push_back(nId1-1); normalsIndices.push_back(nId2-1); normalsIndices.push_back(nId3-1); if (isFaceQuad) normalsIndices.push_back(nId4-1); } } verticesIndices.push_back(id1-1); verticesIndices.push_back(id2-1); verticesIndices.push_back(id3-1); if (isFaceQuad) verticesIndices.push_back((id4-1)); isQuad.push_back(isFaceQuad); } } assert(!verticesIndices.empty()); assert(normalsIndices.empty() || normalsIndices.size() == verticesIndices.size()); assert(uvsIndices.empty() || uvsIndices.size() == verticesIndices.size()); meshFile.close(); // ---------- Merge the data that we have collected from the file ---------- // // Destroy the current mesh meshToCreate.destroy(); // Mesh data vector > meshIndices; vector meshNormals; if (!normals.empty()) meshNormals = vector(vertices.size(), Vector3(0, 0, 0)); vector meshUVs; if (!uvs.empty()) meshUVs = vector(vertices.size(), Vector2(0, 0)); // We cannot load mesh with several parts for the moment uint meshPart = 0; // Fill in the vertex indices // We also triangulate each quad face meshIndices.push_back(std::vector()); for(size_t i = 0, j = 0; i < verticesIndices.size(); j++) { // Get the current vertex IDs uint i1 = verticesIndices[i]; uint i2 = verticesIndices[i+1]; uint i3 = verticesIndices[i+2]; // Add the vertex normal if (!normalsIndices.empty() && !normals.empty()) { meshNormals[i1] = normals[normalsIndices[i]]; meshNormals[i2] = normals[normalsIndices[i+1]]; meshNormals[i3] = normals[normalsIndices[i+2]]; } // Add the vertex UV texture coordinates if (!uvsIndices.empty() && !uvs.empty()) { meshUVs[i1] = uvs[uvsIndices[i]]; meshUVs[i2] = uvs[uvsIndices[i+1]]; meshUVs[i3] = uvs[uvsIndices[i+2]]; } // If the current vertex not in a quad (it is part of a triangle) if (!isQuad[j]) { // Add the vertex indices meshIndices[meshPart].push_back(i1); meshIndices[meshPart].push_back(i2); meshIndices[meshPart].push_back(i3); i+=3; } else { // If the current vertex is in a quad Vector3 v1 = vertices[i1]; Vector3 v2 = vertices[i2]; Vector3 v3 = vertices[i3]; uint i4 = verticesIndices[i+3]; Vector3 v4 = vertices[i4]; Vector3 v13 = v3-v1; Vector3 v12 = v2-v1; Vector3 v14 = v4-v1; float a1 = v13.dot(v12); float a2 = v13.dot(v14); if((a1 >= 0 && a2 <= 0) || (a1 <= 0 && a2 >= 0)) { meshIndices[meshPart].push_back(i1); meshIndices[meshPart].push_back(i2); meshIndices[meshPart].push_back(i3); meshIndices[meshPart].push_back(i1); meshIndices[meshPart].push_back(i3); meshIndices[meshPart].push_back(i4); } else { meshIndices[meshPart].push_back(i1); meshIndices[meshPart].push_back(i2); meshIndices[meshPart].push_back(i4); meshIndices[meshPart].push_back(i2); meshIndices[meshPart].push_back(i3); meshIndices[meshPart].push_back(i4); } // Add the vertex normal if (!normalsIndices.empty() && !normals.empty()) { meshNormals[i4] = normals[normalsIndices[i]]; } // Add the vertex UV texture coordinates if (!uvsIndices.empty() && !uvs.empty()) { meshUVs[i4] = uvs[uvsIndices[i]]; } i+=4; } } assert(meshNormals.empty() || meshNormals.size() == vertices.size()); assert(meshUVs.empty() || meshUVs.size() == vertices.size()); // Set the data to the mesh meshToCreate.setIndices(meshIndices); meshToCreate.setVertices(vertices); meshToCreate.setNormals(meshNormals); meshToCreate.setUVs(meshUVs); } // Store a mesh into a OBJ file void MeshReaderWriter::writeOBJFile(const std::string& filename, const Mesh& meshToWrite) { std::ofstream file(filename.c_str()); // Geth the mesh data const std::vector& vertices = meshToWrite.getVertices(); const std::vector& normals = meshToWrite.getNormals(); const std::vector& uvs = meshToWrite.getUVs(); // If we can open the file if (file.is_open()) { assert(meshToWrite.getNbVertices() == vertices.size()); // Write the vertices for (uint v=0; v& indices = meshToWrite.getIndices(p); // For each index of the part for (uint i=0; i