mirror of
https://github.com/scratchfoundation/bgfx.git
synced 2024-11-29 02:55:36 -05:00
347 lines
10 KiB
C++
347 lines
10 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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 <stdexcept>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <vector>
|
|
#include <list>
|
|
#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<OBJFaceNode> 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<Vector3> vertices;
|
|
list<Vector2> texCoords;
|
|
list<Vector3> normals;
|
|
list<OBJFace> 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<Vector3> verticesArray(vertices.begin(), vertices.end());
|
|
vector<Vector2> texCoordsArray(texCoords.begin(), texCoords.end());
|
|
vector<Vector3> 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<OBJFace>::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<OBJFace>::iterator i = faces.begin(); i != faces.end(); ++ i)
|
|
{
|
|
OBJFace &f = (*i);
|
|
int nodes[3][3];
|
|
int nodeCount = 0;
|
|
for(list<OBJFaceNode>::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();
|
|
}
|