topical media & game development
lib-of-vs-addons-ofx3DModelLoader-src-3DS-model3DS.cpp / cpp
/*
3DS model loader
� Keith O'Conor 2005
keith.oconor @ {cs.tcd.ie, gmail.com}
*/
include <model3DS.h>
model3DS::model3DS(){
hasTexture = false;
}
void model3DS::loadModel(string filename, float scale){
filename = ofToDataPath(filename);
loadModel((const char *)filename.c_str(), scale);
}
void model3DS::loadModel(const char* filename, float scale){
m_filename = filename;
m_scale = scale;
std::ifstream *modelFile = new std::ifstream(filename,std::ios::in | std::ios::binary | std::ios::ate);
if(!modelFile->is_open()){
std::cout<<"[3DS] ERROR: Could not open '"<<filename<<"'"<<std::endl;
return;
}
if(int(modelFile->tellg()) == 0){
std::cout<<"[3DS] ERROR: Model '"<<filename<<"' is empty"<<std::endl;
modelFile->close();
return;
}
// Extract path from filename
int lastSlashPosition=-1, lastForwardSlash=-1, lastBackslash=-1;
lastForwardSlash = (int)m_filename.find_last_of('/');
lastBackslash = (int)m_filename.find_last_of('\\');
if(lastForwardSlash > lastSlashPosition) lastSlashPosition = lastForwardSlash;
if(lastBackslash > lastSlashPosition) lastSlashPosition = lastBackslash;
m_filepath = m_filename.substr(0,lastSlashPosition+1);
m_filename = m_filename.substr(lastSlashPosition+1);
// Check to make sure file is valid 3DS format (begins with 0x4D4D)
ushort chunkHeader;
unsigned int chunkLength;
modelFile->seekg(0, std::ios::beg);
modelFile->read((char*)&chunkHeader,2);
modelFile->read((char*)&chunkLength,4);
if(chunkHeader != CHUNK_MAIN){
std::cout<<"[3DS] ERROR: Model '"<<filename<<"' is not a valid 3DS file"<<std::endl;
modelFile->close();
return;
}
// Detect VBO support
std::stringstream extStream((const char*)glGetString(GL_EXTENSIONS));
std::string nextToken;
bool isVBOSupported=false;
while(!extStream.eof()){
extStream >> nextToken;
if(nextToken == "GL_ARB_vertex_buffer_object"){
isVBOSupported=true;
break;
}
}
m_drawMode = DRAW_VERTEX_ARRAY;
// Initialise bounding box to min & max 4-byte float values
m_boundingBox.minX = m_boundingBox.minY = m_boundingBox.minZ = 3.4e+38f;
m_boundingBox.maxX = m_boundingBox.maxY = m_boundingBox.maxZ = 3.4e-38f;
// Read all 3DS chunks recursively
long end;
modelFile->seekg (0, ios::end);
end = modelFile->tellg();
modelFile->seekg (0, ios::beg);
while(!modelFile->eof() && (end != modelFile->tellg())){
readChunk(modelFile, modelFile->tellg(), chunkLength);
}
m_centerX = (m_boundingBox.minX + m_boundingBox.maxX) / 2.f;
m_centerY = (m_boundingBox.minY + m_boundingBox.maxY) / 2.f;
m_centerZ = (m_boundingBox.minZ + m_boundingBox.maxZ) / 2.f;
// Model loaded, clean up
modelFile->close();
delete modelFile;
std::cout<<"[3DS] Model '"<<filename<<"' loaded"<<std::endl;
}
void model3DS::readChunk(std::ifstream *modelFile, const int objectStart, const int objectLength){
//std::cout<<std::hex<<"readChunk("<<objectStart<<"-"<<(objectStart+objectLength)<<")"<<std::dec<<std::endl;
ushort chunkHeader;
unsigned int chunkLength;
unsigned long offset;
ushort numVertices;
ushort usTemp;
unsigned int uiTemp;
float vertexX,vertexY,vertexZ;
int v;
std::string name;
char currentLetter;
unsigned char rgbByte;
while((modelFile->tellg() < (objectStart + objectLength)) && !modelFile->eof()){
offset = modelFile->tellg();
modelFile->read((char*)&chunkHeader, 2);
modelFile->read((char*)&chunkLength, 4);
if(DEBUG_OUTPUT) std::cout<<std::hex<<"["<<offset<<"] chunk: 0x"<<chunkHeader<<" ("<<offset<<"-"<<(offset+chunkLength)<<")"<<std::dec<<std::endl;
switch(chunkHeader){
////////////
// Main chunks
//////////
case CHUNK_MAIN: continue;
case CHUNK_3D_EDITOR: continue;
case CHUNK_OBJECT_BLOCK:
if(DEBUG_OUTPUT) std::cout<<std::endl<<"[Object block]"<<std::endl;
m_currentMesh = new mesh3DS(this);
m_currentMesh->setDrawMode(m_drawMode);
// Read object name
do{
modelFile->read(¤tLetter,1);
name += currentLetter;
}while(currentLetter!='\0' && name.length()<20);
m_currentMesh->setName(name);
if(DEBUG_OUTPUT) std::cout<<" Object: "<<name<<std::endl;
name.erase();
// Read object sub-chunks
readChunk(modelFile, modelFile->tellg(), chunkLength - (long(modelFile->tellg()) - offset));
if(m_currentMesh->getNumFaces() != 0){
m_currentMesh->buildMesh();
m_meshes.push_back(*m_currentMesh);
}
delete m_currentMesh;
break;
///////////////
// Geometry chunks
/////////////
case CHUNK_MESH:continue;
case CHUNK_VERTICES:
modelFile->read((char*)&numVertices,2);
for(v=0; v < numVertices*3; v+=3){
modelFile->read((char*)&vertexX,4);
modelFile->read((char*)&vertexY,4);
modelFile->read((char*)&vertexZ,4);
// 3DS Max has different axes to OpenGL
vertexX *= m_scale;
vertexY *= m_scale;
vertexZ *= m_scale;
m_currentMesh->addVertex(vertexX);// x
m_currentMesh->addVertex(vertexZ);// y
m_currentMesh->addVertex(-vertexY);// z
// Update bounding box
if(vertexX < m_boundingBox.minX)m_boundingBox.minX = vertexX;
if(vertexZ < m_boundingBox.minY)m_boundingBox.minY = vertexZ;
if(-vertexY < m_boundingBox.minZ)m_boundingBox.minZ = -vertexY;
if(vertexX > m_boundingBox.maxX)m_boundingBox.maxX = vertexX;
if(vertexZ > m_boundingBox.maxY)m_boundingBox.maxY = vertexZ;
if(-vertexY > m_boundingBox.maxZ)m_boundingBox.maxZ = -vertexY;
}
break;
case CHUNK_TEXCOORDS: // texcoords list
modelFile->read((char*)&numVertices,2);
for(v=0; v < numVertices*2; v+=2){
modelFile->read((char*)&vertexX,4);
modelFile->read((char*)&vertexY,4);
m_currentMesh->addTexcoord(vertexX);
m_currentMesh->addTexcoord(vertexY);
}
break;
case CHUNK_FACES:
modelFile->read((char*)&m_tempUshort,2);
for(v=0; v < m_tempUshort*3; v+=3){
modelFile->read((char*)&usTemp,2);m_currentMesh->addFaceIndex(usTemp);
modelFile->read((char*)&usTemp,2);m_currentMesh->addFaceIndex(usTemp);
modelFile->read((char*)&usTemp,2);m_currentMesh->addFaceIndex(usTemp);
modelFile->read((char*)&usTemp,2); //face flags
}
// Read face sub-chunks
readChunk(modelFile, modelFile->tellg(), chunkLength - (long(modelFile->tellg()) - offset));
break;
case CHUNK_SMOOTHING_GROUP:
for(v=0; v < m_tempUshort; v++){
modelFile->read((char*)&uiTemp,4);
m_currentMesh->addFaceSmoothing(uiTemp);
//if(DEBUG_OUTPUT) std::cout<<"Smoothing: "<<uiTemp<<std::endl;
}
break;
///////////////
// Material chunks
/////////////
case CHUNK_FACE_MATERIAL:
// Read material name
do{
modelFile->read(¤tLetter,1);
name += currentLetter;
}while(currentLetter!='\0' && name.length()<20);
modelFile->read((char*)&m_tempUshort,2);
for(v=0; v < m_tempUshort; v++){
modelFile->read((char*)&usTemp,2);
m_currentMesh->addMaterialFace(name, usTemp);
}
name.erase();
break;
case CHUNK_MATERIAL_BLOCK:
if(DEBUG_OUTPUT) std::cout<<std::endl<<"[Material block]"<<std::endl;
m_currentMaterial = new material3DS();
// Read material sub-chunks
readChunk(modelFile, modelFile->tellg(), chunkLength - (long(modelFile->tellg()) - offset));
m_materials[m_currentMaterial->getName()] = *m_currentMaterial;
delete m_currentMaterial;
break;
case CHUNK_MATERIAL_NAME:
// Read material name and add to current material
do{
modelFile->read(¤tLetter,1);
name += currentLetter;
}while(currentLetter!='\0' && name.length()<20);
m_currentMaterial->setName(name);
if(DEBUG_OUTPUT) std::cout<<" Material: "<<m_currentMaterial->getName()<<"("<<m_currentMaterial->getName().size()<<")"<<std::endl;
name.erase();
break;
case CHUNK_TEXTURE_MAP:
case CHUNK_BUMP_MAP:
//Read texture name and add to current material
readChunk(modelFile, modelFile->tellg(), chunkLength - (long(modelFile->tellg()) - offset));
m_currentMaterial->loadTexture(m_filepath + m_tempString, chunkHeader);
hasTexture = true;
break;
case CHUNK_MAP_FILENAME:
// Read texture map filename
m_tempString.erase();
do{
modelFile->read(¤tLetter,1);
m_tempString += currentLetter;
}while(currentLetter!='\0' && m_tempString.length()<20);
break;
case CHUNK_MATERIAL_TWO_SIDED:
m_currentMaterial->setTwoSided(true);
break;
case CHUNK_DIFFUSE_COLOR:
// Read color sub-chunks
readChunk(modelFile, modelFile->tellg(), chunkLength - (long(modelFile->tellg()) - offset));
m_currentMaterial->setDiffuseColor(m_currentColor);
break;
case CHUNK_AMBIENT_COLOR:
// Read color sub-chunks
readChunk(modelFile, modelFile->tellg(), chunkLength - (long(modelFile->tellg()) - offset));
m_currentMaterial->setAmbientColor(m_currentColor);
break;
case CHUNK_SPECULAR_COLOR:
// Read color sub-chunks
readChunk(modelFile, modelFile->tellg(), chunkLength - (long(modelFile->tellg()) - offset));
m_currentMaterial->setSpecularColor(m_currentColor);
break;
case CHUNK_SPECULAR_EXPONENT:
// Read percent sub-chunk
readChunk(modelFile, modelFile->tellg(), chunkLength - (long(modelFile->tellg()) - offset));
m_currentMaterial->setSpecularExponent(m_tempFloat);
break;
case CHUNK_SHININESS:
// Read percent sub-chunk
readChunk(modelFile, modelFile->tellg(), chunkLength - (long(modelFile->tellg()) - offset));
m_currentMaterial->setShininess(m_tempFloat);
break;
case CHUNK_TRANSPARENCY:
// Read percent sub-chunk
readChunk(modelFile, modelFile->tellg(), chunkLength - (long(modelFile->tellg()) - offset));
m_currentMaterial->setOpacity(1.0f - m_tempFloat);
break;
///////////////
// Global chunks
/////////////
case CHUNK_RGB_FLOAT:
case CHUNK_RGB_FLOAT_GAMMA:
modelFile->read((char*)&m_currentColor[0],4);
modelFile->read((char*)&m_currentColor[1],4);
modelFile->read((char*)&m_currentColor[2],4);
break;
case CHUNK_RGB_BYTE:
case CHUNK_RGB_BYTE_GAMMA:
modelFile->read((char*)&rgbByte,1); m_currentColor[0]=float(rgbByte)/255.f;
modelFile->read((char*)&rgbByte,1); m_currentColor[1]=float(rgbByte)/255.f;
modelFile->read((char*)&rgbByte,1); m_currentColor[2]=float(rgbByte)/255.f;
break;
case CHUNK_PERCENT_INT:
modelFile->read((char*)&usTemp,2);
m_tempFloat = usTemp / 100.f;
break;
case CHUNK_PERCENT_FLOAT:
modelFile->read((char*)&m_tempFloat,4);
m_tempFloat /= 100.f;
break;
default:break; // any other chunk
}
// Go to the next chunk's header (if any left in object)
modelFile->seekg(offset + chunkLength, std::ios::beg);
}
}
void material3DS::loadTexture(std::string filename, int chunkType){
string lowerCaseStr = filename;
std::transform(lowerCaseStr.begin(),lowerCaseStr.end(),lowerCaseStr.begin(), ::tolower);
if((lowerCaseStr.find(".jpg") == std::string::npos) && (lowerCaseStr.find(".png") == std::string::npos) && (lowerCaseStr.find(".tga") == std::string::npos) && (lowerCaseStr.find(".bmp") == std::string::npos)){
std::cout<<"[3DS] WARNING: Could not load map '"<<filename<<"'\n[3DS] WARNING: (texture must be TGA, PNG, JPG or BMP)"<<std::endl;
return;
}
GLuint newTextureId;
glGenTextures(1, &newTextureId);
texture3DS newTexture(filename, newTextureId);
switch(chunkType){
case CHUNK_TEXTURE_MAP:
m_textureMapId = newTextureId;
m_hasTextureMap = true;
break;
case CHUNK_BUMP_MAP:
m_bumpMapId = newTextureId;
m_hasBumpMap = true;
break;
}
}
void mesh3DS::buildMesh(){
calculateNormals();
sortFacesByMaterial();
}
void mesh3DS::calculateNormals(){
// Doesn't take smoothing groups into account yet
if(DEBUG_OUTPUT) std::cout<<"Calculating normals... ";
m_normals.assign(m_vertices.size(), 0.0f);
Vertex vtx1, vtx2, vtx3;
Vector3DS v1, v2, faceNormal;
for(int face=0; face < int(m_faces.size()); face+=3){
// Calculate face normal
vtx1.set(m_vertices[m_faces[face]*3], m_vertices[(m_faces[face]*3)+1], m_vertices[(m_faces[face]*3)+2]);
vtx2.set(m_vertices[m_faces[face+1]*3], m_vertices[(m_faces[face+1]*3)+1], m_vertices[(m_faces[face+1]*3)+2]);
vtx3.set(m_vertices[m_faces[face+2]*3], m_vertices[(m_faces[face+2]*3)+1], m_vertices[(m_faces[face+2]*3)+2]);
v1 = vtx2 - vtx1;
v2 = vtx3 - vtx1;
faceNormal = v1.crossProduct(v2);
// Add normal to all three vertex normals
m_normals[m_faces[face]*3] += faceNormal.x;
m_normals[(m_faces[face]*3)+1] += faceNormal.y;
m_normals[(m_faces[face]*3)+2] += faceNormal.z;
m_normals[m_faces[face+1]*3] += faceNormal.x;
m_normals[(m_faces[face+1]*3)+1] += faceNormal.y;
m_normals[(m_faces[face+1]*3)+2] += faceNormal.z;
m_normals[m_faces[face+2]*3] += faceNormal.x;
m_normals[(m_faces[face+2]*3)+1] += faceNormal.y;
m_normals[(m_faces[face+2]*3)+2] += faceNormal.z;
}
//normalize all normals
for(int n=0; n < int(m_normals.size()); n+=3){
faceNormal.set(m_normals[n], m_normals[n+1], m_normals[n+2]);
faceNormal.normalize();
m_normals[n] = faceNormal.x;
m_normals[n+1] = faceNormal.y;
m_normals[n+2] = faceNormal.z;
}
if(DEBUG_OUTPUT) std::cout<<"done"<<std::endl;
}
void mesh3DS::sortFacesByMaterial(){
assert(getNumFaces()!=0);
assert(m_parentModel!=NULL);
std::vector<ushort> newMatFaces;
// mark each face off as assigned to a material so
// we can figure out which faces have no material
std::vector<bool> assignedFaces;
std::vector<bool>::iterator assignedFacesIter;
assignedFaces.assign(m_faces.size() / 3, false);
// loop over each material
std::map<std::string, std::vector<ushort> >::iterator matFacesIter;
for(matFacesIter=m_materialFaces.begin(); matFacesIter!=m_materialFaces.end(); ++matFacesIter){
//std::cout<<" Faces in material '"<<matFacesIter->first<<"': "<<matFacesIter->second.size()<<std::endl;
// loop over all the faces with that material
std::vector<ushort>::iterator facesIter;
for(facesIter=matFacesIter->second.begin(); facesIter!=matFacesIter->second.end(); ++facesIter){
newMatFaces.push_back(m_faces[((*facesIter)*3)]);
newMatFaces.push_back(m_faces[((*facesIter)*3)+1]);
newMatFaces.push_back(m_faces[((*facesIter)*3)+2]);
assignedFaces[*facesIter]=true;
}
//replace the material's face indices with the actual face vertex indices
m_materialFaces[matFacesIter->first].assign(newMatFaces.begin(),newMatFaces.end());
newMatFaces.clear();
}
// Make a default material and assign any unused faces to it
int numUnassignedFaces=0;
for(assignedFacesIter=assignedFaces.begin(); assignedFacesIter!=assignedFaces.end(); ++assignedFacesIter){
if(*assignedFacesIter == false){
numUnassignedFaces++;
//assign face to default material
}
}
//std::cout<<"numUnassignedFaces: "<<numUnassignedFaces<<std::endl;
}
void mesh3DS::draw(){
assert(getNumFaces()!=0);
int face, numFaces, vertexIndex, texcoordIndex;
GLuint materialFaces; //GL_FRONT or GL_FRONT_AND_BACK
std::map<std::string, std::vector<ushort> >::iterator materialsIter;
for(materialsIter=m_materialFaces.begin(); materialsIter!=m_materialFaces.end(); ++materialsIter){
const material3DS& currentMaterial = m_parentModel->getMaterial(materialsIter->first);
// Bind texture map (if any)
bool hasTextureMap = currentMaterial.hasTextureMap();
if(hasTextureMap) glBindTexture(GL_TEXTURE_2D, currentMaterial.getTextureMapId());
else glBindTexture(GL_TEXTURE_2D, 0);
const GLfloat *specular = currentMaterial.getSpecularColor();
float shininess = currentMaterial.getShininess();
float adjustedSpecular[4] = {specular[0]*shininess, specular[1]*shininess, specular[2]*shininess, 1};
glPushAttrib(GL_LIGHTING_BIT);
if(currentMaterial.isTwoSided()){
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,1);
materialFaces = GL_FRONT_AND_BACK;
}
else{
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,0);
materialFaces = GL_FRONT;
}
// Apply material colors
if(glIsEnabled(GL_LIGHTING)){
//const GLfloat matZero[4]={0,0,0,0};
const GLfloat matOne[4]={1,1,1,1};
if(hasTextureMap){ //replace color with texture, but keep lighting contribution
glMaterialfv(materialFaces, GL_DIFFUSE, matOne);
}
else glMaterialfv(materialFaces, GL_DIFFUSE, currentMaterial.getDiffuseColor());
glMaterialfv(materialFaces, GL_AMBIENT, currentMaterial.getAmbientColor());
glMaterialfv(materialFaces, GL_SPECULAR, adjustedSpecular);
glMaterialf(materialFaces, GL_SHININESS, 128.f * currentMaterial.getSpecularExponent());
}
else glColor3fv(currentMaterial.getDiffuseColor());
const std::vector<ushort> *currentMatFaces = &(materialsIter->second);
numFaces = (int)currentMatFaces->size(); //number of faces in this material
switch(m_drawMode){
case DRAW_IMMEDIATE_MODE:
glBegin(GL_TRIANGLES);
for(face=0; face<numFaces; face+=3){
if(hasTextureMap){
texcoordIndex = (*currentMatFaces)[face]*2;
glTexCoord2f(m_texcoords[texcoordIndex], m_texcoords[texcoordIndex+1]);
}
vertexIndex = (*currentMatFaces)[face]*3;
glNormal3f(m_normals[vertexIndex], m_normals[vertexIndex+1], m_normals[vertexIndex+2]);
glVertex3f(m_vertices[vertexIndex], m_vertices[vertexIndex+1], m_vertices[vertexIndex+2]);
if(hasTextureMap){
texcoordIndex = (*currentMatFaces)[face+1]*2;
glTexCoord2f(m_texcoords[texcoordIndex], m_texcoords[texcoordIndex+1]);
}
vertexIndex = (*currentMatFaces)[face+1]*3;
glNormal3f(m_normals[vertexIndex], m_normals[vertexIndex+1], m_normals[vertexIndex+2]);
glVertex3f(m_vertices[vertexIndex], m_vertices[vertexIndex+1], m_vertices[vertexIndex+2]);
if(hasTextureMap){
texcoordIndex = (*currentMatFaces)[face+2]*2;
glTexCoord2f(m_texcoords[texcoordIndex], m_texcoords[texcoordIndex+1]);
}
vertexIndex = (*currentMatFaces)[face+2]*3;
glNormal3f(m_normals[vertexIndex], m_normals[vertexIndex+1], m_normals[vertexIndex+2]);
glVertex3f(m_vertices[vertexIndex], m_vertices[vertexIndex+1], m_vertices[vertexIndex+2]);
}
glEnd();
break;
case DRAW_VERTEX_ARRAY:
glEnableClientState( GL_VERTEX_ARRAY );
glEnableClientState( GL_NORMAL_ARRAY );
if(hasTextureMap){
glTexCoordPointer( 2, GL_FLOAT, 0, &m_texcoords[0] );
glEnableClientState( GL_TEXTURE_COORD_ARRAY );
}
glVertexPointer( 3, GL_FLOAT, 0, &m_vertices[0] );
glNormalPointer(GL_FLOAT, 0, &m_normals[0] );
glDrawElements(GL_TRIANGLES, numFaces, GL_UNSIGNED_SHORT, &(materialsIter->second[0]));
glDisableClientState( GL_VERTEX_ARRAY );
glDisableClientState( GL_NORMAL_ARRAY );
if(hasTextureMap){
glDisableClientState( GL_TEXTURE_COORD_ARRAY );
}
break;
case DRAW_VBO:
break;
default:
std::cout<<"[3DS] ERROR: Invalid mesh draw mode specified"<<std::endl;
break;
}
glPopAttrib(); // GL_LIGHTING_BIT
}
}
void model3DS::draw(){
std::vector<mesh3DS>::iterator meshIter;
glPushMatrix();
glTranslatef(-m_centerX,-m_centerY,-m_centerZ);
for(meshIter = m_meshes.begin(); meshIter != m_meshes.end(); meshIter++){
meshIter->draw();
}
glPopMatrix();
}
(C) Æliens
04/09/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.