reactphysics3d/testbed/opengl-framework/src/MeshReaderWriter.cpp
2015-04-08 20:47:55 +02:00

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);
}
}