/** ----------------------------------------------------------
* \class VSResModelLib
*
* Lighthouse3D
*
* VSResModelLib - Very Simple Resource Model Library
*
* \version 0.2.0
* Added cubemap textures
* Added the possibility to reuse textures
* Loader gets tangent and bitangent attributes
*
* version 0.1.0
* Initial Release
*
* This lib provides an interface for Assimp to load and render 3D models
* and performs simple resource managment
*
* This lib requires the following classes from VSL:
* (http://www.lighthouse3d.com/very-simple-libs)
*
* VSResourceLib
* VSMathLib
* VSLogLib
* VSShaderLib
*
* and the following third party libs:
*
* GLEW (http://glew.sourceforge.net/),
* Assimp (http://assimp.sourceforge.net/)
*
* Full documentation at
* http://www.lighthouse3d.com/very-simple-libs
*
---------------------------------------------------------------*/
#include "vsResModelLib.h"
VSResModelLib::VSResModelLib():pScene(0), pUseAdjacency(false)
{
mMyMeshes.reserve(10);
pMyMeshesAux.reserve(10);
}
VSResModelLib::~VSResModelLib() {
for (unsigned int i = 0; i < mMyMeshes.size(); ++i) {
glDeleteVertexArrays(1,&(mMyMeshes[i].vao));
for (int t = 0; t < MAX_TEXTURES; ++t ) {
glDeleteTextures(1,&(mMyMeshes[i].texUnits[t]));
}
glDeleteBuffers(1,&(mMyMeshes[i].uniformBlockIndex));
}
mMyMeshes.clear();
}
void
VSResModelLib::clone(VSResourceLib *res) {
if (res == NULL)
return;
VSResModelLib *r = (VSResModelLib *)res;
this->mMyMeshes = r->mMyMeshes;
}
bool
VSResModelLib::load(std::string filename) {
Assimp::Importer importer;
//check if file exists
std::ifstream fin(filename.c_str());
if(!fin.fail())
fin.close();
else {
VSLOG(mLogError, "Unable to open file %s",
filename.c_str());
return false;
}
pScene = importer.ReadFile( filename,
aiProcessPreset_TargetRealtime_Quality);
// If the import failed, report it
if( !pScene) {
VSLOG(mLogError, "Failed to import %s",
filename.c_str());
return false;
}
// Get prefix for texture loading
size_t index = filename.find_last_of("/\\");
std::string prefix = filename.substr(0, index+1);
pTextureIdMap.clear();
bool result = loadTextures(pScene, prefix);
genVAOsAndUniformBuffer(pScene);
// determine bounding box
struct aiVector3D min, max;
min.x = min.y = min.z = 1e10f;
max.x = max.y = max.z = -1e10f;
mVSML->loadIdentity(VSMathLib::AUX0);
get_bounding_box_for_node(pScene->mRootNode,&min,&max);
float tmp;
tmp = max.x - min.x;
tmp = max.y - min.y > tmp? max.y - min.y:tmp;
tmp = max.z - min.z > tmp? max.z - min.z:tmp;
mScaleToUnitCube = 2.0f / tmp;
mCenter[0] = min.x + (max.x - min.x) * 0.5f;
mCenter[1] = min.y + (max.y - min.y) * 0.5f;
mCenter[2] = min.z + (max.z - min.z) * 0.5f;
mVSML->loadIdentity(VSMathLib::AUX0);
mVSML->scale(VSMathLib::AUX0, mScaleToUnitCube, mScaleToUnitCube, mScaleToUnitCube);
mVSML->translate(VSMathLib::AUX0, -mCenter[0], -mCenter[1], -mCenter[2]);
for (unsigned int i = 0; i < mMyMeshes.size(); ++i) {
mVSML->pushMatrix(mVSML->AUX0);
mVSML->multMatrix(mVSML->AUX0, mMyMeshes[i].transform);
memcpy(mMyMeshes[i].transform, mVSML->get(mVSML->AUX0), sizeof(float)*16);
mVSML->popMatrix(mVSML->AUX0);
}
// clear texture map
pTextureIdMap.clear();
return result;
}
void
VSResModelLib::render () {
// we are only going to use texture unit 0
mVSML->pushMatrix(VSMathLib::MODEL);
//mVSML->scale(mScaleToUnitCube, mScaleToUnitCube, mScaleToUnitCube);
//mVSML->translate(-mCenter[0], -mCenter[1], -mCenter[2]);
for (unsigned int i = 0; i < mMyMeshes.size(); ++i) {
mVSML->pushMatrix(VSMathLib::MODEL);
mVSML->multMatrix(VSMathLib::MODEL,
mMyMeshes[i].transform);
// send matrices to shaders
mVSML->matricesToGL();
// set material
setMaterial(mMyMeshes[i].mat);
// bind texture
for (unsigned int j = 0; j < VSResourceLib::MAX_TEXTURES; ++j) {
if (mMyMeshes[i].texUnits[j] != 0) {
glActiveTexture(GL_TEXTURE0 + j);
glBindTexture(mMyMeshes[i].texTypes[j],
mMyMeshes[i].texUnits[j]);
}
}
// bind VAO
glBindVertexArray(mMyMeshes[i].vao);
glDrawElements(mMyMeshes[i].type,
mMyMeshes[i].numIndices*2, GL_UNSIGNED_INT, 0);
for (unsigned int j = 0; j < VSResourceLib::MAX_TEXTURES; ++j) {
if (mMyMeshes[i].texUnits[j] != 0) {
glActiveTexture(GL_TEXTURE0 + j);
glBindTexture(mMyMeshes[i].texTypes[j], 0);
}
}
mVSML->popMatrix(VSMathLib::MODEL);
}
glBindVertexArray(0);
mVSML->popMatrix(VSMathLib::MODEL);
}
// Load model textures
bool
VSResModelLib::loadTextures(const aiScene* scene,
std::string prefix)
{
VSLOG(mLogInfo, "Loading Textures from %s",
prefix.c_str());
/* scan scene's materials for textures */
for (unsigned int m=0; mmNumMaterials; ++m)
{
int texIndex = 0;
aiString path; // filename
aiReturn texFound =
scene->mMaterials[m]->
GetTexture(aiTextureType_DIFFUSE, texIndex, &path);
while (texFound == AI_SUCCESS) {
//fill map with textures, OpenGL image ids set to 0
pTextureIdMap[path.data] = 0;
// more textures?
texIndex++;
texFound =
scene->mMaterials[m]->
GetTexture(aiTextureType_DIFFUSE, texIndex, &path);
}
}
int numTextures = pTextureIdMap.size();
/* get iterator */
std::map::iterator itr =
pTextureIdMap.begin();
for (int i= 0; itr != pTextureIdMap.end(); ++i, ++itr)
{
// get filename
std::string filename = (*itr).first;
filename = prefix + filename;
// save texture id for filename in map
(*itr).second = loadRGBATexture(filename, true,true);
VSLOG(mLogInfo, "Texture %s loaded with name %d",
filename.c_str(), (int)(*itr).second);
}
return true;
}
void
VSResModelLib::genVAOsAndUniformBuffer(const struct aiScene *sc) {
struct MyMesh aMesh;
struct Material aMat;
GLuint buffer;
int totalTris = 0;
unsigned int *adjFaceArray;
VSLOG(mLogInfo, "Number of Meshes: %d",sc->mNumMeshes);
// For each mesh
for (unsigned int n = 0; n < sc->mNumMeshes; ++n)
{
const struct aiMesh* mesh = sc->mMeshes[n];
if (mesh->mPrimitiveTypes != 4) {
aMesh.numIndices = 0;
pMyMeshesAux.push_back(aMesh);
continue;
}
VSLOG(mLogInfo, "Mesh[%d] Triangles %d",n,
mesh->mNumFaces);
totalTris += mesh->mNumFaces;
// create array with faces
// have to convert from Assimp format to array
unsigned int *faceArray;
faceArray = (unsigned int *)malloc(
sizeof(unsigned int) * mesh->mNumFaces * 3);
unsigned int faceIndex = 0;
for (unsigned int t = 0; t < mesh->mNumFaces; ++t) {
const struct aiFace* face = &mesh->mFaces[t];
memcpy(&faceArray[faceIndex], face->mIndices,
3 * sizeof(unsigned int));
faceIndex += 3;
}
if (pUseAdjacency) {
// Create the half edge structure
std::map, struct HalfEdge *> myEdges;
struct HalfEdge *edge;
// fill it up with edges. twin info will be added latter
edge = (struct HalfEdge *)malloc(sizeof(struct HalfEdge) * mesh->mNumFaces * 3);
for (unsigned int i = 0; i < mesh->mNumFaces; ++i) {
edge[i*3].vertex = faceArray[i*3+1];
edge[i*3+1].vertex = faceArray[i*3+2];
edge[i*3+2].vertex = faceArray[i*3];
edge[i*3].next = &edge[i*3+1];
edge[i*3+1].next = &edge[i*3+2];
edge[i*3+2].next = &edge[i*3];
myEdges[std::pair(faceArray[i*3+2],faceArray[i*3])] = &edge[i*3];
myEdges[std::pair(faceArray[i*3],faceArray[i*3+1])] = &edge[i*3+1];
myEdges[std::pair(faceArray[i*3+1],faceArray[i*3+2])] = &edge[i*3+2];
}
// add twin info
std::map, struct HalfEdge *>::iterator iter;
std::pair edgeIndex, twinIndex;
iter = myEdges.begin();
for (; iter != myEdges.end(); ++iter) {
edgeIndex = iter->first;
twinIndex = std::pair(edgeIndex.second, edgeIndex.first);
if (myEdges.count(twinIndex))
iter->second->twin = myEdges[twinIndex];
else
iter->second->twin = NULL;
}
adjFaceArray = (unsigned int *)malloc(sizeof(unsigned int) * mesh->mNumFaces * 6);
for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
// NOTE: twin may be null
adjFaceArray[i*6] = edge[3*i + 0].next->vertex;
adjFaceArray[i*6+1] = edge[3*i + 0].twin?edge[3*i + 0].twin->vertex:edge[3*i + 0].next->vertex;
adjFaceArray[i*6+2] = edge[3*i + 1].next->vertex;
adjFaceArray[i*6+3] = edge[3*i + 1].twin?edge[3*i + 1].twin->vertex:edge[3*i + 1].next->vertex;
adjFaceArray[i*6+4] = edge[3*i + 2].next->vertex;
adjFaceArray[i*6+5] = edge[3*i + 2].twin?edge[3*i + 2].twin->vertex:edge[3*i + 2].next->vertex;
}
}
//printf("\n");
//for (int i = 0; i < mesh->mNumFaces * 3; ++i)
// printf("%d ", faceArray[i]);
//printf("\n");
//for (int i = 0; i < mesh->mNumFaces * 6; ++i)
// printf("%d ", adjFaceArray[i]);
//printf("\n");
aMesh.numIndices = sc->mMeshes[n]->mNumFaces * 3;
// generate Vertex Array for mesh
glGenVertexArrays(1,&(aMesh.vao));
glBindVertexArray(aMesh.vao);
// buffer for faces
glGenBuffers(1, &buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
if (pUseAdjacency) {
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
sizeof(unsigned int) * mesh->mNumFaces * 6,
adjFaceArray, GL_STATIC_DRAW);
free(adjFaceArray);
}
else
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
sizeof(unsigned int) * mesh->mNumFaces * 3,
faceArray, GL_STATIC_DRAW);
free(faceArray);
// buffer for vertex positions
if (mesh->HasPositions()) {
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER,
sizeof(float)*3*mesh->mNumVertices,
mesh->mVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(
VSShaderLib::VERTEX_COORD_ATTRIB);
glVertexAttribPointer(VSShaderLib::VERTEX_COORD_ATTRIB,
3, GL_FLOAT, 0, 0, 0);
}
// buffer for vertex normals
if (mesh->HasNormals()) {
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER,
sizeof(float)*3*mesh->mNumVertices, mesh->mNormals,
GL_STATIC_DRAW);
glEnableVertexAttribArray(VSShaderLib::NORMAL_ATTRIB);
glVertexAttribPointer(VSShaderLib::NORMAL_ATTRIB,
3, GL_FLOAT, 0, 0, 0);
}
// buffer for vertex normals
if (mesh->HasTangentsAndBitangents()) {
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER,
sizeof(float)*3*mesh->mNumVertices, mesh->mTangents,
GL_STATIC_DRAW);
glEnableVertexAttribArray(VSShaderLib::TANGENT_ATTRIB);
glVertexAttribPointer(VSShaderLib::TANGENT_ATTRIB,
3, GL_FLOAT, 0, 0, 0);
}
// buffer for vertex normals
if (mesh->HasTangentsAndBitangents()) {
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER,
sizeof(float)*3*mesh->mNumVertices, mesh->mBitangents,
GL_STATIC_DRAW);
glEnableVertexAttribArray(VSShaderLib::BITANGENT_ATTRIB);
glVertexAttribPointer(VSShaderLib::BITANGENT_ATTRIB,
3, GL_FLOAT, 0, 0, 0);
}
// buffer for vertex texture coordinates
if (mesh->HasTextureCoords(0)) {
float *texCoords = (float *)malloc(
sizeof(float)*2*mesh->mNumVertices);
for (unsigned int k = 0; k < mesh->mNumVertices; ++k) {
texCoords[k*2] = mesh->mTextureCoords[0][k].x;
texCoords[k*2+1] = mesh->mTextureCoords[0][k].y;
}
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER,
sizeof(float)*2*mesh->mNumVertices, texCoords,
GL_STATIC_DRAW);
glEnableVertexAttribArray(
VSShaderLib::TEXTURE_COORD_ATTRIB);
glVertexAttribPointer(
VSShaderLib::TEXTURE_COORD_ATTRIB, 2,
GL_FLOAT, 0, 0, 0);
free(texCoords);
}
// unbind buffers
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER,0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
// create material uniform buffer
struct aiMaterial *mtl =
sc->mMaterials[mesh->mMaterialIndex];
aiString texPath; //contains filename of texture
for (int j = 0; j < VSResourceLib::MAX_TEXTURES; ++j)
aMesh.texUnits[j] = 0;
if(AI_SUCCESS == mtl->GetTexture(aiTextureType_DIFFUSE,
0, &texPath)){
//bind texture
aMesh.texUnits[0] =
pTextureIdMap[texPath.data];
aMesh.texTypes[0] = GL_TEXTURE_2D;
aMat.texCount = 1;
}
else {
aMesh.texUnits[0] = 0;
aMat.texCount = 0;
}
float c[4];
set_float4(c, 0.8f, 0.8f, 0.8f, 1.0f);
aiColor4D diffuse;
if(AI_SUCCESS == aiGetMaterialColor(mtl,
AI_MATKEY_COLOR_DIFFUSE, &diffuse))
color4_to_float4(&diffuse, c);
memcpy(aMat.diffuse, c, sizeof(c));
set_float4(c, 0.2f, 0.2f, 0.2f, 1.0f);
aiColor4D ambient;
if(AI_SUCCESS == aiGetMaterialColor(mtl,
AI_MATKEY_COLOR_AMBIENT, &ambient))
color4_to_float4(&ambient, c);
memcpy(aMat.ambient, c, sizeof(c));
set_float4(c, 0.0f, 0.0f, 0.0f, 1.0f);
aiColor4D specular;
if(AI_SUCCESS == aiGetMaterialColor(mtl,
AI_MATKEY_COLOR_SPECULAR, &specular))
color4_to_float4(&specular, c);
memcpy(aMat.specular, c, sizeof(c));
set_float4(c, 0.0f, 0.0f, 0.0f, 1.0f);
aiColor4D emission;
if(AI_SUCCESS == aiGetMaterialColor(mtl,
AI_MATKEY_COLOR_EMISSIVE, &emission))
color4_to_float4(&emission, c);
memcpy(aMat.emissive, c, sizeof(c));
float shininess = 0.0;
unsigned int max;
aiGetMaterialFloatArray(mtl,
AI_MATKEY_SHININESS, &shininess, &max);
aMat.shininess = shininess;
aMesh.mat = aMat;
pMyMeshesAux.push_back(aMesh);
}
mVSML->loadIdentity(VSMathLib::AUX0);
recursive_walk_for_matrices(sc, sc->mRootNode);
pMyMeshesAux.clear();
VSLOG(mLogInfo, "Total Meshes: %d | Faces: %d",
sc->mNumMeshes, totalTris);
}
#define aisgl_min(x,y) (xx?y:x)
void
VSResModelLib::get_bounding_box_for_node (const struct aiNode* nd,
struct aiVector3D* min,
struct aiVector3D* max)
{
unsigned int n = 0;
mVSML->pushMatrix(VSMathLib::AUX0);
if (nd->mNumMeshes) {
// Get node transformation matrix
struct aiMatrix4x4 m = nd->mTransformation;
// OpenGL matrices are column major
m.Transpose();
// apply node transformation
float aux[16];
memcpy(aux,&m,sizeof(float) * 16);
mVSML->multMatrix(VSMathLib::AUX0, aux);
for (; n < nd->mNumMeshes; ++n) {
const struct aiMesh* mesh =
pScene->mMeshes[nd->mMeshes[n]];
for (unsigned int t = 0; t < mesh->mNumVertices; ++t) {
struct aiVector3D tmp = mesh->mVertices[t];
float a[4], res[4];
a[0] = tmp.x;
a[1] = tmp.y;
a[2] = tmp.z;
a[3] = 1.0f;
mVSML->multMatrixPoint(VSMathLib::AUX0, a, res);
min->x = aisgl_min(min->x,res[0]);
min->y = aisgl_min(min->y,res[1]);
min->z = aisgl_min(min->z,res[2]);
max->x = aisgl_max(max->x,res[0]);
max->y = aisgl_max(max->y,res[1]);
max->z = aisgl_max(max->z,res[2]);
}
}
}
for (n = 0; n < nd->mNumChildren; ++n) {
get_bounding_box_for_node(nd->mChildren[n],min,max);
}
mVSML->popMatrix(VSMathLib::AUX0);
}
void
VSResModelLib::recursive_walk_for_matrices(
const struct aiScene *sc,
const struct aiNode* nd) {
mVSML->pushMatrix(VSMathLib::AUX0);
if (nd->mNumMeshes)
{
// Get node transformation matrix
struct aiMatrix4x4 m = nd->mTransformation;
// OpenGL matrices are column major
m.Transpose();
// save model matrix and apply node transformation
float aux[16];
memcpy(aux,&m,sizeof(float) * 16);
mVSML->multMatrix(VSMathLib::AUX0, aux);
// get matrices for all meshes assigned to this node
for (unsigned int n = 0; n < nd->mNumMeshes; ++n) {
if(sc->mMeshes[nd->mMeshes[n]]->mPrimitiveTypes == 4) {
struct MyMesh aMesh;
memcpy(&aMesh, &(pMyMeshesAux[nd->mMeshes[n]]),
sizeof (aMesh));
memcpy(aMesh.transform,mVSML->get(VSMathLib::AUX0),
sizeof(float)*16);
aMesh.type = GL_TRIANGLES;
mMyMeshes.push_back(aMesh);
}
}
}
// recurse for all children
for (unsigned int n=0; n < nd->mNumChildren; ++n){
recursive_walk_for_matrices(sc, nd->mChildren[n]);
}
mVSML->popMatrix(VSMathLib::AUX0);
}
void
VSResModelLib::setTexture(unsigned int unit, unsigned int textureID, GLenum textureType) {
for (unsigned int i = 0; i < mMyMeshes.size(); ++i) {
mMyMeshes[i].texUnits[unit] = textureID;
mMyMeshes[i].texTypes[unit] = textureType;
mMyMeshes[i].mat.texCount = 1;
}
}
void
VSResModelLib::addTexture(unsigned int unit, std::string filename) {
int textID = loadRGBATexture(filename, true);
for (unsigned int i = 0; i < mMyMeshes.size(); ++i) {
mMyMeshes[i].texUnits[unit] = textID;
mMyMeshes[i].texTypes[unit] = GL_TEXTURE_2D;
mMyMeshes[i].mat.texCount = 1;
}
}
void
VSResModelLib::addCubeMapTexture(unsigned int unit, std::string posX, std::string negX,
std::string posY, std::string negY,
std::string posZ, std::string negZ) {
int textID = loadCubeMapTexture(posX, negX, posY, negY, posZ, negZ);
for (unsigned int i = 0; i < mMyMeshes.size(); ++i) {
mMyMeshes[i].texUnits[unit] = textID;
mMyMeshes[i].texTypes[unit] = GL_TEXTURE_CUBE_MAP;
mMyMeshes[i].mat.texCount = 1;
}
}
// Auxiliary functions to convert Assimp data to float arays
void
VSResModelLib::set_float4(float f[4],
float a, float b, float c, float d)
{
f[0] = a;
f[1] = b;
f[2] = c;
f[3] = d;
}
// Auxiliary functions to convert Assimp data to float arays
void
VSResModelLib::color4_to_float4(const struct aiColor4D *c,
float f[4])
{
f[0] = c->r;
f[1] = c->g;
f[2] = c->b;
f[3] = c->a;
}