assimp/code/3DSLoader.cpp

1311 lines
37 KiB
C++

/** @file Implementation of the 3ds importer class */
#include "3DSLoader.h"
#include "MaterialSystem.h"
#include "../include/IOStream.h"
#include "../include/IOSystem.h"
#include "../include/aiMesh.h"
#include "../include/aiScene.h"
#include "../include/aiAssert.h"
#include <boost/scoped_ptr.hpp>
using namespace Assimp;
#define ASSIMP_3DS_WARN_CHUNK_OVERFLOW_MSG \
"WARNING: Size of chunk data plus size of " \
"subordinate chunks is larger than the size " \
"specified in the higher-level chunk header." \
// ------------------------------------------------------------------------------------------------
// Constructor to be privately used by Importer
Dot3DSImporter::Dot3DSImporter()
{
}
// ------------------------------------------------------------------------------------------------
// Destructor, private as well
Dot3DSImporter::~Dot3DSImporter()
{
}
// ------------------------------------------------------------------------------------------------
// Returns whether the class can handle the format of the given file.
bool Dot3DSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
{
// simple check of file extension is enough for the moment
std::string::size_type pos = pFile.find_last_of('.');
// no file extension - can't read
if( pos == std::string::npos)
return false;
std::string extension = pFile.substr( pos);
// not brilliant but working ;-)
if( extension == ".3ds" || extension == ".3DS" ||
extension == ".3Ds" || extension == ".3dS")
return true;
return false;
}
// ------------------------------------------------------------------------------------------------
// recursively delete a given node
void DeleteNodeRecursively (aiNode* p_piNode)
{
if (!p_piNode)return;
if (p_piNode->mChildren)
{
for (unsigned int i = 0 ; i < p_piNode->mNumChildren;++i)
{
DeleteNodeRecursively(p_piNode->mChildren[i]);
}
}
delete p_piNode;
return;
}
// ------------------------------------------------------------------------------------------------
// Imports the given file into the given scene structure.
void Dot3DSImporter::InternReadFile(
const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
{
boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile));
// Check whether we can read from the file
if( file.get() == NULL)
{
throw new ImportErrorException( "Failed to open file " + pFile + ".");
}
// check whether the .3ds file is large enough to contain
// at least one chunk.
size_t fileSize = file->FileSize();
if( fileSize < 16)
{
throw new ImportErrorException( ".3ds File is too small.");
}
this->mScene = new Dot3DS::Scene();
// allocate storage and copy the contents of the file to a memory buffer
this->mBuffer = new unsigned char[fileSize];
file->Read( mBuffer, 1, fileSize);
this->mCurrent = this->mBuffer;
this->mLast = this->mBuffer+fileSize;
// initialize members
this->mLastNodeIndex = -1;
this->mCurrentNode = new Dot3DS::Node();
this->mRootNode = this->mCurrentNode;
this->mRootNode->mHierarchyPos = -1;
this->mRootNode->mHierarchyIndex = -1;
this->mRootNode->mParent = NULL;
this->mMasterScale = 1.0f;
this->mBackgroundImage = "";
this->bHasBG = false;
int iRemaining = (unsigned int)fileSize;
this->ParseMainChunk(&iRemaining);
// Generate an unique set of vertices/indices for
// all meshes contained in the file
for (std::vector<Dot3DS::Mesh>::iterator
i = this->mScene->mMeshes.begin();
i != this->mScene->mMeshes.end();++i)
{
// TODO: see function body
this->CheckIndices(&(*i));
this->MakeUnique(&(*i));
// first generate normals for the mesh
this->GenNormals(&(*i));
}
// Apply scaling and offsets to all texture coordinates
this->ApplyScaleNOffset();
// Replace all occurences of the default material with a valid material.
// Generate it if no material containing DEFAULT in its name has been
// found in the file
this->ReplaceDefaultMaterial();
try
{
// Convert the scene from our internal representation to an aiScene object
this->ConvertScene(pScene);
}
catch (ImportErrorException ex)
{
// delete the scene itself
if (pScene->mMeshes)
{
for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
delete pScene->mMeshes[i];
delete[] pScene->mMeshes;
}
if (pScene->mMaterials)
{
for (unsigned int i = 0; i < pScene->mNumMaterials;++i)
delete pScene->mMaterials[i];
delete[] pScene->mMaterials;
}
// there are no animations
if (pScene->mRootNode)DeleteNodeRecursively(pScene->mRootNode);
throw ex;
}
// Generate the node graph for the scene. This is a little bit
// tricky since we'll need to split some meshes into submeshes
this->GenerateNodeGraph(pScene);
// Now apply a master scaling factor to the scene
this->ApplyMasterScale(pScene);
delete[] this->mBuffer;
delete this->mScene;
return;
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::ApplyMasterScale(aiScene* pScene)
{
// NOTE: Some invalid files have masterscale set to 0.0
if (0.0f == this->mMasterScale)
{
this->mMasterScale = 1.0f;
}
else this->mMasterScale = 1.0f / this->mMasterScale;
// construct an uniform scaling matrix and multiply with it
pScene->mRootNode->mTransformation *= aiMatrix4x4(
this->mMasterScale,0.0f, 0.0f, 0.0f,
0.0f, this->mMasterScale,0.0f, 0.0f,
0.0f, 0.0f, this->mMasterScale,0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::ReadChunk(const Dot3DSFile::Chunk** p_ppcOut)
{
ai_assert(p_ppcOut != NULL);
// read chunk
if ((unsigned int)this->mCurrent >= (unsigned int)this->mLast)
{
*p_ppcOut = NULL;
return;
}
const unsigned int iDiff = (unsigned int)this->mLast - (unsigned int)this->mCurrent;
if (iDiff < sizeof(Dot3DSFile::Chunk))
{
*p_ppcOut = NULL;
return;
}
*p_ppcOut = (const Dot3DSFile::Chunk*) this->mCurrent;
this->mCurrent += sizeof(Dot3DSFile::Chunk);
return;
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::ParseMainChunk(int* piRemaining)
{
const Dot3DSFile::Chunk* psChunk;
this->ReadChunk(&psChunk);
if (NULL == psChunk)return;
const unsigned char* pcCur = this->mCurrent;
const unsigned char* pcCurNext = pcCur + (psChunk->Size
- sizeof(Dot3DSFile::Chunk));
// get chunk type
int iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
switch (psChunk->Flag)
{
case Dot3DSFile::CHUNK_MAIN:
//case 0x444d: // bugfix
this->ParseEditorChunk(&iRemaining);
break;
};
if ((unsigned int)pcCurNext < (unsigned int)this->mCurrent)
{
// place an error message. If we crash the programmer
// will be able to find it
this->mErrorText = ASSIMP_3DS_WARN_CHUNK_OVERFLOW_MSG;
pcCurNext = this->mCurrent;
}
// Go to the starting position of the next top-level chunk
this->mCurrent = pcCurNext;
*piRemaining -= psChunk->Size;
if (0 >= *piRemaining)return;
return this->ParseMainChunk(piRemaining);
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::ParseEditorChunk(int* piRemaining)
{
const Dot3DSFile::Chunk* psChunk;
this->ReadChunk(&psChunk);
if (NULL == psChunk)return;
const unsigned char* pcCur = this->mCurrent;
const unsigned char* pcCurNext = pcCur + (psChunk->Size
- sizeof(Dot3DSFile::Chunk));
// get chunk type
int iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
switch (psChunk->Flag)
{
case Dot3DSFile::CHUNK_OBJMESH:
this->ParseObjectChunk(&iRemaining);
break;
// NOTE: In several documentations in the internet this
// chunk appears at different locations
case Dot3DSFile::CHUNK_KEYFRAMER:
this->ParseKeyframeChunk(&iRemaining);
break;
};
if ((unsigned int)pcCurNext < (unsigned int)this->mCurrent)
{
// place an error message. If we crash the programmer
// will be able to find it
this->mErrorText = ASSIMP_3DS_WARN_CHUNK_OVERFLOW_MSG;
pcCurNext = this->mCurrent;
}
// Go to the starting position of the next top-level chunk
this->mCurrent = pcCurNext;
*piRemaining -= psChunk->Size;
if (0 >= *piRemaining)return;
return this->ParseEditorChunk(piRemaining);
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::ParseObjectChunk(int* piRemaining)
{
const Dot3DSFile::Chunk* psChunk;
this->ReadChunk(&psChunk);
if (NULL == psChunk)return;
const unsigned char* pcCur = this->mCurrent;
const unsigned char* pcCurNext = pcCur + (psChunk->Size
- sizeof(Dot3DSFile::Chunk));
const unsigned char* sz = this->mCurrent;
unsigned int iCnt = 0;
// get chunk type
int iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
switch (psChunk->Flag)
{
case Dot3DSFile::CHUNK_OBJBLOCK:
this->mScene->mMeshes.push_back(Dot3DS::Mesh());
// at first we need to parse the name of the
// geometry object
while (*sz++ != '\0')
{
if (sz > pcCurNext-1)break;
++iCnt;
}
this->mScene->mMeshes.back().mName = std::string(
(const char*)this->mCurrent,iCnt);
++iCnt;
this->mCurrent += iCnt;
iRemaining -= iCnt;
this->ParseChunk(&iRemaining);
break;
case Dot3DSFile::CHUNK_MAT_MATERIAL:
this->mScene->mMaterials.push_back(Dot3DS::Material());
this->ParseMaterialChunk(&iRemaining);
break;
case Dot3DSFile::CHUNK_AMBCOLOR:
// This is the ambient base color of the scene.
// We add it to the ambient color of all materials
this->ParseColorChunk(&this->mClrAmbient,true);
if (is_qnan(this->mClrAmbient.r))
{
this->mClrAmbient.r = 0.0f;
this->mClrAmbient.g = 0.0f;
this->mClrAmbient.b = 0.0f;
}
break;
case Dot3DSFile::CHUNK_BIT_MAP:
this->mBackgroundImage = std::string((const char*)this->mCurrent);
break;
case Dot3DSFile::CHUNK_BIT_MAP_EXISTS:
bHasBG = true;
break;
case Dot3DSFile::CHUNK_MASTER_SCALE:
this->mMasterScale = *((float*)this->mCurrent);
this->mCurrent += sizeof(float);
break;
// NOTE: In several documentations in the internet this
// chunk appears at different locations
case Dot3DSFile::CHUNK_KEYFRAMER:
this->ParseKeyframeChunk(&iRemaining);
break;
};
if ((unsigned int)pcCurNext < (unsigned int)this->mCurrent)
{
// place an error message. If we crash the programmer
// will be able to find it
this->mErrorText = ASSIMP_3DS_WARN_CHUNK_OVERFLOW_MSG;
pcCurNext = this->mCurrent;
}
// Go to the starting position of the next top-level chunk
this->mCurrent = pcCurNext;
*piRemaining -= psChunk->Size;
if (0 >= *piRemaining)return;
return this->ParseObjectChunk(piRemaining);
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::SkipChunk()
{
const Dot3DSFile::Chunk* psChunk;
this->ReadChunk(&psChunk);
if (NULL == psChunk)return;
this->mCurrent += psChunk->Size - sizeof(Dot3DSFile::Chunk);
return;
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::ParseChunk(int* piRemaining)
{
const Dot3DSFile::Chunk* psChunk;
this->ReadChunk(&psChunk);
if (NULL == psChunk)return;
const unsigned char* pcCur = this->mCurrent;
const unsigned char* pcCurNext = pcCur + (psChunk->Size
- sizeof(Dot3DSFile::Chunk));
// get chunk type
int iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
switch (psChunk->Flag)
{
case Dot3DSFile::CHUNK_TRIMESH:
// this starts a new mesh
this->ParseMeshChunk(&iRemaining);
break;
};
if ((unsigned int)pcCurNext < (unsigned int)this->mCurrent)
{
// place an error message. If we crash the programmer
// will be able to find it
this->mErrorText = ASSIMP_3DS_WARN_CHUNK_OVERFLOW_MSG;
pcCurNext = this->mCurrent;
}
// Go to the starting position of the next top-level chunk
this->mCurrent = pcCurNext;
*piRemaining -= psChunk->Size;
if (0 >= *piRemaining)return;
return this->ParseChunk(piRemaining);
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::ParseKeyframeChunk(int* piRemaining)
{
const Dot3DSFile::Chunk* psChunk;
this->ReadChunk(&psChunk);
if (NULL == psChunk)return;
const unsigned char* pcCur = this->mCurrent;
const unsigned char* pcCurNext = pcCur + (psChunk->Size
- sizeof(Dot3DSFile::Chunk));
// get chunk type
int iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
switch (psChunk->Flag)
{
case Dot3DSFile::CHUNK_TRACKINFO:
// this starts a new mesh
this->ParseHierarchyChunk(&iRemaining);
break;
};
if ((unsigned int)pcCurNext < (unsigned int)this->mCurrent)
{
// place an error message. If we crash the programmer
// will be able to find it
this->mErrorText = ASSIMP_3DS_WARN_CHUNK_OVERFLOW_MSG;
pcCurNext = this->mCurrent;
}
// Go to the starting position of the next top-level chunk
this->mCurrent = pcCurNext;
*piRemaining -= psChunk->Size;
if (0 >= *piRemaining)return;
return this->ParseKeyframeChunk(piRemaining);
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::InverseNodeSearch(Dot3DS::Node* pcNode,Dot3DS::Node* pcCurrent)
{
if (NULL == pcCurrent)
{
this->mRootNode->push_back(pcNode);
return;
}
if (pcCurrent->mHierarchyPos == pcNode->mHierarchyPos)
{
if(NULL != pcCurrent->mParent)
pcCurrent->mParent->push_back(pcNode);
else pcCurrent->push_back(pcNode);
return;
}
return this->InverseNodeSearch(pcNode,pcCurrent->mParent);
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::ParseHierarchyChunk(int* piRemaining)
{
const Dot3DSFile::Chunk* psChunk;
this->ReadChunk(&psChunk);
if (NULL == psChunk)return;
const unsigned char* pcCur = this->mCurrent;
const unsigned char* pcCurNext = pcCur + (psChunk->Size
- sizeof(Dot3DSFile::Chunk));
// get chunk type
const unsigned char* sz = (unsigned char*)this->mCurrent;
unsigned int iCnt = 0;
uint16_t iHierarchy;
//uint16_t iTemp;
Dot3DS::Node* pcNode;
switch (psChunk->Flag)
{
case Dot3DSFile::CHUNK_TRACKOBJNAME:
// get object name
while (*sz++ != '\0')
{
if (sz > pcCurNext-1)break;
++iCnt;
}
pcNode = new Dot3DS::Node();
pcNode->mName = std::string((const char*)this->mCurrent,iCnt);
iCnt++;
// there are two unknown values which we can safely ignore
this->mCurrent += iCnt + sizeof(uint16_t)*2;
iHierarchy = *((uint16_t*)this->mCurrent);
iHierarchy++;
pcNode->mHierarchyPos = iHierarchy;
pcNode->mHierarchyIndex = this->mLastNodeIndex;
if (iHierarchy > this->mLastNodeIndex)
{
// place it at the current position in the hierarchy
this->mCurrentNode->push_back(pcNode);
}
else
{
// need to go back to the specified position in the hierarchy.
this->InverseNodeSearch(pcNode,this->mCurrentNode);
}
this->mLastNodeIndex++;
this->mCurrentNode = pcNode;
break;
#if 0
case Dot3DSFile::CHUNK_TRACKPIVOT:
this->mCurrentNode->vPivot = *((aiVector3D*)this->mCurrent);
this->mCurrent += sizeof(aiVector3D);
break;
case Dot3DSFile::CHUNK_TRACKPOS:
/*
+2 short flags;
+8 short unknown[4];
+2 short keys;
+2 short unknown;
struct {
+2 short framenum;
+4 long unknown;
float pos_x, pos_y, pos_z;
} pos[keys];
*/
this->mCurrent += 10;
iTemp = *((uint16_t*)mCurrent);
this->mCurrent += sizeof(uint16_t) * 2;
if (0 != iTemp)
{
for (unsigned int i = 0; i < (unsigned int)iTemp;++i)
{
uint16_t sNum = *((uint16_t*)mCurrent);
this->mCurrent += sizeof(uint16_t);
if (0 == sNum)
{
this->mCurrent += sizeof(uint32_t);
this->mCurrentNode->vPosition = *((aiVector3D*)this->mCurrent);
this->mCurrent += sizeof(aiVector3D);
}
else this->mCurrent += sizeof(uint32_t) + sizeof(aiVector3D);
}
}
break;
case Dot3DSFile::CHUNK_TRACKROTATE:
/*
+2 short flags;
+8 short unknown[4];
+2 short keys;
+2 short unknown;
struct {
+2 short framenum;
+4 long unknown;
float rad , pos_x, pos_y, pos_z;
} pos[keys];
*/
this->mCurrent += 10;
iTemp = *((uint16_t*)mCurrent);
this->mCurrent += sizeof(uint16_t) * 2;
if (0 != iTemp)
{
bool neg = false;
unsigned int iNum0 = 0;
for (unsigned int i = 0; i < (unsigned int)iTemp;++i)
{
uint16_t sNum = *((uint16_t*)mCurrent);
this->mCurrent += sizeof(uint16_t);
if (0 == sNum)
{
this->mCurrent += sizeof(uint32_t);
float fRadians = *((float*)this->mCurrent);
this->mCurrent += sizeof(float);
aiVector3D vAxis = *((aiVector3D*)this->mCurrent);
this->mCurrent += sizeof(aiVector3D);
// some idiotic files have rotations with fRadians = 0 ...
if (0.0f != fRadians)
{
// if the radians go beyond PI then the rotations
// thereafter must be inversed
#if 0
if (neg)fRadians *= -1.0f;
if ((fRadians >= 3.1415926f || fRadians <= -3.1415926f))
{
neg = !neg;
}
#endif
// get the rotation matrix around the axis
const float fSin = sinf(-fRadians);
const float fCos = cosf(-fRadians);
const float fOneMinusCos = 1.0f - fCos;
std::swap(vAxis.z,vAxis.y);
vAxis.Normalize();
aiMatrix4x4 mRot = aiMatrix4x4(
(vAxis.x * vAxis.x) * fOneMinusCos + fCos,
(vAxis.x * vAxis.y) * fOneMinusCos - (vAxis.z * fSin),
(vAxis.x * vAxis.z) * fOneMinusCos + (vAxis.y * fSin),
0.0f,
(vAxis.y * vAxis.x) * fOneMinusCos + (vAxis.z * fSin),
(vAxis.y * vAxis.y) * fOneMinusCos + fCos,
(vAxis.y * vAxis.z) * fOneMinusCos - (vAxis.x * fSin),
0.0f,
(vAxis.z * vAxis.x) * fOneMinusCos - (vAxis.y * fSin),
(vAxis.z * vAxis.y) * fOneMinusCos + (vAxis.x * fSin),
(vAxis.z * vAxis.z) * fOneMinusCos + fCos,
0.0f,0.0f,0.0f,0.0f,1.0f);
//mRot.Transpose();
// build a chain of concatenated rotation matrix'
// if there are multiple track chunks for the same frame
if (0 != iNum0)
{
this->mCurrentNode->mRotation = this->mCurrentNode->mRotation * mRot;
}
else
{
// for the first time simply set the rotation matrix
this->mCurrentNode->mRotation = mRot;
}
iNum0++;
}
}
else this->mCurrent += sizeof(uint32_t) + sizeof(aiVector3D) + sizeof(float);
}
}
break;
case Dot3DSFile::CHUNK_TRACKSCALE:
/*
+2 short flags;
+8 short unknown[4];
+2 short keys;
+2 short unknown;
struct {
+2 short framenum;
+4 long unknown;
float pos_x, pos_y, pos_z;
} pos[keys];
*/
this->mCurrent += 10;
iTemp = *((uint16_t*)mCurrent);
this->mCurrent += sizeof(uint16_t) * 2;
if (0 != iTemp)
{
for (unsigned int i = 0; i < (unsigned int)iTemp;++i)
{
uint16_t sNum = *((uint16_t*)mCurrent);
this->mCurrent += sizeof(uint16_t);
if (0 == sNum)
{
this->mCurrent += sizeof(uint32_t);
aiVector3D vMe = *((aiVector3D*)this->mCurrent);
// ignore zero scalings
if (0.0f != vMe.x && 0.0f != vMe.y && 0.0f != vMe.z)
{
this->mCurrentNode->vScaling.x *= vMe.x;
this->mCurrentNode->vScaling.y *= vMe.y;
this->mCurrentNode->vScaling.z *= vMe.z;
}
this->mCurrent += sizeof(aiVector3D);
}
else this->mCurrent += sizeof(uint32_t) + sizeof(aiVector3D);
}
}
break;
#endif // 0
};
if ((unsigned int)pcCurNext < (unsigned int)this->mCurrent)
{
// place an error message. If we crash the programmer
// will be able to find it
this->mErrorText = ASSIMP_3DS_WARN_CHUNK_OVERFLOW_MSG;
pcCurNext = this->mCurrent;
}
// Go to the starting position of the next top-level chunk
this->mCurrent = pcCurNext;
*piRemaining -= psChunk->Size;
if (0 >= *piRemaining)return;
return this->ParseHierarchyChunk(piRemaining);
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::ParseFaceChunk(int* piRemaining)
{
const Dot3DSFile::Chunk* psChunk;
Dot3DS::Mesh& mMesh = this->mScene->mMeshes.back();
this->ReadChunk(&psChunk);
if (NULL == psChunk)return;
const unsigned char* pcCur = this->mCurrent;
const unsigned char* pcCurNext = pcCur + (psChunk->Size
- sizeof(Dot3DSFile::Chunk));
// get chunk type
const unsigned char* sz = this->mCurrent;
uint32_t iCnt = 0,iTemp;
switch (psChunk->Flag)
{
case Dot3DSFile::CHUNK_SMOOLIST:
// one int32 for each face
for (std::vector<Dot3DS::Face>::iterator
i = mMesh.mFaces.begin();
i != mMesh.mFaces.end();++i)
{
// nth bit is set for nth smoothing group
(*i).iSmoothGroup = *((uint32_t*)this->mCurrent);
#if 0
for (unsigned int x = 0, a = 1; x < 32;++x,a <<= 1)
{
if ((*i).iSmoothGroup & a)
mMesh.bSmoothGroupRequired[x] = true;
}
#endif
this->mCurrent += sizeof(uint32_t);
}
break;
case Dot3DSFile::CHUNK_FACEMAT:
// at fist an asciiz with the material name
while (*sz++ != '\0')
{
if (sz > pcCurNext-1)break;
}
// find the index of the material
unsigned int iIndex = 0xFFFFFFFF;
iCnt = 0;
for (std::vector<Dot3DS::Material>::const_iterator
i = this->mScene->mMaterials.begin();
i != this->mScene->mMaterials.end();++i,++iCnt)
{
// compare case-independent to be sure it works
if (0 == ASSIMP_stricmp((const char*)this->mCurrent,
(const char*)((*i).mName.c_str())))
{
iIndex = iCnt;
break;
}
}
if (iIndex == 0xFFFFFFFF)
{
// this material is not known. Ignore this. We will later
// assign the default material to all faces using *this*
// material. Use 0xcdcdcdcd as special value to indicate
// this.
iIndex = 0xcdcdcdcd;
}
this->mCurrent = sz;
iCnt = (int)(*((uint16_t*)this->mCurrent));
this->mCurrent += sizeof(uint16_t);
for (unsigned int i = 0; i < iCnt;++i)
{
iTemp = (uint16_t)*((uint16_t*)this->mCurrent);
// check range
if (iTemp >= mMesh.mFaceMaterials.size())
{
mMesh.mFaceMaterials[mMesh.mFaceMaterials.size()-1] = iIndex;
}
else
{
mMesh.mFaceMaterials[iTemp] = iIndex;
}
this->mCurrent += sizeof(uint16_t);
}
break;
};
if ((unsigned int)pcCurNext < (unsigned int)this->mCurrent)
{
// place an error message. If we crash the programmer
// will be able to find it
this->mErrorText = ASSIMP_3DS_WARN_CHUNK_OVERFLOW_MSG;
pcCurNext = this->mCurrent;
}
// Go to the starting position of the next chunk on this level
this->mCurrent = pcCurNext;
*piRemaining -= psChunk->Size;
if (0 >= *piRemaining)return;
return ParseFaceChunk(piRemaining);
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::ParseMeshChunk(int* piRemaining)
{
const Dot3DSFile::Chunk* psChunk;
Dot3DS::Mesh& mMesh = this->mScene->mMeshes.back();
this->ReadChunk(&psChunk);
if (NULL == psChunk)return;
const unsigned char* pcCur = this->mCurrent;
const unsigned char* pcCurNext = pcCur + (psChunk->Size
- sizeof(Dot3DSFile::Chunk));
// get chunk type
const unsigned char* sz = this->mCurrent;
unsigned int iCnt = 0;
int iRemaining;
uint16_t iNum = 0;
float* pf;
switch (psChunk->Flag)
{
case Dot3DSFile::CHUNK_VERTLIST:
iNum = *((short*)this->mCurrent);
this->mCurrent += sizeof(short);
while (iNum-- > 0)
{
mMesh.mPositions.push_back(*((aiVector3D*)this->mCurrent));
mMesh.mPositions.back().z *= -1.0f;
this->mCurrent += sizeof(aiVector3D);
}
break;
case Dot3DSFile::CHUNK_TRMATRIX:
{
// http://www.gamedev.net/community/forums/topic.asp?topic_id=263063
// http://www.gamedev.net/community/forums/topic.asp?topic_id=392310
pf = (float*)this->mCurrent;
this->mCurrent += 12 * sizeof(float);
mMesh.mMat.a1 = pf[0];
mMesh.mMat.a2 = pf[1];
mMesh.mMat.a3 = pf[2];
mMesh.mMat.b1 = pf[3];
mMesh.mMat.b2 = pf[4];
mMesh.mMat.b3 = pf[5];
mMesh.mMat.c1 = pf[6];
mMesh.mMat.c2 = pf[7];
mMesh.mMat.c3 = pf[8];
mMesh.mMat.d1 = pf[9];
mMesh.mMat.d2 = pf[10];
mMesh.mMat.d3 = pf[11];
std::swap((float&)mMesh.mMat.d2, (float&)mMesh.mMat.d3);
std::swap((float&)mMesh.mMat.a2, (float&)mMesh.mMat.a3);
std::swap((float&)mMesh.mMat.b1, (float&)mMesh.mMat.c1);
std::swap((float&)mMesh.mMat.c2, (float&)mMesh.mMat.b3);
std::swap((float&)mMesh.mMat.b2, (float&)mMesh.mMat.c3);
mMesh.mMat.Transpose();
//aiMatrix4x4 mInv = mMesh.mMat;
//mInv.Inverse();
//// invert the matrix and transform all vertices with it
//// (the origin of all vertices is 0|0|0 now)
//for (register unsigned int i = 0; i < mMesh.mPositions.size();++i)
// {
// aiVector3D a,c;
// a = mMesh.mPositions[i];
// c[0]= mInv[0][0]*a[0] + mInv[1][0]*a[1] + mInv[2][0]*a[2] + mInv[3][0];
// c[1]= mInv[0][1]*a[0] + mInv[1][1]*a[1] + mInv[2][1]*a[2] + mInv[3][1];
// c[2]= mInv[0][2]*a[0] + mInv[1][2]*a[1] + mInv[2][2]*a[2] + mInv[3][2];
// mMesh.mPositions[i] = c;
// }
// now check whether the matrix has got a negative determinant
// If yes, we need to flip all vertices x axis ....
// From lib3ds, mesh.c
if (mMesh.mMat.Determinant() < 0.0f)
{
aiMatrix4x4 mInv = mMesh.mMat;
mInv.Inverse();
aiMatrix4x4 mMe = mMesh.mMat;
mMe.a1 *= -1.0f;
mMe.a2 *= -1.0f;
mMe.a3 *= -1.0f;
mMe.a4 *= -1.0f;
mInv = mMe * mInv;
for (register unsigned int i = 0; i < mMesh.mPositions.size();++i)
{
aiVector3D a,c;
a = mMesh.mPositions[i];
c[0]= mInv[0][0]*a[0] + mInv[1][0]*a[1] + mInv[2][0]*a[2] + mInv[3][0];
c[1]= mInv[0][1]*a[0] + mInv[1][1]*a[1] + mInv[2][1]*a[2] + mInv[3][1];
c[2]= mInv[0][2]*a[0] + mInv[1][2]*a[1] + mInv[2][2]*a[2] + mInv[3][2];
mMesh.mPositions[i] = c;
}
}
}
break;
case Dot3DSFile::CHUNK_MAPLIST:
iNum = *((uint16_t*)this->mCurrent);
this->mCurrent += sizeof(uint16_t);
while (iNum-- > 0)
{
mMesh.mTexCoords.push_back(*((aiVector2D*)this->mCurrent));
this->mCurrent += sizeof(aiVector2D);
}
break;
#if (defined _DEBUG)
case Dot3DSFile::CHUNK_TXTINFO:
// for debugging purposes. Read two bytes to determine the mapping type
iNum = *((uint16_t*)this->mCurrent);
this->mCurrent += sizeof(uint16_t);
break;
#endif
case Dot3DSFile::CHUNK_FACELIST:
iNum = *((uint16_t*)this->mCurrent);
this->mCurrent += sizeof(uint16_t);
while (iNum-- > 0)
{
Dot3DS::Face sFace;
sFace.i1 = *((uint16_t*)this->mCurrent);
this->mCurrent += sizeof(uint16_t);
sFace.i2 = *((uint16_t*)this->mCurrent);
this->mCurrent += sizeof(uint16_t);
sFace.i3 = *((uint16_t*)this->mCurrent);
this->mCurrent += 2*sizeof(uint16_t);
mMesh.mFaces.push_back(sFace);
//if (sFace.i1 < sFace.i2)sFace.bDirection = false;
}
// resize the material array (0xcdcdcdcd marks the
// default material; so if a face is not referenced
// by a material $$DEFAULT will be assigned to it)
mMesh.mFaceMaterials.resize(mMesh.mFaces.size(),0xcdcdcdcd);
iRemaining = (int)pcCurNext - (int)this->mCurrent;
if (iRemaining > 0)this->ParseFaceChunk(&iRemaining);
break;
};
if ((unsigned int)pcCurNext < (unsigned int)this->mCurrent)
{
// place an error message. If we crash the programmer
// will be able to find it
this->mErrorText = ASSIMP_3DS_WARN_CHUNK_OVERFLOW_MSG;
pcCurNext = this->mCurrent;
}
// Go to the starting position of the next chunk on this level
this->mCurrent = pcCurNext;
*piRemaining -= psChunk->Size;
if (0 >= *piRemaining)return;
return ParseMeshChunk(piRemaining);
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::ParseMaterialChunk(int* piRemaining)
{
const Dot3DSFile::Chunk* psChunk;
this->ReadChunk(&psChunk);
if (NULL == psChunk)return;
const unsigned char* pcCur = this->mCurrent;
const unsigned char* pcCurNext = pcCur + (psChunk->Size
- sizeof(Dot3DSFile::Chunk));
// get chunk type
const unsigned char* sz = this->mCurrent;
unsigned int iCnt = 0;
int iRemaining;
aiColor3D* pc;
float* pcf;
switch (psChunk->Flag)
{
case Dot3DSFile::CHUNK_MAT_MATNAME:
// string in file is zero-terminated,
// this should be no problem. However, validate whether
// it overlaps the end of the chunk, if yes we should
// truncate it.
while (*sz++ != '\0')
{
if (sz > pcCurNext-1)break;
++iCnt;
}
this->mScene->mMaterials.back().mName = std::string(
(const char*)this->mCurrent,iCnt);
break;
case Dot3DSFile::CHUNK_MAT_DIFFUSE:
pc = &this->mScene->mMaterials.back().mDiffuse;
this->ParseColorChunk(pc);
if (is_qnan(pc->r))
{
// color chunk is invalid. Simply ignore it
pc->r = pc->g = pc->b = 1.0f;
}
break;
case Dot3DSFile::CHUNK_MAT_SPECULAR:
pc = &this->mScene->mMaterials.back().mSpecular;
this->ParseColorChunk(pc);
if (is_qnan(pc->r))
{
// color chunk is invalid. Simply ignore it
pc->r = pc->g = pc->b = 1.0f;
}
break;
case Dot3DSFile::CHUNK_MAT_AMBIENT:
pc = &this->mScene->mMaterials.back().mAmbient;
this->ParseColorChunk(pc);
if (is_qnan(pc->r))
{
// color chunk is invalid. Simply ignore it
pc->r = pc->g = pc->b = 1.0f;
}
break;
case Dot3DSFile::CHUNK_MAT_SELF_ILLUM:
pc = &this->mScene->mMaterials.back().mEmissive;
this->ParseColorChunk(pc);
if (is_qnan(pc->r))
{
// color chunk is invalid. Simply ignore it
// EMISSSIVE TO 0|0|0
pc->r = pc->g = pc->b = 0.0f;
}
break;
case Dot3DSFile::CHUNK_MAT_TRANSPARENCY:
pcf = &this->mScene->mMaterials.back().mTransparency;
*pcf = this->ParsePercentageChunk();
// NOTE: transparency, not opacity
*pcf = 1.0f - *pcf;
if (is_qnan(*pcf))
*pcf = 0.0f;
break;
case Dot3DSFile::CHUNK_MAT_SHADING:
this->mScene->mMaterials.back().mShading =
(Dot3DS::Dot3DSFile::shadetype3ds)*((uint16_t*)this->mCurrent);
this->mCurrent += sizeof(uint16_t);
break;
case Dot3DSFile::CHUNK_MAT_SHININESS:
pcf = &this->mScene->mMaterials.back().mSpecularExponent;
*pcf = this->ParsePercentageChunk();
if (is_qnan(*pcf))
*pcf = 0.0f;
else *pcf *= (float)0xFFFF;
break;
case Dot3DSFile::CHUNK_MAT_SELF_ILPCT:
pcf = &this->mScene->mMaterials.back().sTexEmissive.mTextureBlend;
*pcf = this->ParsePercentageChunk();
if (is_qnan(*pcf))
*pcf = 1.0f;
break;
// parse texture chunks
case Dot3DSFile::CHUNK_MAT_TEXTURE:
iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
this->ParseTextureChunk(&iRemaining,&this->mScene->mMaterials.back().sTexDiffuse);
break;
case Dot3DSFile::CHUNK_MAT_BUMPMAP:
iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
this->ParseTextureChunk(&iRemaining,&this->mScene->mMaterials.back().sTexBump);
break;
case Dot3DSFile::CHUNK_MAT_OPACMAP:
iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
this->ParseTextureChunk(&iRemaining,&this->mScene->mMaterials.back().sTexOpacity);
break;
case Dot3DSFile::CHUNK_MAT_MAT_SHINMAP:
iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
this->ParseTextureChunk(&iRemaining,&this->mScene->mMaterials.back().sTexShininess);
break;
case Dot3DSFile::CHUNK_MAT_SPECMAP:
iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
this->ParseTextureChunk(&iRemaining,&this->mScene->mMaterials.back().sTexSpecular);
break;
case Dot3DSFile::CHUNK_MAT_SELFIMAP:
iRemaining = (psChunk->Size - sizeof(Dot3DSFile::Chunk));
this->ParseTextureChunk(&iRemaining,&this->mScene->mMaterials.back().sTexEmissive);
break;
};
if ((unsigned int)pcCurNext < (unsigned int)this->mCurrent)
{
// place an error message. If we crash the programmer
// will be able to find it
this->mErrorText = ASSIMP_3DS_WARN_CHUNK_OVERFLOW_MSG;
pcCurNext = this->mCurrent;
}
// Go to the starting position of the next chunk on this level
this->mCurrent = pcCurNext;
*piRemaining -= psChunk->Size;
if (0 >= *piRemaining)return;
return ParseMaterialChunk(piRemaining);
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::ParseTextureChunk(int* piRemaining,Dot3DS::Texture* pcOut)
{
const Dot3DSFile::Chunk* psChunk;
this->ReadChunk(&psChunk);
if (NULL == psChunk)return;
const unsigned char* pcCur = this->mCurrent;
const unsigned char* pcCurNext = pcCur + (psChunk->Size
- sizeof(Dot3DSFile::Chunk));
// get chunk type
const unsigned char* sz = this->mCurrent;
unsigned int iCnt = 0;
switch (psChunk->Flag)
{
case Dot3DSFile::CHUNK_MAPFILE:
// string in file is zero-terminated,
// this should be no problem. However, validate whether
// it overlaps the end of the chunk, if yes we should
// truncate it.
while (*sz++ != '\0')
{
if (sz > pcCurNext-1)break;
++iCnt;
}
pcOut->mMapName = std::string((const char*)this->mCurrent,iCnt);
break;
// manually parse the blend factor
case Dot3DSFile::CHUNK_PERCENTF:
pcOut->mTextureBlend = *((float*)this->mCurrent);
break;
// manually parse the blend factor
case Dot3DSFile::CHUNK_PERCENTW:
pcOut->mTextureBlend = (float)(*((short*)this->mCurrent)) / (float)0xFFFF;
break;
case Dot3DSFile::CHUNK_MAT_MAP_USCALE:
pcOut->mScaleU = *((float*)this->mCurrent);
break;
case Dot3DSFile::CHUNK_MAT_MAP_VSCALE:
pcOut->mScaleV = *((float*)this->mCurrent);
break;
case Dot3DSFile::CHUNK_MAT_MAP_UOFFSET:
pcOut->mOffsetU = *((float*)this->mCurrent);
break;
case Dot3DSFile::CHUNK_MAT_MAP_VOFFSET:
pcOut->mOffsetV = *((float*)this->mCurrent);
break;
case Dot3DSFile::CHUNK_MAT_MAP_ANG:
pcOut->mRotation = *((float*)this->mCurrent);
break;
};
// Go to the starting position of the next chunk on this level
this->mCurrent = pcCurNext;
*piRemaining -= psChunk->Size;
if (0 >= *piRemaining)return;
return ParseTextureChunk(piRemaining,pcOut);
}
// ------------------------------------------------------------------------------------------------
float Dot3DSImporter::ParsePercentageChunk()
{
const Dot3DSFile::Chunk* psChunk;
this->ReadChunk(&psChunk);
if (NULL == psChunk)return std::numeric_limits<float>::quiet_NaN();
if (Dot3DSFile::CHUNK_PERCENTF == psChunk->Flag)
{
if (sizeof(float) > psChunk->Size)
return std::numeric_limits<float>::quiet_NaN();
return *((float*)this->mCurrent);
}
else if (Dot3DSFile::CHUNK_PERCENTW == psChunk->Flag)
{
if (2 > psChunk->Size)
return std::numeric_limits<float>::quiet_NaN();
return (float)(*((short*)this->mCurrent)) / (float)0xFFFF;
}
this->mCurrent += psChunk->Size - sizeof(Dot3DSFile::Chunk);
return std::numeric_limits<float>::quiet_NaN();
}
// ------------------------------------------------------------------------------------------------
void Dot3DSImporter::ParseColorChunk(aiColor3D* p_pcOut,
bool p_bAcceptPercent)
{
ai_assert(p_pcOut != NULL);
// error return value
static const aiColor3D clrError = aiColor3D(std::numeric_limits<float>::quiet_NaN(),
std::numeric_limits<float>::quiet_NaN(),
std::numeric_limits<float>::quiet_NaN());
const Dot3DSFile::Chunk* psChunk;
this->ReadChunk(&psChunk);
if (NULL == psChunk)
{
*p_pcOut = clrError;
return;
}
const unsigned char* pcCur = this->mCurrent;
this->mCurrent += psChunk->Size - sizeof(Dot3DSFile::Chunk);
bool bGamma = false;
switch(psChunk->Flag)
{
case Dot3DSFile::CHUNK_LINRGBF:
bGamma = true;
case Dot3DSFile::CHUNK_RGBF:
if (sizeof(float) * 3 > psChunk->Size - sizeof(Dot3DSFile::Chunk))
{
*p_pcOut = clrError;
return;
}
p_pcOut->r = ((float*)pcCur)[0];
p_pcOut->g = ((float*)pcCur)[1];
p_pcOut->b = ((float*)pcCur)[2];
break;
case Dot3DSFile::CHUNK_LINRGBB:
bGamma = true;
case Dot3DSFile::CHUNK_RGBB:
if (sizeof(char) * 3 > psChunk->Size - sizeof(Dot3DSFile::Chunk))
{
*p_pcOut = clrError;
return;
}
p_pcOut->r = (float)pcCur[0] / 255.0f;
p_pcOut->g = (float)pcCur[1] / 255.0f;
p_pcOut->b = (float)pcCur[2] / 255.0f;
break;
// percentage chunks: accepted to be compatible with various
// .3ds files with very curious content
case Dot3DSFile::CHUNK_PERCENTF:
if (p_bAcceptPercent && 4 <= psChunk->Size - sizeof(Dot3DSFile::Chunk))
{
p_pcOut->r = *((float*)pcCur);
p_pcOut->g = *((float*)pcCur);
p_pcOut->b = *((float*)pcCur);
break;
}
*p_pcOut = clrError;
return;
case Dot3DSFile::CHUNK_PERCENTW:
if (p_bAcceptPercent && 1 <= psChunk->Size - sizeof(Dot3DSFile::Chunk))
{
p_pcOut->r = (float)pcCur[0] / 255.0f;
p_pcOut->g = (float)pcCur[0] / 255.0f;
p_pcOut->b = (float)pcCur[0] / 255.0f;
break;
}
*p_pcOut = clrError;
return;
default:
// skip unknown chunks, hope this won't cause any problems.
return this->ParseColorChunk(p_pcOut,p_bAcceptPercent);
};
// assume input gamma = 1.0, output gamma = 2.2
// Not sure whether this is correct, too tired to
// think about it ;-)
if (bGamma)
{
p_pcOut->r = powf(p_pcOut->r, 1.0f / 2.2f);
p_pcOut->g = powf(p_pcOut->g, 1.0f / 2.2f);
p_pcOut->b = powf(p_pcOut->b, 1.0f / 2.2f);
}
return;
}