fysxasteroids/engine/OBJModel.cc

1295 lines
36 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2007 dhpoware. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
//
// The methods normalize() and scale() are based on source code from
// http://www.mvps.org/directx/articles/scalemesh9.htm.
//
// The addVertex() method is based on source code from the Direct3D MeshFromOBJ
// sample found in the DirectX SDK.
//
// The generateTangents() method is based on public source code from
// http://www.terathon.com/code/tangent.php.
//
// The importGeometryFirstPass(), importGeometrySecondPass(), and
// importMaterials() methods are based on source code from Nate Robins' OpenGL
// Tutors programs (http://www.xmission.com/~nate/tutors.html).
//
//-----------------------------------------------------------------------------
#include <algorithm>
#include <cmath>
#include <cstring>
#include <limits>
#include <string>
#include <cstdio>
#include "Engine.h"
#include "OBJModel.h"
namespace Engine {
namespace
{
bool MeshCompFunc(const OBJModel::Mesh &lhs, const OBJModel::Mesh &rhs)
{
return lhs.pMaterial->alpha > rhs.pMaterial->alpha;
}
}
OBJModel::OBJModel()
{
m_hasPositions = false;
m_hasNormals = false;
m_hasTextureCoords = false;
m_hasTangents = false;
m_numberOfVertexCoords = 0;
m_numberOfTextureCoords = 0;
m_numberOfNormals = 0;
m_numberOfTriangles = 0;
m_numberOfMaterials = 0;
m_numberOfMeshes = 0;
m_center[0] = m_center[1] = m_center[2] = 0.0f;
m_width = m_height = m_length = m_radius = 0.0f;
}
OBJModel::~OBJModel()
{
destroy();
}
void OBJModel::bounds(float center[3], float &width, float &height,
float &length, float &radius) const
{
float xMax = std::numeric_limits<float>::min();
float yMax = std::numeric_limits<float>::min();
float zMax = std::numeric_limits<float>::min();
float xMin = std::numeric_limits<float>::max();
float yMin = std::numeric_limits<float>::max();
float zMin = std::numeric_limits<float>::max();
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
int numVerts = static_cast<int>(m_vertexBuffer.size());
for (int i = 0; i < numVerts; ++i)
{
x = m_vertexBuffer[i].position[0];
y = m_vertexBuffer[i].position[1];
z = m_vertexBuffer[i].position[2];
if (x < xMin)
xMin = x;
if (x > xMax)
xMax = x;
if (y < yMin)
yMin = y;
if (y > yMax)
yMax = y;
if (z < zMin)
zMin = z;
if (z > zMax)
zMax = z;
}
center[0] = (xMin + xMax) / 2.0f;
center[1] = (yMin + yMax) / 2.0f;
center[2] = (zMin + zMax) / 2.0f;
width = xMax - xMin;
height = yMax - yMin;
length = zMax - zMin;
radius = std::max(std::max(width, height), length);
}
void OBJModel::destroy()
{
m_hasPositions = false;
m_hasTextureCoords = false;
m_hasNormals = false;
m_hasTangents = false;
m_numberOfVertexCoords = 0;
m_numberOfTextureCoords = 0;
m_numberOfNormals = 0;
m_numberOfTriangles = 0;
m_numberOfMaterials = 0;
m_numberOfMeshes = 0;
m_center[0] = m_center[1] = m_center[2] = 0.0f;
m_width = m_height = m_length = m_radius = 0.0f;
m_directoryPath.clear();
m_meshes.clear();
m_materials.clear();
m_vertexBuffer.clear();
m_indexBuffer.clear();
m_attributeBuffer.clear();
m_vertexCoords.clear();
m_textureCoords.clear();
m_normals.clear();
m_materialCache.clear();
m_vertexCache.clear();
}
bool OBJModel::import(const char *pszFilename, bool rebuildNormals)
{
FILE *pFile = fopen(pszFilename, "r");
if (!pFile)
return false;
// Extract the directory the OBJ file is in from the file name.
// This directory path will be used to load the OBJ's associated MTL file.
m_directoryPath.clear();
boost::filesystem::path full_path (pszFilename);
m_directoryPath = full_path.parent_path().string();
LogDebug ("parent path for %s is %s", pszFilename, m_directoryPath.c_str());
// Import the OBJ file.
importGeometryFirstPass(pFile);
rewind(pFile);
importGeometrySecondPass(pFile);
fclose(pFile);
// Perform post import tasks.
buildMeshes();
bounds(m_center, m_width, m_height, m_length, m_radius);
// Build vertex normals if required.
if (rebuildNormals)
{
generateNormals();
}
else
{
if (!hasNormals())
generateNormals();
}
// Build tangents is required.
for (int i = 0; i < m_numberOfMaterials; ++i)
{
if (!m_materials[i].bumpMapFilename.empty())
{
generateTangents();
break;
}
}
return true;
}
void OBJModel::normalize(float scaleTo, bool center)
{
float width = 0.0f;
float height = 0.0f;
float length = 0.0f;
float radius = 0.0f;
float centerPos[3] = {0.0f};
bounds(centerPos, width, height, length, radius);
float scalingFactor = scaleTo / radius;
float offset[3] = {0.0f};
if (center)
{
offset[0] = -centerPos[0];
offset[1] = -centerPos[1];
offset[2] = -centerPos[2];
}
else
{
offset[0] = 0.0f;
offset[1] = 0.0f;
offset[2] = 0.0f;
}
scale(scalingFactor, offset);
bounds(m_center, m_width, m_height, m_length, m_radius);
}
void OBJModel::reverseWinding()
{
int swap = 0;
// Reverse face winding.
for (int i = 0; i < static_cast<int>(m_indexBuffer.size()); i += 3)
{
swap = m_indexBuffer[i + 1];
m_indexBuffer[i + 1] = m_indexBuffer[i + 2];
m_indexBuffer[i + 2] = swap;
}
float *pNormal = 0;
float *pTangent = 0;
// Invert normals and tangents.
for (int i = 0; i < static_cast<int>(m_vertexBuffer.size()); ++i)
{
pNormal = m_vertexBuffer[i].normal;
pNormal[0] = -pNormal[0];
pNormal[1] = -pNormal[1];
pNormal[2] = -pNormal[2];
pTangent = m_vertexBuffer[i].tangent;
pTangent[0] = -pTangent[0];
pTangent[1] = -pTangent[1];
pTangent[2] = -pTangent[2];
}
}
void OBJModel::scale(float scaleFactor, float offset[3])
{
float *pPosition = 0;
for (int i = 0; i < static_cast<int>(m_vertexBuffer.size()); ++i)
{
pPosition = m_vertexBuffer[i].position;
pPosition[0] += offset[0];
pPosition[1] += offset[1];
pPosition[2] += offset[2];
pPosition[0] *= scaleFactor;
pPosition[1] *= scaleFactor;
pPosition[2] *= scaleFactor;
}
}
void OBJModel::addTrianglePos(int index, int material, int v0, int v1, int v2)
{
Vertex vertex =
{
{0.0f, 0.0f, 0.0f},
{0.0f, 0.0f},
{0.0f, 0.0f, 0.0f}
};
m_attributeBuffer[index] = material;
vertex.position[0] = m_vertexCoords[v0 * 3];
vertex.position[1] = m_vertexCoords[v0 * 3 + 1];
vertex.position[2] = m_vertexCoords[v0 * 3 + 2];
m_indexBuffer[index * 3] = addVertex(v0, &vertex);
vertex.position[0] = m_vertexCoords[v1 * 3];
vertex.position[1] = m_vertexCoords[v1 * 3 + 1];
vertex.position[2] = m_vertexCoords[v1 * 3 + 2];
m_indexBuffer[index * 3 + 1] = addVertex(v1, &vertex);
vertex.position[0] = m_vertexCoords[v2 * 3];
vertex.position[1] = m_vertexCoords[v2 * 3 + 1];
vertex.position[2] = m_vertexCoords[v2 * 3 + 2];
m_indexBuffer[index * 3 + 2] = addVertex(v2, &vertex);
}
void OBJModel::addTrianglePosNormal(int index, int material, int v0, int v1,
int v2, int vn0, int vn1, int vn2)
{
Vertex vertex =
{
{0.0f, 0.0f, 0.0f},
{0.0f, 0.0f},
{0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f}
};
m_attributeBuffer[index] = material;
vertex.position[0] = m_vertexCoords[v0 * 3];
vertex.position[1] = m_vertexCoords[v0 * 3 + 1];
vertex.position[2] = m_vertexCoords[v0 * 3 + 2];
vertex.normal[0] = m_normals[vn0 * 3];
vertex.normal[1] = m_normals[vn0 * 3 + 1];
vertex.normal[2] = m_normals[vn0 * 3 + 2];
m_indexBuffer[index * 3] = addVertex(v0, &vertex);
vertex.position[0] = m_vertexCoords[v1 * 3];
vertex.position[1] = m_vertexCoords[v1 * 3 + 1];
vertex.position[2] = m_vertexCoords[v1 * 3 + 2];
vertex.normal[0] = m_normals[vn1 * 3];
vertex.normal[1] = m_normals[vn1 * 3 + 1];
vertex.normal[2] = m_normals[vn1 * 3 + 2];
m_indexBuffer[index * 3 + 1] = addVertex(v1, &vertex);
vertex.position[0] = m_vertexCoords[v2 * 3];
vertex.position[1] = m_vertexCoords[v2 * 3 + 1];
vertex.position[2] = m_vertexCoords[v2 * 3 + 2];
vertex.normal[0] = m_normals[vn2 * 3];
vertex.normal[1] = m_normals[vn2 * 3 + 1];
vertex.normal[2] = m_normals[vn2 * 3 + 2];
m_indexBuffer[index * 3 + 2] = addVertex(v2, &vertex);
}
void OBJModel::addTrianglePosTexCoord(int index, int material, int v0, int v1,
int v2, int vt0, int vt1, int vt2)
{
Vertex vertex =
{
{0.0f, 0.0f, 0.0f},
{0.0f, 0.0f},
{0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f}
};
m_attributeBuffer[index] = material;
vertex.position[0] = m_vertexCoords[v0 * 3];
vertex.position[1] = m_vertexCoords[v0 * 3 + 1];
vertex.position[2] = m_vertexCoords[v0 * 3 + 2];
vertex.texCoord[0] = m_textureCoords[vt0 * 2];
vertex.texCoord[1] = m_textureCoords[vt0 * 2 + 1];
m_indexBuffer[index * 3] = addVertex(v0, &vertex);
vertex.position[0] = m_vertexCoords[v1 * 3];
vertex.position[1] = m_vertexCoords[v1 * 3 + 1];
vertex.position[2] = m_vertexCoords[v1 * 3 + 2];
vertex.texCoord[0] = m_textureCoords[vt1 * 2];
vertex.texCoord[1] = m_textureCoords[vt1 * 2 + 1];
m_indexBuffer[index * 3 + 1] = addVertex(v1, &vertex);
vertex.position[0] = m_vertexCoords[v2 * 3];
vertex.position[1] = m_vertexCoords[v2 * 3 + 1];
vertex.position[2] = m_vertexCoords[v2 * 3 + 2];
vertex.texCoord[0] = m_textureCoords[vt2 * 2];
vertex.texCoord[1] = m_textureCoords[vt2 * 2 + 1];
m_indexBuffer[index * 3 + 2] = addVertex(v2, &vertex);
}
void OBJModel::addTrianglePosTexCoordNormal(int index, int material, int v0,
int v1, int v2, int vt0, int vt1,
int vt2, int vn0, int vn1, int vn2)
{
Vertex vertex =
{
{0.0f, 0.0f, 0.0f},
{0.0f, 0.0f},
{0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f}
};
m_attributeBuffer[index] = material;
vertex.position[0] = m_vertexCoords[v0 * 3];
vertex.position[1] = m_vertexCoords[v0 * 3 + 1];
vertex.position[2] = m_vertexCoords[v0 * 3 + 2];
vertex.texCoord[0] = m_textureCoords[vt0 * 2];
vertex.texCoord[1] = m_textureCoords[vt0 * 2 + 1];
vertex.normal[0] = m_normals[vn0 * 3];
vertex.normal[1] = m_normals[vn0 * 3 + 1];
vertex.normal[2] = m_normals[vn0 * 3 + 2];
m_indexBuffer[index * 3] = addVertex(v0, &vertex);
vertex.position[0] = m_vertexCoords[v1 * 3];
vertex.position[1] = m_vertexCoords[v1 * 3 + 1];
vertex.position[2] = m_vertexCoords[v1 * 3 + 2];
vertex.texCoord[0] = m_textureCoords[vt1 * 2];
vertex.texCoord[1] = m_textureCoords[vt1 * 2 + 1];
vertex.normal[0] = m_normals[vn1 * 3];
vertex.normal[1] = m_normals[vn1 * 3 + 1];
vertex.normal[2] = m_normals[vn1 * 3 + 2];
m_indexBuffer[index * 3 + 1] = addVertex(v1, &vertex);
vertex.position[0] = m_vertexCoords[v2 * 3];
vertex.position[1] = m_vertexCoords[v2 * 3 + 1];
vertex.position[2] = m_vertexCoords[v2 * 3 + 2];
vertex.texCoord[0] = m_textureCoords[vt2 * 2];
vertex.texCoord[1] = m_textureCoords[vt2 * 2 + 1];
vertex.normal[0] = m_normals[vn2 * 3];
vertex.normal[1] = m_normals[vn2 * 3 + 1];
vertex.normal[2] = m_normals[vn2 * 3 + 2];
m_indexBuffer[index * 3 + 2] = addVertex(v2, &vertex);
}
int OBJModel::addVertex(int hash, const Vertex *pVertex)
{
int index = -1;
std::map<int, std::vector<int> >::const_iterator iter = m_vertexCache.find(hash);
if (iter == m_vertexCache.end())
{
// Vertex hash doesn't exist in the cache.
index = static_cast<int>(m_vertexBuffer.size());
m_vertexBuffer.push_back(*pVertex);
m_vertexCache.insert(std::make_pair(hash, std::vector<int>(1, index)));
}
else
{
// One or more vertices have been hashed to this entry in the cache.
const std::vector<int> &vertices = iter->second;
const Vertex *pCachedVertex = 0;
bool found = false;
for (std::vector<int>::const_iterator i = vertices.begin(); i != vertices.end(); ++i)
{
index = *i;
pCachedVertex = &m_vertexBuffer[index];
if (memcmp(pCachedVertex, pVertex, sizeof(Vertex)) == 0)
{
found = true;
break;
}
}
if (!found)
{
index = static_cast<int>(m_vertexBuffer.size());
m_vertexBuffer.push_back(*pVertex);
m_vertexCache[hash].push_back(index);
}
}
return index;
}
void OBJModel::buildMeshes()
{
// Group the model's triangles based on material type.
Mesh *pMesh = 0;
int materialId = -1;
int numMeshes = 0;
// Count the number of meshes.
for (int i = 0; i < static_cast<int>(m_attributeBuffer.size()); ++i)
{
if (m_attributeBuffer[i] != materialId)
{
materialId = m_attributeBuffer[i];
++numMeshes;
}
}
// Allocate memory for the meshes and reset counters.
m_numberOfMeshes = numMeshes;
m_meshes.resize(m_numberOfMeshes);
numMeshes = 0;
materialId = -1;
// Build the meshes. One mesh for each unique material.
for (int i = 0; i < static_cast<int>(m_attributeBuffer.size()); ++i)
{
if (m_attributeBuffer[i] != materialId)
{
materialId = m_attributeBuffer[i];
pMesh = &m_meshes[numMeshes++];
pMesh->pMaterial = &m_materials[materialId];
pMesh->startIndex = i * 3;
++pMesh->triangleCount;
}
else
{
++pMesh->triangleCount;
}
}
// Sort the meshes based on its material alpha. Fully opaque meshes
// towards the front and fully transparent towards the back.
std::sort(m_meshes.begin(), m_meshes.end(), MeshCompFunc);
}
void OBJModel::generateNormals()
{
const int *pTriangle = 0;
Vertex *pVertex0 = 0;
Vertex *pVertex1 = 0;
Vertex *pVertex2 = 0;
float edge1[3] = {0.0f, 0.0f, 0.0f};
float edge2[3] = {0.0f, 0.0f, 0.0f};
float normal[3] = {0.0f, 0.0f, 0.0f};
float length = 0.0f;
int totalVertices = getNumberOfVertices();
int totalTriangles = getNumberOfTriangles();
// Initialize all the vertex normals.
for (int i = 0; i < totalVertices; ++i)
{
pVertex0 = &m_vertexBuffer[i];
pVertex0->normal[0] = 0.0f;
pVertex0->normal[1] = 0.0f;
pVertex0->normal[2] = 0.0f;
}
// Calculate the vertex normals.
for (int i = 0; i < totalTriangles; ++i)
{
pTriangle = &m_indexBuffer[i * 3];
pVertex0 = &m_vertexBuffer[pTriangle[0]];
pVertex1 = &m_vertexBuffer[pTriangle[1]];
pVertex2 = &m_vertexBuffer[pTriangle[2]];
// Calculate triangle face normal.
edge1[0] = pVertex1->position[0] - pVertex0->position[0];
edge1[1] = pVertex1->position[1] - pVertex0->position[1];
edge1[2] = pVertex1->position[2] - pVertex0->position[2];
edge2[0] = pVertex2->position[0] - pVertex0->position[0];
edge2[1] = pVertex2->position[1] - pVertex0->position[1];
edge2[2] = pVertex2->position[2] - pVertex0->position[2];
normal[0] = (edge1[1] * edge2[2]) - (edge1[2] * edge2[1]);
normal[1] = (edge1[2] * edge2[0]) - (edge1[0] * edge2[2]);
normal[2] = (edge1[0] * edge2[1]) - (edge1[1] * edge2[0]);
// Accumulate the normals.
pVertex0->normal[0] += normal[0];
pVertex0->normal[1] += normal[1];
pVertex0->normal[2] += normal[2];
pVertex1->normal[0] += normal[0];
pVertex1->normal[1] += normal[1];
pVertex1->normal[2] += normal[2];
pVertex2->normal[0] += normal[0];
pVertex2->normal[1] += normal[1];
pVertex2->normal[2] += normal[2];
}
// Normalize the vertex normals.
for (int i = 0; i < totalVertices; ++i)
{
pVertex0 = &m_vertexBuffer[i];
length = 1.0f / sqrtf(pVertex0->normal[0] * pVertex0->normal[0] +
pVertex0->normal[1] * pVertex0->normal[1] +
pVertex0->normal[2] * pVertex0->normal[2]);
pVertex0->normal[0] *= length;
pVertex0->normal[1] *= length;
pVertex0->normal[2] *= length;
}
m_hasNormals = true;
}
void OBJModel::generateTangents()
{
const int *pTriangle = 0;
Vertex *pVertex0 = 0;
Vertex *pVertex1 = 0;
Vertex *pVertex2 = 0;
float edge1[3] = {0.0f, 0.0f, 0.0f};
float edge2[3] = {0.0f, 0.0f, 0.0f};
float texEdge1[2] = {0.0f, 0.0f};
float texEdge2[2] = {0.0f, 0.0f};
float tangent[3] = {0.0f, 0.0f, 0.0f};
float bitangent[3] = {0.0f, 0.0f, 0.0f};
float det = 0.0f;
float nDotT = 0.0f;
float bDotB = 0.0f;
float length = 0.0f;
int totalVertices = getNumberOfVertices();
int totalTriangles = getNumberOfTriangles();
// Initialize all the vertex tangents and bitangents.
for (int i = 0; i < totalVertices; ++i)
{
pVertex0 = &m_vertexBuffer[i];
pVertex0->tangent[0] = 0.0f;
pVertex0->tangent[1] = 0.0f;
pVertex0->tangent[2] = 0.0f;
pVertex0->tangent[3] = 0.0f;
pVertex0->bitangent[0] = 0.0f;
pVertex0->bitangent[1] = 0.0f;
pVertex0->bitangent[2] = 0.0f;
}
// Calculate the vertex tangents and bitangents.
for (int i = 0; i < totalTriangles; ++i)
{
pTriangle = &m_indexBuffer[i * 3];
pVertex0 = &m_vertexBuffer[pTriangle[0]];
pVertex1 = &m_vertexBuffer[pTriangle[1]];
pVertex2 = &m_vertexBuffer[pTriangle[2]];
// Calculate the triangle face tangent and bitangent.
edge1[0] = pVertex1->position[0] - pVertex0->position[0];
edge1[1] = pVertex1->position[1] - pVertex0->position[1];
edge1[2] = pVertex1->position[2] - pVertex0->position[2];
edge2[0] = pVertex2->position[0] - pVertex0->position[0];
edge2[1] = pVertex2->position[1] - pVertex0->position[1];
edge2[2] = pVertex2->position[2] - pVertex0->position[2];
texEdge1[0] = pVertex1->texCoord[0] - pVertex0->texCoord[0];
texEdge1[1] = pVertex1->texCoord[1] - pVertex0->texCoord[1];
texEdge2[0] = pVertex2->texCoord[0] - pVertex0->texCoord[0];
texEdge2[1] = pVertex2->texCoord[1] - pVertex0->texCoord[1];
det = texEdge1[0] * texEdge2[1] - texEdge2[0] * texEdge1[1];
if (fabs(det) < 1e-6f)
{
tangent[0] = 1.0f;
tangent[1] = 0.0f;
tangent[2] = 0.0f;
bitangent[0] = 0.0f;
bitangent[1] = 1.0f;
bitangent[2] = 0.0f;
}
else
{
det = 1.0f / det;
tangent[0] = (texEdge2[1] * edge1[0] - texEdge1[1] * edge2[0]) * det;
tangent[1] = (texEdge2[1] * edge1[1] - texEdge1[1] * edge2[1]) * det;
tangent[2] = (texEdge2[1] * edge1[2] - texEdge1[1] * edge2[2]) * det;
bitangent[0] = (-texEdge2[0] * edge1[0] + texEdge1[0] * edge2[0]) * det;
bitangent[1] = (-texEdge2[0] * edge1[1] + texEdge1[0] * edge2[1]) * det;
bitangent[2] = (-texEdge2[0] * edge1[2] + texEdge1[0] * edge2[2]) * det;
}
// Accumulate the tangents and bitangents.
pVertex0->tangent[0] += tangent[0];
pVertex0->tangent[1] += tangent[1];
pVertex0->tangent[2] += tangent[2];
pVertex0->bitangent[0] += bitangent[0];
pVertex0->bitangent[1] += bitangent[1];
pVertex0->bitangent[2] += bitangent[2];
pVertex1->tangent[0] += tangent[0];
pVertex1->tangent[1] += tangent[1];
pVertex1->tangent[2] += tangent[2];
pVertex1->bitangent[0] += bitangent[0];
pVertex1->bitangent[1] += bitangent[1];
pVertex1->bitangent[2] += bitangent[2];
pVertex2->tangent[0] += tangent[0];
pVertex2->tangent[1] += tangent[1];
pVertex2->tangent[2] += tangent[2];
pVertex2->bitangent[0] += bitangent[0];
pVertex2->bitangent[1] += bitangent[1];
pVertex2->bitangent[2] += bitangent[2];
}
// Orthogonalize and normalize the vertex tangents.
for (int i = 0; i < totalVertices; ++i)
{
pVertex0 = &m_vertexBuffer[i];
// Gram-Schmidt orthogonalize tangent with normal.
nDotT = pVertex0->normal[0] * pVertex0->tangent[0] +
pVertex0->normal[1] * pVertex0->tangent[1] +
pVertex0->normal[2] * pVertex0->tangent[2];
pVertex0->tangent[0] -= pVertex0->normal[0] * nDotT;
pVertex0->tangent[1] -= pVertex0->normal[1] * nDotT;
pVertex0->tangent[2] -= pVertex0->normal[2] * nDotT;
// Normalize the tangent.
length = 1.0f / sqrtf(pVertex0->tangent[0] * pVertex0->tangent[0] +
pVertex0->tangent[1] * pVertex0->tangent[1] +
pVertex0->tangent[2] * pVertex0->tangent[2]);
pVertex0->tangent[0] *= length;
pVertex0->tangent[1] *= length;
pVertex0->tangent[2] *= length;
// Calculate the handedness of the local tangent space.
// The bitangent vector is the cross product between the triangle face
// normal vector and the calculated tangent vector. The resulting
// bitangent vector should be the same as the bitangent vector
// calculated from the set of linear equations above. If they point in
// different directions then we need to invert the cross product
// calculated bitangent vector. We store this scalar multiplier in the
// tangent vector's 'w' component so that the correct bitangent vector
// can be generated in the normal mapping shader's vertex shader.
//
// Normal maps have a left handed coordinate system with the origin
// located at the top left of the normal map texture. The x coordinates
// run horizontally from left to right. The y coordinates run
// vertically from top to bottom. The z coordinates run out of the
// normal map texture towards the viewer. Our handedness calculations
// must take this fact into account as well so that the normal mapping
// shader's vertex shader will generate the correct bitangent vectors.
// Some normal map authoring tools such as Crazybump
// (http://www.crazybump.com/) includes options to allow you to control
// the orientation of the normal map normal's y-axis.
bitangent[0] = (pVertex0->normal[1] * pVertex0->tangent[2]) -
(pVertex0->normal[2] * pVertex0->tangent[1]);
bitangent[1] = (pVertex0->normal[2] * pVertex0->tangent[0]) -
(pVertex0->normal[0] * pVertex0->tangent[2]);
bitangent[2] = (pVertex0->normal[0] * pVertex0->tangent[1]) -
(pVertex0->normal[1] * pVertex0->tangent[0]);
bDotB = bitangent[0] * pVertex0->bitangent[0] +
bitangent[1] * pVertex0->bitangent[1] +
bitangent[2] * pVertex0->bitangent[2];
pVertex0->tangent[3] = (bDotB < 0.0f) ? 1.0f : -1.0f;
pVertex0->bitangent[0] = bitangent[0];
pVertex0->bitangent[1] = bitangent[1];
pVertex0->bitangent[2] = bitangent[2];
}
m_hasTangents = true;
}
void OBJModel::importGeometryFirstPass(FILE *pFile)
{
m_hasTextureCoords = false;
m_hasNormals = false;
m_numberOfVertexCoords = 0;
m_numberOfTextureCoords = 0;
m_numberOfNormals = 0;
m_numberOfTriangles = 0;
int v = 0;
int vt = 0;
int vn = 0;
char buffer[256] = {0};
std::string name;
boost::filesystem::path file_path;
while (fscanf(pFile, "%s", buffer) != EOF)
{
switch (buffer[0])
{
case 'f': // v, v//vn, v/vt, v/vt/vn.
fscanf(pFile, "%s", buffer);
if (strstr(buffer, "//")) // v//vn
{
sscanf(buffer, "%d//%d", &v, &vn);
fscanf(pFile, "%d//%d", &v, &vn);
fscanf(pFile, "%d//%d", &v, &vn);
++m_numberOfTriangles;
while (fscanf(pFile, "%d//%d", &v, &vn) > 0)
++m_numberOfTriangles;
}
else if (sscanf(buffer, "%d/%d/%d", &v, &vt, &vn) == 3) // v/vt/vn
{
fscanf(pFile, "%d/%d/%d", &v, &vt, &vn);
fscanf(pFile, "%d/%d/%d", &v, &vt, &vn);
++m_numberOfTriangles;
while (fscanf(pFile, "%d/%d/%d", &v, &vt, &vn) > 0)
++m_numberOfTriangles;
}
else if (sscanf(buffer, "%d/%d", &v, &vt) == 2) // v/vt
{
fscanf(pFile, "%d/%d", &v, &vt);
fscanf(pFile, "%d/%d", &v, &vt);
++m_numberOfTriangles;
while (fscanf(pFile, "%d/%d", &v, &vt) > 0)
++m_numberOfTriangles;
}
else // v
{
fscanf(pFile, "%d", &v);
fscanf(pFile, "%d", &v);
++m_numberOfTriangles;
while (fscanf(pFile, "%d", &v) > 0)
++m_numberOfTriangles;
}
break;
case 'm': // mtllib
fgets(buffer, sizeof(buffer), pFile);
sscanf(buffer, "%s %s", buffer, buffer);
file_path = m_directoryPath;
file_path /= buffer;
// LogMessage ("full mtl path = %s", file_path.string().c_str());
name = file_path.string();
importMaterials(name.c_str());
break;
case 'v': // v, vt, or vn
switch (buffer[1])
{
case '\0':
fgets(buffer, sizeof(buffer), pFile);
++m_numberOfVertexCoords;
break;
case 'n':
fgets(buffer, sizeof(buffer), pFile);
++m_numberOfNormals;
break;
case 't':
fgets(buffer, sizeof(buffer), pFile);
++m_numberOfTextureCoords;
default:
break;
}
break;
default:
fgets(buffer, sizeof(buffer), pFile);
break;
}
}
m_hasPositions = m_numberOfVertexCoords > 0;
m_hasNormals = m_numberOfNormals > 0;
m_hasTextureCoords = m_numberOfTextureCoords > 0;
// Allocate memory for the OBJ model data.
m_vertexCoords.resize(m_numberOfVertexCoords * 3);
m_textureCoords.resize(m_numberOfTextureCoords * 2);
m_normals.resize(m_numberOfNormals * 3);
m_indexBuffer.resize(m_numberOfTriangles * 3);
m_attributeBuffer.resize(m_numberOfTriangles);
// Define a default material if no materials were loaded.
if (m_numberOfMaterials == 0)
{
Material defaultMaterial =
{
{0.2f, 0.2f, 0.2f, 1.0f},
{0.8f, 0.8f, 0.8f, 1.0f},
{0.0f, 0.0f, 0.0f, 1.0f},
0.0f,
1.0f,
std::string("default"),
std::string(),
std::string()
};
m_materials.push_back(defaultMaterial);
m_materialCache[defaultMaterial.name] = 0;
}
}
void OBJModel::importGeometrySecondPass(FILE *pFile)
{
int v[3] = {0};
int vt[3] = {0};
int vn[3] = {0};
int numVertices = 0;
int numTexCoords = 0;
int numNormals = 0;
int numTriangles = 0;
int activeMaterial = 0;
char buffer[256] = {0};
std::string name;
std::map<std::string, int>::const_iterator iter;
while (fscanf(pFile, "%s", buffer) != EOF)
{
switch (buffer[0])
{
case 'f': // v, v//vn, v/vt, or v/vt/vn.
v[0] = v[1] = v[2] = 0;
vt[0] = vt[1] = vt[2] = 0;
vn[0] = vn[1] = vn[2] = 0;
fscanf(pFile, "%s", buffer);
if (strstr(buffer, "//")) // v//vn
{
sscanf(buffer, "%d//%d", &v[0], &vn[0]);
fscanf(pFile, "%d//%d", &v[1], &vn[1]);
fscanf(pFile, "%d//%d", &v[2], &vn[2]);
v[0] = (v[0] < 0) ? v[0] + numVertices - 1 : v[0] - 1;
v[1] = (v[1] < 0) ? v[1] + numVertices - 1 : v[1] - 1;
v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
vn[0] = (vn[0] < 0) ? vn[0] + numNormals - 1 : vn[0] - 1;
vn[1] = (vn[1] < 0) ? vn[1] + numNormals - 1 : vn[1] - 1;
vn[2] = (vn[2] < 0) ? vn[2] + numNormals - 1 : vn[2] - 1;
addTrianglePosNormal(numTriangles++, activeMaterial,
v[0], v[1], v[2], vn[0], vn[1], vn[2]);
v[1] = v[2];
vn[1] = vn[2];
while (fscanf(pFile, "%d//%d", &v[2], &vn[2]) > 0)
{
v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
vn[2] = (vn[2] < 0) ? vn[2] + numNormals - 1 : vn[2] - 1;
addTrianglePosNormal(numTriangles++, activeMaterial,
v[0], v[1], v[2], vn[0], vn[1], vn[2]);
v[1] = v[2];
vn[1] = vn[2];
}
}
else if (sscanf(buffer, "%d/%d/%d", &v[0], &vt[0], &vn[0]) == 3) // v/vt/vn
{
fscanf(pFile, "%d/%d/%d", &v[1], &vt[1], &vn[1]);
fscanf(pFile, "%d/%d/%d", &v[2], &vt[2], &vn[2]);
v[0] = (v[0] < 0) ? v[0] + numVertices - 1 : v[0] - 1;
v[1] = (v[1] < 0) ? v[1] + numVertices - 1 : v[1] - 1;
v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
vt[0] = (vt[0] < 0) ? vt[0] + numTexCoords - 1 : vt[0] - 1;
vt[1] = (vt[1] < 0) ? vt[1] + numTexCoords - 1 : vt[1] - 1;
vt[2] = (vt[2] < 0) ? vt[2] + numTexCoords - 1 : vt[2] - 1;
vn[0] = (vn[0] < 0) ? vn[0] + numNormals - 1 : vn[0] - 1;
vn[1] = (vn[1] < 0) ? vn[1] + numNormals - 1 : vn[1] - 1;
vn[2] = (vn[2] < 0) ? vn[2] + numNormals - 1 : vn[2] - 1;
addTrianglePosTexCoordNormal(numTriangles++, activeMaterial,
v[0], v[1], v[2], vt[0], vt[1], vt[2], vn[0], vn[1], vn[2]);
v[1] = v[2];
vt[1] = vt[2];
vn[1] = vn[2];
while (fscanf(pFile, "%d/%d/%d", &v[2], &vt[2], &vn[2]) > 0)
{
v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
vt[2] = (vt[2] < 0) ? vt[2] + numTexCoords - 1 : vt[2] - 1;
vn[2] = (vn[2] < 0) ? vn[2] + numNormals - 1 : vn[2] - 1;
addTrianglePosTexCoordNormal(numTriangles++, activeMaterial,
v[0], v[1], v[2], vt[0], vt[1], vt[2], vn[0], vn[1], vn[2]);
v[1] = v[2];
vt[1] = vt[2];
vn[1] = vn[2];
}
}
else if (sscanf(buffer, "%d/%d", &v[0], &vt[0]) == 2) // v/vt
{
fscanf(pFile, "%d/%d", &v[1], &vt[1]);
fscanf(pFile, "%d/%d", &v[2], &vt[2]);
v[0] = (v[0] < 0) ? v[0] + numVertices - 1 : v[0] - 1;
v[1] = (v[1] < 0) ? v[1] + numVertices - 1 : v[1] - 1;
v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
vt[0] = (vt[0] < 0) ? vt[0] + numTexCoords - 1 : vt[0] - 1;
vt[1] = (vt[1] < 0) ? vt[1] + numTexCoords - 1 : vt[1] - 1;
vt[2] = (vt[2] < 0) ? vt[2] + numTexCoords - 1 : vt[2] - 1;
addTrianglePosTexCoord(numTriangles++, activeMaterial,
v[0], v[1], v[2], vt[0], vt[1], vt[2]);
v[1] = v[2];
vt[1] = vt[2];
while (fscanf(pFile, "%d/%d", &v[2], &vt[2]) > 0)
{
v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
vt[2] = (vt[2] < 0) ? vt[2] + numTexCoords - 1 : vt[2] - 1;
addTrianglePosTexCoord(numTriangles++, activeMaterial,
v[0], v[1], v[2], vt[0], vt[1], vt[2]);
v[1] = v[2];
vt[1] = vt[2];
}
}
else // v
{
sscanf(buffer, "%d", &v[0]);
fscanf(pFile, "%d", &v[1]);
fscanf(pFile, "%d", &v[2]);
v[0] = (v[0] < 0) ? v[0] + numVertices - 1 : v[0] - 1;
v[1] = (v[1] < 0) ? v[1] + numVertices - 1 : v[1] - 1;
v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
addTrianglePos(numTriangles++, activeMaterial, v[0], v[1], v[2]);
v[1] = v[2];
while (fscanf(pFile, "%d", &v[2]) > 0)
{
v[2] = (v[2] < 0) ? v[2] + numVertices - 1 : v[2] - 1;
addTrianglePos(numTriangles++, activeMaterial, v[0], v[1], v[2]);
v[1] = v[2];
}
}
break;
case 'u': // usemtl
fgets(buffer, sizeof(buffer), pFile);
sscanf(buffer, "%s %s", buffer, buffer);
name = buffer;
iter = m_materialCache.find(buffer);
activeMaterial = (iter == m_materialCache.end()) ? 0 : iter->second;
break;
case 'v': // v, vn, or vt.
switch (buffer[1])
{
case '\0': // v
fscanf(pFile, "%f %f %f",
&m_vertexCoords[3 * numVertices],
&m_vertexCoords[3 * numVertices + 1],
&m_vertexCoords[3 * numVertices + 2]);
++numVertices;
break;
case 'n': // vn
fscanf(pFile, "%f %f %f",
&m_normals[3 * numNormals],
&m_normals[3 * numNormals + 1],
&m_normals[3 * numNormals + 2]);
++numNormals;
break;
case 't': // vt
fscanf(pFile, "%f %f",
&m_textureCoords[2 * numTexCoords],
&m_textureCoords[2 * numTexCoords + 1]);
++numTexCoords;
break;
default:
break;
}
break;
default:
fgets(buffer, sizeof(buffer), pFile);
break;
}
}
}
bool OBJModel::importMaterials(const char *pszFilename)
{
LogMessage ("Loading material %s", pszFilename);
FILE *pFile = fopen(pszFilename, "r");
if (!pFile)
return false;
Material *pMaterial = 0;
int illum = 0;
int numMaterials = 0;
char buffer[256] = {0};
// Count the number of materials in the MTL file.
while (fscanf(pFile, "%s", buffer) != EOF)
{
switch (buffer[0])
{
case 'n': // newmtl
++numMaterials;
fgets(buffer, sizeof(buffer), pFile);
sscanf(buffer, "%s %s", buffer, buffer);
break;
default:
fgets(buffer, sizeof(buffer), pFile);
break;
}
}
rewind(pFile);
m_numberOfMaterials = numMaterials;
LogMessage ("file contains %d materials", m_numberOfMaterials);
numMaterials = 0;
m_materials.resize(m_numberOfMaterials);
boost::filesystem::path file_path;
// Load the materials in the MTL file.
while (fscanf(pFile, "%s", buffer) != EOF)
{
switch (buffer[0])
{
case 'N': // Ns
fscanf(pFile, "%f", &pMaterial->shininess);
// Wavefront .MTL file shininess is from [0,1000].
// Scale back to a generic [0,1] range.
pMaterial->shininess /= 1000.0f;
break;
case 'K': // Ka, Kd, or Ks
switch (buffer[1])
{
case 'a': // Ka
fscanf(pFile, "%f %f %f",
&pMaterial->ambient[0],
&pMaterial->ambient[1],
&pMaterial->ambient[2]);
pMaterial->ambient[3] = 1.0f;
break;
case 'd': // Kd
fscanf(pFile, "%f %f %f",
&pMaterial->diffuse[0],
&pMaterial->diffuse[1],
&pMaterial->diffuse[2]);
pMaterial->diffuse[3] = 1.0f;
break;
case 's': // Ks
fscanf(pFile, "%f %f %f",
&pMaterial->specular[0],
&pMaterial->specular[1],
&pMaterial->specular[2]);
pMaterial->specular[3] = 1.0f;
break;
default:
fgets(buffer, sizeof(buffer), pFile);
break;
}
break;
case 'T': // Tr
switch (buffer[1])
{
case 'r': // Tr
fscanf(pFile, "%f", &pMaterial->alpha);
pMaterial->alpha = 1.0f - pMaterial->alpha;
break;
default:
fgets(buffer, sizeof(buffer), pFile);
break;
}
break;
case 'd':
fscanf(pFile, "%f", &pMaterial->alpha);
break;
case 'i': // illum
fscanf(pFile, "%d", &illum);
if (illum == 1)
{
pMaterial->specular[0] = 0.0f;
pMaterial->specular[1] = 0.0f;
pMaterial->specular[2] = 0.0f;
pMaterial->specular[3] = 1.0f;
}
break;
case 'm': // map_Kd, map_bump
if (strstr(buffer, "map_Kd") != 0)
{
fgets(buffer, sizeof(buffer), pFile);
sscanf(buffer, "%s %s", buffer, buffer);
file_path = m_directoryPath;
file_path /= buffer;
pMaterial->colorMapFilename = file_path.string();
LogMessage ("colorMapFilename = %s", pMaterial->colorMapFilename.c_str());
}
else if (strstr(buffer, "map_bump") != 0)
{
fgets(buffer, sizeof(buffer), pFile);
sscanf(buffer, "%s %s", buffer, buffer);
file_path = m_directoryPath;
file_path /= buffer;
pMaterial->bumpMapFilename = file_path.string();
LogMessage ("bumpMapFilename = %s", pMaterial->bumpMapFilename.c_str());
}
else
{
fgets(buffer, sizeof(buffer), pFile);
}
break;
case 'n': // newmtl
fgets(buffer, sizeof(buffer), pFile);
sscanf(buffer, "%s %s", buffer, buffer);
pMaterial = &m_materials[numMaterials];
pMaterial->ambient[0] = 0.2f;
pMaterial->ambient[1] = 0.2f;
pMaterial->ambient[2] = 0.2f;
pMaterial->ambient[3] = 1.0f;
pMaterial->diffuse[0] = 0.8f;
pMaterial->diffuse[1] = 0.8f;
pMaterial->diffuse[2] = 0.8f;
pMaterial->diffuse[3] = 1.0f;
pMaterial->specular[0] = 0.0f;
pMaterial->specular[1] = 0.0f;
pMaterial->specular[2] = 0.0f;
pMaterial->specular[3] = 1.0f;
pMaterial->shininess = 0.0f;
pMaterial->alpha = 1.0f;
pMaterial->name = buffer;
pMaterial->colorMapFilename.clear();
pMaterial->bumpMapFilename.clear();
m_materialCache[pMaterial->name] = numMaterials;
++numMaterials;
break;
default:
fgets(buffer, sizeof(buffer), pFile);
break;
}
}
fclose(pFile);
return true;
}
}