/** @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 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 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::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::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::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::quiet_NaN(); if (Dot3DSFile::CHUNK_PERCENTF == psChunk->Flag) { if (sizeof(float) > psChunk->Size) return std::numeric_limits::quiet_NaN(); return *((float*)this->mCurrent); } else if (Dot3DSFile::CHUNK_PERCENTW == psChunk->Flag) { if (2 > psChunk->Size) return std::numeric_limits::quiet_NaN(); return (float)(*((short*)this->mCurrent)) / (float)0xFFFF; } this->mCurrent += psChunk->Size - sizeof(Dot3DSFile::Chunk); return std::numeric_limits::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::quiet_NaN(), std::numeric_limits::quiet_NaN(), std::numeric_limits::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; }