394 lines
15 KiB
C++
394 lines
15 KiB
C++
/********************************************************************************
|
|
* 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 <fstream>
|
|
#include <sstream>
|
|
#include <locale>
|
|
#include <cctype>
|
|
#include <map>
|
|
#include <algorithm>
|
|
|
|
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<bool> isQuad;
|
|
std::vector<Vector3> vertices;
|
|
std::vector<Vector3> normals;
|
|
std::vector<Vector2> uvs;
|
|
std::vector<uint> verticesIndices;
|
|
std::vector<uint> normalsIndices;
|
|
std::vector<uint> 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<std::vector<uint> > meshIndices;
|
|
vector<Vector3> meshNormals;
|
|
if (!normals.empty()) meshNormals = vector<Vector3>(vertices.size(), Vector3(0, 0, 0));
|
|
vector<Vector2> meshUVs;
|
|
if (!uvs.empty()) meshUVs = vector<Vector2>(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<uint>());
|
|
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<Vector3>& vertices = meshToWrite.getVertices();
|
|
const std::vector<Vector3>& normals = meshToWrite.getNormals();
|
|
const std::vector<Vector2>& 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<vertices.size(); v++) {
|
|
|
|
file << "v " << vertices[v].x << " " << vertices[v].y << " " << vertices[v].z <<
|
|
std::endl;
|
|
}
|
|
|
|
// Write the normals
|
|
if (meshToWrite.hasNormals()) {
|
|
file << std::endl;
|
|
|
|
assert(meshToWrite.getNbVertices() == normals.size());
|
|
|
|
for (uint v=0; v<normals.size(); v++) {
|
|
|
|
file << "vn " << normals[v].x << " " << normals[v].y << " " << normals[v].z <<
|
|
std::endl;
|
|
}
|
|
}
|
|
|
|
// Write the UVs texture coordinates
|
|
if (meshToWrite.hasUVTextureCoordinates()) {
|
|
file << std::endl;
|
|
|
|
assert(meshToWrite.getNbVertices() == uvs.size());
|
|
|
|
for (uint v=0; v<uvs.size(); v++) {
|
|
|
|
file << "vt " << uvs[v].x << " " << uvs[v].y << std::endl;
|
|
}
|
|
}
|
|
|
|
// Write the faces
|
|
file << std::endl;
|
|
for (uint p=0; p<meshToWrite.getNbParts(); p++) {
|
|
|
|
// Get the indices of the part
|
|
const std::vector<uint>& indices = meshToWrite.getIndices(p);
|
|
|
|
// For each index of the part
|
|
for (uint i=0; i<indices.size(); i+=3) {
|
|
|
|
if (meshToWrite.hasNormals() && meshToWrite.hasUVTextureCoordinates()) {
|
|
file << "f " <<indices[i]+1 << "/" << indices[i]+1 << "/" << indices[i]+1 <<
|
|
" " << indices[i+1]+1 << "/" << indices[i+1]+1 << "/" << indices[i+1]+1 <<
|
|
" " << indices[i+2]+1 << "/" << indices[i+2]+1 << "/" << indices[i+2]+1 <<
|
|
std::endl;
|
|
}
|
|
else if (meshToWrite.hasNormals() || meshToWrite.hasUVTextureCoordinates()) {
|
|
file << "f " <<indices[i]+1 << "/" << indices[i]+1 <<
|
|
" " << indices[i+1]+1 << "/" << indices[i+1]+1 <<
|
|
" " << indices[i+2]+1 << "/" << indices[i+2]+1 << std::endl;
|
|
}
|
|
else {
|
|
file << "f " << indices[i]+1 << " " << indices[i+1]+1 << " " << indices[i+2]+1 <<
|
|
std::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
std::cerr << "Error : Cannot open the file " << filename << std::endl;
|
|
exit(1);
|
|
}
|
|
}
|