From 2f36cc5f5fe1c3e35465d4f240dccb3b18ee2fb2 Mon Sep 17 00:00:00 2001 From: aramis_acg Date: Wed, 6 Aug 2008 23:01:38 +0000 Subject: [PATCH] Added MD5 (md5mesh works, md5anim has not yet been tested) and STL. new unittests, although not yet complete (material system, normals). Bugfixes (GFn and MDL7). Added HMP5 support. Rewrote MD2 and MD3 to be more stable. git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@77 67173fc5-114c-0410-ac8e-9d2fd5bffc1f --- code/ASELoader.cpp | 122 ++-- code/ASEParser.cpp | 3 - code/CalcTangentsProcess.cpp | 9 +- code/GenFaceNormalsProcess.cpp | 14 +- code/GenVertexNormalsProcess.cpp | 16 +- code/GenVertexNormalsProcess.h | 5 +- code/HMPLoader.cpp | 248 +++++--- code/HMPLoader.h | 58 +- code/Hash.h | 67 +++ code/Importer.cpp | 16 +- code/ImproveCacheLocality.cpp | 17 +- code/MD2Loader.cpp | 362 +++++------ code/MD3Loader.cpp | 458 +++++++------- code/MD5Loader.cpp | 561 ++++++++++++++++++ code/MD5Loader.h | 68 ++- code/MD5Parser.cpp | 545 +++++++++++++++++ code/MD5Parser.h | 419 +++++++++++++ code/MDLLoader.cpp | 235 ++++---- code/MDLLoader.h | 13 +- code/MDLMaterialLoader.cpp | 48 +- code/MaterialSystem.cpp | 181 +++--- code/MaterialSystem.h | 34 +- code/RemoveRedundantMaterials.cpp | 6 +- code/STLLoader.cpp | 398 +++++++++++++ code/STLLoader.h | 119 ++++ code/SplitLargeMeshes.cpp | 4 +- code/ValidateDataStructure.cpp | 18 +- code/aiAssert.cpp | 2 +- code/fast_atof.h | 32 +- include/aiDefines.h | 6 + include/aiMaterial.h | 2 +- include/aiQuaternion.h | 18 + test/STL/triangle.stl | 9 + ...StepWin32.bat => ExecuteUnitTestWin32.bat} | 0 ...StepWin64.bat => ExecuteUnitTestWin64.bat} | 0 test/unit/utGenNormals.cpp | 34 ++ test/unit/utGenNormals.h | 37 ++ test/unit/utMaterialSystem.cpp | 82 +++ test/unit/utMaterialSystem.h | 45 ++ ...thNormals.cpp => utPretransformVertices.h} | 0 test/unit/utRemoveRedundantMaterials.cpp | 101 ++++ test/unit/utRemoveRedundantMaterials.h | 39 ++ tools/assimp_view/Display.cpp | 2 +- workspaces/vc8/UnitTest.vcproj | 24 +- workspaces/vc8/assimp.vcproj | 80 ++- 45 files changed, 3625 insertions(+), 932 deletions(-) create mode 100644 code/Hash.h create mode 100644 code/MD5Loader.cpp create mode 100644 code/MD5Parser.cpp create mode 100644 code/MD5Parser.h create mode 100644 code/STLLoader.cpp create mode 100644 code/STLLoader.h create mode 100644 test/STL/triangle.stl rename test/unit/{ExecuteStepWin32.bat => ExecuteUnitTestWin32.bat} (100%) rename test/unit/{ExecuteStepWin64.bat => ExecuteUnitTestWin64.bat} (100%) create mode 100644 test/unit/utGenNormals.h create mode 100644 test/unit/utMaterialSystem.h rename test/unit/{utGenSmoothNormals.cpp => utPretransformVertices.h} (100%) create mode 100644 test/unit/utRemoveRedundantMaterials.cpp create mode 100644 test/unit/utRemoveRedundantMaterials.h diff --git a/code/ASELoader.cpp b/code/ASELoader.cpp index adc9a9cf1..1a7f65cf0 100644 --- a/code/ASELoader.cpp +++ b/code/ASELoader.cpp @@ -94,7 +94,7 @@ bool ASEImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const // NOTE: Sometimes the extension .ASK is also used // however, often it only contains static animation skeletons - // without the real animations. + // without real animations. if (extension[3] != 'e' && extension[3] != 'E' && extension[3] != 'k' && extension[3] != 'K')return false; @@ -114,14 +114,9 @@ void ASEImporter::InternReadFile( } size_t fileSize = file->FileSize(); - std::string::size_type pos = pFile.find_last_of('.'); std::string extension = pFile.substr( pos); - if(extension[3] == 'k' || extension[3] == 'K') - { - this->mIsAsk = true; - } - else this->mIsAsk = false; + this->mIsAsk = (extension[3] == 'k' || extension[3] == 'K'); // allocate storage and copy the contents of the file to a memory buffer // (terminate it with zero) @@ -132,61 +127,69 @@ void ASEImporter::InternReadFile( // construct an ASE parser and parse the file this->mParser = new ASE::Parser((const char*)this->mBuffer); - this->mParser->Parse(); - - // if absolutely no material has been loaded from the file - // we need to generate a default material - this->GenerateDefaultMaterial(); - - // process all meshes - std::vector avOutMeshes; - avOutMeshes.reserve(this->mParser->m_vMeshes.size()*2); - for (std::vector::iterator - i = this->mParser->m_vMeshes.begin(); - i != this->mParser->m_vMeshes.end();++i) + try { - if ((*i).bSkip)continue; + this->mParser->Parse(); - // transform all vertices into worldspace - // world2obj transform is specified in the - // transformation matrix of a scenegraph node - this->TransformVertices(*i); + // if absolutely no material has been loaded from the file + // we need to generate a default material + this->GenerateDefaultMaterial(); - // now we need to create proper meshes from the import - // we need to split them by materials, build valid vertex/face lists ... - this->BuildUniqueRepresentation(*i); + // process all meshes + std::vector avOutMeshes; + avOutMeshes.reserve(this->mParser->m_vMeshes.size()*2); + for (std::vector::iterator + i = this->mParser->m_vMeshes.begin(); + i != this->mParser->m_vMeshes.end();++i) + { + if ((*i).bSkip)continue; - // need to generate proper vertex normals if necessary - this->GenerateNormals(*i); + // transform all vertices into worldspace + // world2obj transform is specified in the + // transformation matrix of a scenegraph node + this->TransformVertices(*i); + + // now we need to create proper meshes from the import we need to + // split them by materials, build valid vertex/face lists ... + this->BuildUniqueRepresentation(*i); + + // need to generate proper vertex normals if necessary + this->GenerateNormals(*i); + + // convert all meshes to aiMesh objects + this->ConvertMeshes(*i,avOutMeshes); + } + + // now build the output mesh list. remove dummies + pScene->mNumMeshes = (unsigned int)avOutMeshes.size(); + aiMesh** pp = pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; + for (std::vector::const_iterator + i = avOutMeshes.begin(); + i != avOutMeshes.end();++i) + { + if (!(*i)->mNumFaces)continue; + *pp++ = *i; + } + pScene->mNumMeshes = (unsigned int)(pp - pScene->mMeshes); + + // buil final material indices (remove submaterials and make the final list) + this->BuildMaterialIndices(); + + // build the final node graph + this->BuildNodes(); + + // build output animations + this->BuildAnimations(); - // convert all meshes to aiMesh objects - this->ConvertMeshes(*i,avOutMeshes); } - - // now build the output mesh list. remove dummies - pScene->mNumMeshes = (unsigned int)avOutMeshes.size(); - aiMesh** pp = pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; - for (std::vector::const_iterator - i = avOutMeshes.begin(); - i != avOutMeshes.end();++i) + catch (ImportErrorException* ex) { - if (!(*i)->mNumFaces)continue; - *pp++ = *i; + delete this->mParser; AI_DEBUG_INVALIDATE_PTR( this->mParser ); + delete[] this->mBuffer; AI_DEBUG_INVALIDATE_PTR( this->mBuffer ); + throw ex; } - pScene->mNumMeshes = (unsigned int)(pp - pScene->mMeshes); - - // buil final material indices (remove submaterials and make the final list) - this->BuildMaterialIndices(); - - // build the final node graph - this->BuildNodes(); - - // build output animations - this->BuildAnimations(); - - // delete the ASE parser - delete this->mParser; - this->mParser = NULL; + delete this->mParser; AI_DEBUG_INVALIDATE_PTR( this->mParser ); + delete[] this->mBuffer; AI_DEBUG_INVALIDATE_PTR( this->mBuffer ); return; } // ------------------------------------------------------------------------------------------------ @@ -333,11 +336,7 @@ void ASEImporter::AddNodes(aiNode* pcParent,const char* szName, aiMatrix4x4 mParentAdjust = decompTrafo.mMatrix; mParentAdjust.Inverse(); - //if(ComputeLocalToWorldShift(mParentAdjust, decompTrafo, mesh.inherit)) - { - node->mTransformation = mParentAdjust*mesh.mTransform; - } - //else node->mTransformation = mesh.mTransform; + node->mTransformation = mParentAdjust*mesh.mTransform; // Transform all vertices of the mesh back into their local space -> // at the moment they are pretransformed @@ -352,8 +351,6 @@ void ASEImporter::AddNodes(aiNode* pcParent,const char* szName, pvCurPtr++; } - //pcMesh->mColors[2] = NULL; - // add sub nodes aiMatrix4x4 mNewAbs = decompTrafo.mMatrix * node->mTransformation; ASE::DecompTransform dec( mNewAbs); @@ -1242,8 +1239,7 @@ void ASEImporter::GenerateNormals(ASE::Mesh& mesh) vNormals += mesh.mNormals[(*a)]; fDiv += 1.0f; } - vNormals.x /= fDiv;vNormals.y /= fDiv;vNormals.z /= fDiv; - vNormals.Normalize(); + vNormals /= fDiv; avNormals[(*i).mIndices[c]] = vNormals; poResult.clear(); } diff --git a/code/ASEParser.cpp b/code/ASEParser.cpp index 4da2dd8f1..4c3b99f3b 100644 --- a/code/ASEParser.cpp +++ b/code/ASEParser.cpp @@ -55,9 +55,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "../include/aiScene.h" #include "../include/aiAssert.h" -// boost headers -#include - using namespace Assimp; using namespace Assimp::ASE; diff --git a/code/CalcTangentsProcess.cpp b/code/CalcTangentsProcess.cpp index 890187707..8fa0a9a00 100644 --- a/code/CalcTangentsProcess.cpp +++ b/code/CalcTangentsProcess.cpp @@ -43,11 +43,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * tangents and bitangents for all imported meshes */ +// STL headers #include #include -#include "../include/DefaultLogger.h" + +// internal headers #include "CalcTangentsProcess.h" #include "SpatialSort.h" + +// public ASSIMP headers +#include "../include/DefaultLogger.h" #include "../include/aiPostProcess.h" #include "../include/aiMesh.h" #include "../include/aiScene.h" @@ -85,7 +90,7 @@ void CalcTangentsProcess::Execute( aiScene* pScene) for( unsigned int a = 0; a < pScene->mNumMeshes; a++) if(ProcessMesh( pScene->mMeshes[a]))bHas = true; - if (bHas)DefaultLogger::get()->debug("CalcTangentsProcess finished. There was much work to do ..."); + if (bHas)DefaultLogger::get()->debug("CalcTangentsProcess finished. Tangents have been calculated"); else DefaultLogger::get()->debug("CalcTangentsProcess finished"); } diff --git a/code/GenFaceNormalsProcess.cpp b/code/GenFaceNormalsProcess.cpp index 65af8c1d8..4c8666a41 100644 --- a/code/GenFaceNormalsProcess.cpp +++ b/code/GenFaceNormalsProcess.cpp @@ -50,25 +50,27 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; +// ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer GenFaceNormalsProcess::GenFaceNormalsProcess() { } +// ------------------------------------------------------------------------------------------------ // Destructor, private as well GenFaceNormalsProcess::~GenFaceNormalsProcess() { // nothing to do here } -// ------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool GenFaceNormalsProcess::IsActive( unsigned int pFlags) const { return (pFlags & aiProcess_GenNormals) != 0; } -// ------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. void GenFaceNormalsProcess::Execute( aiScene* pScene) { @@ -86,10 +88,10 @@ void GenFaceNormalsProcess::Execute( aiScene* pScene) "Face normals have been calculated"); } else DefaultLogger::get()->debug("GenFaceNormalsProcess finished. " - "There was nothing to do"); + "Normals are already there"); } -// ------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. bool GenFaceNormalsProcess::GenMeshFaceNormals (aiMesh* pMesh) { @@ -109,8 +111,8 @@ bool GenFaceNormalsProcess::GenMeshFaceNormals (aiMesh* pMesh) aiVector3D pDelta2 = *pV3 - *pV1; aiVector3D vNor = pDelta1 ^ pDelta2; - if (face.mIndices[1] > face.mIndices[2]) - vNor *= -1.0f; + //if (face.mIndices[1] > face.mIndices[2]) + // vNor *= -1.0f; for (unsigned int i = 0;i < face.mNumIndices;++i) { diff --git a/code/GenVertexNormalsProcess.cpp b/code/GenVertexNormalsProcess.cpp index 0cd45ef93..d471731b1 100644 --- a/code/GenVertexNormalsProcess.cpp +++ b/code/GenVertexNormalsProcess.cpp @@ -51,25 +51,27 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; +// ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer GenVertexNormalsProcess::GenVertexNormalsProcess() { } +// ------------------------------------------------------------------------------------------------ // Destructor, private as well GenVertexNormalsProcess::~GenVertexNormalsProcess() { // nothing to do here } -// ------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool GenVertexNormalsProcess::IsActive( unsigned int pFlags) const { return (pFlags & aiProcess_GenSmoothNormals) != 0; } -// ------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. void GenVertexNormalsProcess::Execute( aiScene* pScene) { @@ -88,10 +90,10 @@ void GenVertexNormalsProcess::Execute( aiScene* pScene) "Vertex normals have been calculated"); } else DefaultLogger::get()->debug("GenVertexNormalsProcess finished. " - "There was nothing to do"); + "Normals are already there"); } -// ------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. bool GenVertexNormalsProcess::GenMeshVertexNormals (aiMesh* pMesh) { @@ -110,8 +112,8 @@ bool GenVertexNormalsProcess::GenMeshVertexNormals (aiMesh* pMesh) aiVector3D pDelta2 = *pV3 - *pV1; aiVector3D vNor = pDelta1 ^ pDelta2; - if (face.mIndices[1] > face.mIndices[2]) - vNor *= -1.0f; + /*if (face.mIndices[1] > face.mIndices[2]) + vNor *= -1.0f;*/ for (unsigned int i = 0;i < face.mNumIndices;++i) { @@ -154,7 +156,7 @@ bool GenVertexNormalsProcess::GenMeshVertexNormals (aiMesh* pMesh) pcNor /= (float) verticesFound.size(); pcNew[i] = pcNor; } - delete pMesh->mNormals; + delete[] pMesh->mNormals; pMesh->mNormals = pcNew; return true; diff --git a/code/GenVertexNormalsProcess.h b/code/GenVertexNormalsProcess.h index 067e20c5c..4eee32ae7 100644 --- a/code/GenVertexNormalsProcess.h +++ b/code/GenVertexNormalsProcess.h @@ -44,8 +44,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "BaseProcess.h" #include "../include/aiMesh.h" -namespace Assimp - { +class GenNormalsTest; +namespace Assimp { // --------------------------------------------------------------------------- /** The GenFaceNormalsProcess computes vertex normals for all vertizes of all meshes @@ -53,6 +53,7 @@ namespace Assimp class ASSIMP_API GenVertexNormalsProcess : public BaseProcess { friend class Importer; + friend class ::GenNormalsTest; protected: /** Constructor to be privately used by Importer */ diff --git a/code/HMPLoader.cpp b/code/HMPLoader.cpp index e749b5e9b..12905db31 100644 --- a/code/HMPLoader.cpp +++ b/code/HMPLoader.cpp @@ -41,9 +41,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** @file Implementation of the MDL importer class */ +// internal headers #include "MaterialSystem.h" #include "HMPLoader.h" +#include "MD2FileData.h" +// public ASSIMP headers #include "../include/DefaultLogger.h" #include "../include/IOStream.h" #include "../include/IOSystem.h" @@ -51,12 +54,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "../include/aiScene.h" #include "../include/aiAssert.h" +// boost headers #include using namespace Assimp; -extern float g_avNormals[162][3]; - // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer HMPImporter::HMPImporter() @@ -103,12 +105,12 @@ void HMPImporter::InternReadFile( throw new ImportErrorException( "Failed to open HMP file " + pFile + "."); } - // check whether the ply file is large enough to contain + // check whether the HMP file is large enough to contain // at least the file header size_t fileSize = file->FileSize(); if( fileSize < 50) { - throw new ImportErrorException( ".hmp File is too small."); + throw new ImportErrorException( "HMP File is too small."); } // allocate storage and copy the contents of the file to a memory buffer @@ -147,9 +149,17 @@ void HMPImporter::InternReadFile( } else { + // print the magic word to the logger + char szBuffer[5]; + szBuffer[0] = ((char*)&iMagic)[0]; + szBuffer[1] = ((char*)&iMagic)[1]; + szBuffer[2] = ((char*)&iMagic)[2]; + szBuffer[3] = ((char*)&iMagic)[3]; + szBuffer[4] = '\0'; + // we're definitely unable to load this file throw new ImportErrorException( "Unknown HMP subformat " + pFile + - ". Magic word is not known"); + ". Magic word (" + szBuffer + ") is not known"); } } catch (ImportErrorException* ex) { @@ -161,39 +171,42 @@ void HMPImporter::InternReadFile( delete[] this->mBuffer; return; } +// ------------------------------------------------------------------------------------------------ +void HMPImporter::ValidateHeader_HMP457( ) +{ + const HMP::Header_HMP5* const pcHeader = (const HMP::Header_HMP5*)this->mBuffer; + if (120 > this->iFileSize) + { + throw new ImportErrorException("HMP file is too small (header size is " + "120 bytes, this file is smaller)"); + } + + if (!pcHeader->ftrisize_x || !pcHeader->ftrisize_y) + { + throw new ImportErrorException("Size of triangles in either x or y direction is zero"); + } + if(pcHeader->fnumverts_x < 1.0f || (pcHeader->numverts/pcHeader->fnumverts_x) < 1.0f) + { + throw new ImportErrorException("Number of triangles in either x or y direction is zero"); + } + if(!pcHeader->numframes) + { + throw new ImportErrorException("There are no frames. At least one should be there"); + } +} // ------------------------------------------------------------------------------------------------ void HMPImporter::InternReadFile_HMP4( ) { throw new ImportErrorException("HMP4 is currently not supported"); } - // ------------------------------------------------------------------------------------------------ void HMPImporter::InternReadFile_HMP5( ) { - throw new ImportErrorException("HMP4 is currently not supported"); -} - -// ------------------------------------------------------------------------------------------------ -void HMPImporter::InternReadFile_HMP7( ) -{ - if (120 > this->iFileSize) - { - throw new ImportErrorException("HMP7 file is too small (header size is " - "120 bytes, this file is smaller)"); - } - // read the file header and skip everything to byte 84 const HMP::Header_HMP5* pcHeader = (const HMP::Header_HMP5*)this->mBuffer; const unsigned char* szCurrent = (const unsigned char*)(this->mBuffer+84); - - if (!pcHeader->ftrisize_x || !pcHeader->ftrisize_y || - pcHeader->fnumverts_x < 1.0f || (pcHeader->numverts/pcHeader->fnumverts_x) < 1.0f || - !pcHeader->numframes) - { - throw new ImportErrorException("One or more of the values in the HMP7 " - "file header are invalid."); - } + this->ValidateHeader_HMP457(); // generate an output mesh this->pScene->mNumMeshes = 1; @@ -207,6 +220,120 @@ void HMPImporter::InternReadFile_HMP7( ) const unsigned int height = (unsigned int)(pcHeader->numverts / pcHeader->fnumverts_x); const unsigned int width = (unsigned int)pcHeader->fnumverts_x; + // generate/load a material for the terrain + this->CreateMaterial(szCurrent,&szCurrent); + + // goto offset 120, I don't know why ... + // (fixme) is this the frame header? I assume yes since it starts with 2. + szCurrent += 36; + + this->SizeCheck(szCurrent + sizeof(const HMP::Vertex_HMP7)*height*width); + + // now load all vertices from the file + aiVector3D* pcVertOut = pcMesh->mVertices; + aiVector3D* pcNorOut = pcMesh->mNormals; + const HMP::Vertex_HMP5* src = (const HMP::Vertex_HMP5*) szCurrent; + for (unsigned int y = 0; y < height;++y) + { + for (unsigned int x = 0; x < width;++x) + { + pcVertOut->x = x * pcHeader->ftrisize_x; + pcVertOut->y = y * pcHeader->ftrisize_y; + pcVertOut->z = (((float)src->z / 0xffff)-0.5f) * pcHeader->ftrisize_x * 8.0f; + MD2::LookupNormalIndex(src->normals162index, *pcNorOut ); + ++pcVertOut;++pcNorOut;++src; + } + } + + // generate texture coordinates if necessary + if (pcHeader->numskins)this->GenerateTextureCoords(width,height); + + // now build a list of faces + this->CreateOutputFaceList(width,height); + + // there is no nodegraph in HMP files. Simply assign the one mesh + // (no, not the one ring) to the root node + this->pScene->mRootNode = new aiNode(); + this->pScene->mRootNode->mName.Set("terrain_root"); + this->pScene->mRootNode->mNumMeshes = 1; + this->pScene->mRootNode->mMeshes = new unsigned int[1]; + this->pScene->mRootNode->mMeshes[0] = 0; +} +// ------------------------------------------------------------------------------------------------ +void HMPImporter::InternReadFile_HMP7( ) +{ + // read the file header and skip everything to byte 84 + const HMP::Header_HMP5* const pcHeader = (const HMP::Header_HMP5*)this->mBuffer; + const unsigned char* szCurrent = (const unsigned char*)(this->mBuffer+84); + this->ValidateHeader_HMP457(); + + // generate an output mesh + this->pScene->mNumMeshes = 1; + this->pScene->mMeshes = new aiMesh*[1]; + aiMesh* pcMesh = this->pScene->mMeshes[0] = new aiMesh(); + + pcMesh->mMaterialIndex = 0; + pcMesh->mVertices = new aiVector3D[pcHeader->numverts]; + pcMesh->mNormals = new aiVector3D[pcHeader->numverts]; + + const unsigned int height = (unsigned int)(pcHeader->numverts / pcHeader->fnumverts_x); + const unsigned int width = (unsigned int)pcHeader->fnumverts_x; + + // generate/load a material for the terrain + this->CreateMaterial(szCurrent,&szCurrent); + + // goto offset 120, I don't know why ... + // (fixme) is this the frame header? I assume yes since it starts with 2. + szCurrent += 36; + + this->SizeCheck(szCurrent + sizeof(const HMP::Vertex_HMP7)*height*width); + + // now load all vertices from the file + aiVector3D* pcVertOut = pcMesh->mVertices; + aiVector3D* pcNorOut = pcMesh->mNormals; + const HMP::Vertex_HMP7* src = (const HMP::Vertex_HMP7*) szCurrent; + for (unsigned int y = 0; y < height;++y) + { + for (unsigned int x = 0; x < width;++x) + { + pcVertOut->x = x * pcHeader->ftrisize_x; + pcVertOut->y = y * pcHeader->ftrisize_y; + + // FIXME: What exctly is the correct scaling factor to use? + // possibly pcHeader->scale_origin[2] in combination with a + // signed interpretation of src->z? + pcVertOut->z = (((float)src->z / 0xffff)-0.5f) * pcHeader->ftrisize_x * 8.0f; + + pcNorOut->x = ((float)src->normal_x / 0x80 ); // * pcHeader->scale_origin[0]; + pcNorOut->y = ((float)src->normal_y / 0x80 ); // * pcHeader->scale_origin[1]; + pcNorOut->z = 1.0f; + pcNorOut->Normalize(); + + ++pcVertOut;++pcNorOut;++src; + } + } + + // generate texture coordinates if necessary + if (pcHeader->numskins)this->GenerateTextureCoords(width,height); + + // now build a list of faces + this->CreateOutputFaceList(width,height); + + // there is no nodegraph in HMP files. Simply assign the one mesh + // (no, not the One Ring) to the root node + this->pScene->mRootNode = new aiNode(); + this->pScene->mRootNode->mName.Set("terrain_root"); + this->pScene->mRootNode->mNumMeshes = 1; + this->pScene->mRootNode->mMeshes = new unsigned int[1]; + this->pScene->mRootNode->mMeshes[0] = 0; +} +// ------------------------------------------------------------------------------------------------ +void HMPImporter::CreateMaterial(const unsigned char* szCurrent, + const unsigned char** szCurrentOut) +{ + aiMesh* const pcMesh = this->pScene->mMeshes[0]; + const HMP::Header_HMP5* const pcHeader = (const HMP::Header_HMP5*)this->mBuffer; + // we don't need to generate texture coordinates if // we have no textures in the file ... if (pcHeader->numskins) @@ -225,7 +352,7 @@ void HMPImporter::InternReadFile_HMP7( ) pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); aiColor3D clr; - clr.b = clr.g = clr.r = 0.7f; + clr.b = clr.g = clr.r = 0.6f; pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); @@ -241,45 +368,14 @@ void HMPImporter::InternReadFile_HMP7( ) this->pScene->mMaterials = new aiMaterial*[1]; this->pScene->mMaterials[0] = pcHelper; } + *szCurrentOut = szCurrent; +} +// ------------------------------------------------------------------------------------------------ +void HMPImporter::CreateOutputFaceList(unsigned int width,unsigned int height) +{ + aiMesh* const pcMesh = this->pScene->mMeshes[0]; - // goto offset 120, I don't know why ... - // (fixme) is this the frame header? I assume yes since it starts - // with 2. - szCurrent += 36; - - this->SizeCheck(szCurrent + sizeof(const HMP::Vertex_HMP7)*height*width); - - // now load all vertices from the file - aiVector3D* pcVertOut = pcMesh->mVertices; - aiVector3D* pcNorOut = pcMesh->mNormals; - const HMP::Vertex_HMP7* src = (const HMP::Vertex_HMP7*) szCurrent; - for (unsigned int y = 0; y < height;++y) - { - for (unsigned int x = 0; x < width;++x) - { - pcVertOut->x = x * pcHeader->ftrisize_x; - pcVertOut->y = y * pcHeader->ftrisize_y; - // FIXME: What exctly is the correct scaling factor to use? - // possibly pcHeader->scale_origin[2] in combination with a - // signed interpretation of src->z? - pcVertOut->z = (((float)src->z / 0xffff)-0.5f) * pcHeader->ftrisize_x * 8.0f; - - pcNorOut->x = ((float)src->normal_x / 0x80 ); // * pcHeader->scale_origin[0]; - pcNorOut->y = ((float)src->normal_y / 0x80 ); // * pcHeader->scale_origin[1]; - pcNorOut->z = 1.0f; - pcNorOut->Normalize(); - - ++pcVertOut;++pcNorOut;++src; - } - } - - // generate texture coordinates if necessary - if (pcHeader->numskins) - { - this->GenerateTextureCoords(width,height); - } - - // now build a list of faces + // allocate enough storage const unsigned int iNumSquares = (width-1) * (height-1); pcMesh->mNumFaces = iNumSquares << 1; pcMesh->mFaces = new aiFace[pcMesh->mNumFaces]; @@ -289,12 +385,13 @@ void HMPImporter::InternReadFile_HMP7( ) aiVector3D* pcNormals = new aiVector3D[pcMesh->mNumVertices]; aiFace* pcFaceOut(pcMesh->mFaces); - pcVertOut = pcVertices; - pcNorOut = pcNormals; + aiVector3D* pcVertOut = pcVertices; + aiVector3D* pcNorOut = pcNormals; aiVector3D* pcUVs = pcMesh->mTextureCoords[0] ? new aiVector3D[pcMesh->mNumVertices] : NULL; aiVector3D* pcUVOut(pcUVs); + // build the terrain square unsigned int iCurrent = 0; for (unsigned int y = 0; y < height-1;++y) { @@ -360,16 +457,6 @@ void HMPImporter::InternReadFile_HMP7( ) delete[] pcMesh->mTextureCoords[0]; pcMesh->mTextureCoords[0] = pcUVs; } - - // there is no nodegraph in HMP files. Simply assign the one mesh - // (no, not the one ring) to the root node - this->pScene->mRootNode = new aiNode(); - this->pScene->mRootNode->mName.Set("terrain_root"); - this->pScene->mRootNode->mNumMeshes = 1; - this->pScene->mRootNode->mMeshes = new unsigned int[1]; - this->pScene->mRootNode->mMeshes[0] = 0; - - return; } // ------------------------------------------------------------------------------------------------ void HMPImporter::ReadFirstSkin(unsigned int iNumSkins, const unsigned char* szCursor, @@ -433,15 +520,16 @@ void HMPImporter::GenerateTextureCoords( aiVector3D* uv = this->pScene->mMeshes[0]->mTextureCoords[0]; - const float fX = (1.0f / height) + (1.0f / height) / (height-1); - const float fY = (1.0f / width) + (1.0f / width) / (width-1); + const float fY = (1.0f / height) + (1.0f / height) / (height-1); + const float fX = (1.0f / width) + (1.0f / width) / (width-1); for (unsigned int y = 0; y < height;++y) { for (unsigned int x = 0; x < width;++x) { - uv->x = fX*x; uv->y = 1.0f-fY*y; + uv->x = fX*x; + uv->z = 0.0f; ++uv; } } diff --git a/code/HMPLoader.h b/code/HMPLoader.h index 6b7ae4182..4012dbe87 100644 --- a/code/HMPLoader.h +++ b/code/HMPLoader.h @@ -70,6 +70,16 @@ class MaterialHelper; namespace HMP { +// ugly compiler dependent packing stuff +#if defined(_MSC_VER) || defined(__BORLANDC__) || defined (__BCPLUSPLUS__) +# pragma pack(push,1) +# define PACK_STRUCT +#elif defined( __GNUC__ ) +# define PACK_STRUCT __attribute__((packed)) +#else +# error Compiler not supported. Never do this again. +#endif + // --------------------------------------------------------------------------- /** Data structure for the header of a HMP5 file. * This is also used by HMP4 and HMP7, but with modifications @@ -111,8 +121,27 @@ struct Header_HMP5 int32_t num_stverts; int32_t flags; float size; -}; +} PACK_STRUCT; +// --------------------------------------------------------------------------- +/** Data structure for a terrain vertex in a HMP4 file +*/ +struct Vertex_HMP4 +{ + uint16_t p_pos[3]; + uint8_t normals162index; + uint8_t pad; +} PACK_STRUCT; + +// --------------------------------------------------------------------------- +/** Data structure for a terrain vertex in a HMP5 file +*/ +struct Vertex_HMP5 +{ + uint16_t z; + uint8_t normals162index; + uint8_t pad; +} PACK_STRUCT; // --------------------------------------------------------------------------- /** Data structure for a terrain vertex in a HMP7 file @@ -121,7 +150,13 @@ struct Vertex_HMP7 { uint16_t z; int8_t normal_x,normal_y; -}; +} PACK_STRUCT; + +// reset packing to the original value +#if defined(_MSC_VER) || defined(__BORLANDC__) || defined (__BCPLUSPLUS__) +# pragma pack( pop ) +#endif +#undef PACK_STRUCT }; //! namespace HMP @@ -182,6 +217,25 @@ protected: */ void InternReadFile_HMP7( ); + // ------------------------------------------------------------------- + /** Validate a HMP 5,4,7 file header + */ + void ValidateHeader_HMP457( ); + + // ------------------------------------------------------------------- + /** Try to load one material from the file, if this fails create + * a default material + */ + void CreateMaterial(const unsigned char* szCurrent, + const unsigned char** szCurrentOut); + + // ------------------------------------------------------------------- + /** Build a list of output faces and vertices. The function + * triangulates the height map read from the file + * \param width Width of the height field + * \param width Height of the height field + */ + void CreateOutputFaceList(unsigned int width,unsigned int height); // ------------------------------------------------------------------- /** Generate planar texture coordinates for a terrain diff --git a/code/Hash.h b/code/Hash.h new file mode 100644 index 000000000..d7ef61a78 --- /dev/null +++ b/code/Hash.h @@ -0,0 +1,67 @@ + +#ifndef AI_HASH_H_INCLUDED +#define AI_HASH_H_INCLUDED + +// ------------------------------------------------------------------------------------------------ +// hashing function taken from +// http://www.azillionmonkeys.com/qed/hash.html +// (incremental version of the hashing function) +// (stdint.h should have been been included here) +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif + +// ------------------------------------------------------------------------------------------------ +inline uint32_t SuperFastHash (const char * data, int len, uint32_t hash = 0) { +uint32_t tmp; +int rem; + + if (len <= 0 || data == NULL) return 0; + + rem = len & 3; + len >>= 2; + + /* Main loop */ + for (;len > 0; len--) { + hash += get16bits (data); + tmp = (get16bits (data+2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + data += 2*sizeof (uint16_t); + hash += hash >> 11; + } + + /* Handle end cases */ + switch (rem) { + case 3: hash += get16bits (data); + hash ^= hash << 16; + hash ^= data[sizeof (uint16_t)] << 18; + hash += hash >> 11; + break; + case 2: hash += get16bits (data); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: hash += *data; + hash ^= hash << 10; + hash += hash >> 1; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; +} + +#endif // !! AI_HASH_H_INCLUDED \ No newline at end of file diff --git a/code/Importer.cpp b/code/Importer.cpp index f67fac87c..2132bb891 100644 --- a/code/Importer.cpp +++ b/code/Importer.cpp @@ -92,6 +92,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #if (!defined AI_BUILD_NO_SMD_IMPORTER) # include "SMDLoader.h" #endif +#if (!defined AI_BUILD_NO_MD5_IMPORTER) +# include "MD5Loader.h" +#endif +#if (!defined AI_BUILD_NO_STL_IMPORTER) +# include "STLLoader.h" +#endif // PostProcess-Steps #if (!defined AI_BUILD_NO_CALCTANGENTS_PROCESS) @@ -177,12 +183,18 @@ Importer::Importer() : #if (!defined AI_BUILD_NO_ASE_IMPORTER) mImporter.push_back( new ASEImporter()); #endif - #if (!defined AI_BUILD_NO_HMP_IMPORTER) +#if (!defined AI_BUILD_NO_HMP_IMPORTER) mImporter.push_back( new HMPImporter()); #endif - #if (!defined AI_BUILD_NO_SMD_IMPORTER) +#if (!defined AI_BUILD_NO_SMD_IMPORTER) mImporter.push_back( new SMDImporter()); #endif +#if (!defined AI_BUILD_NO_MD5_IMPORTER) + mImporter.push_back( new MD5Importer()); +#endif +#if (!defined AI_BUILD_NO_STL_IMPORTER) + mImporter.push_back( new STLImporter()); +#endif // add an instance of each post processing step here in the order // of sequence it is executed diff --git a/code/ImproveCacheLocality.cpp b/code/ImproveCacheLocality.cpp index 98727414f..68733b950 100644 --- a/code/ImproveCacheLocality.cpp +++ b/code/ImproveCacheLocality.cpp @@ -39,8 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- */ -/** @file Implementation of the post processing step to join identical vertices - * for all imported meshes +/** @file Implementation of the post processing to improve the + * cache locality of a mesh. *
* The algorithm is roughly basing on this paper: * http://www.cs.princeton.edu/gfx/pubs/Sander_2007_%3ETR/tipsy.pdf @@ -167,6 +167,17 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN } delete[] piFIFOStack; float fACMR = (float)iCacheMisses / pMesh->mNumFaces; + if (3.0 == fACMR) + { + char szBuff[128]; // should be sufficiently large in every case + + // the JoinIdenticalVertices process has not been executed on this + // mesh, otherwise this value would normally be at least minimally# + // smaller than 3.0 ... + ::sprintf(szBuff,"Mesh %i: JIV-Step has not been executed properly (precondition)",meshNum); + DefaultLogger::get()->warn(szBuff); + return; + } // first we need to build a vertex-triangle adjacency list VertexTriangleAdjacency adj(pMesh->mFaces,pMesh->mNumFaces, pMesh->mNumVertices,true); @@ -351,7 +362,7 @@ void ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshN char szBuff[128]; // should be sufficiently large in every case float fACMR2 = (float)iCacheMisses / pMesh->mNumFaces; - sprintf(szBuff,"Mesh %i | ACMR in: %f out: %f | ~%.1f%%",meshNum,fACMR,fACMR2, + ::sprintf(szBuff,"Mesh %i | ACMR in: %f out: %f | ~%.1f%%",meshNum,fACMR,fACMR2, ((fACMR - fACMR2) / fACMR) * 100.f); DefaultLogger::get()->info(szBuff); } diff --git a/code/MD2Loader.cpp b/code/MD2Loader.cpp index a4686ac92..38415af19 100644 --- a/code/MD2Loader.cpp +++ b/code/MD2Loader.cpp @@ -129,7 +129,6 @@ void MD2Importer::ValidateHeader( ) this->m_pcHeader->offsetEnd > this->fileSize) { throw new ImportErrorException("Invalid MD2 header: some offsets are outside the file"); - delete[] this->mBuffer; } if (this->m_pcHeader->numSkins > AI_MD2_MAX_SKINS) @@ -142,7 +141,7 @@ void MD2Importer::ValidateHeader( ) // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. void MD2Importer::InternReadFile( - const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) + const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) { boost::scoped_ptr file( pIOHandler->Open( pFile)); @@ -160,160 +159,159 @@ void MD2Importer::InternReadFile( throw new ImportErrorException( "md2 File is too small."); } - // allocate storage and copy the contents of the file to a memory buffer - this->mBuffer = new unsigned char[fileSize]; - file->Read( (void*)mBuffer, 1, fileSize); - - this->m_pcHeader = (const MD2::Header*)this->mBuffer; - - // check magic number - if (this->m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_BE && - this->m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_LE) + try { - delete[] this->mBuffer; - throw new ImportErrorException( "Invalid md2 file: Magic bytes not found"); - } + // allocate storage and copy the contents of the file to a memory buffer + this->mBuffer = new unsigned char[fileSize]; + file->Read( (void*)mBuffer, 1, fileSize); - // check file format version - if (this->m_pcHeader->version != 8) - { - DefaultLogger::get()->warn( "Unsupported md2 file version. Continuing happily ..."); - } - this->ValidateHeader(); + this->m_pcHeader = (const MD2::Header*)this->mBuffer; - // check some values whether they are valid - if (0 == this->m_pcHeader->numFrames) - { - delete[] this->mBuffer; - throw new ImportErrorException( "Invalid md2 file: NUM_FRAMES is 0"); - } - if (this->m_pcHeader->offsetEnd > (int32_t)fileSize) - { - delete[] this->mBuffer; - throw new ImportErrorException( "Invalid md2 file: File is too small"); - } - - // there won't be more than one mesh inside the file - pScene->mNumMaterials = 1; - pScene->mRootNode = new aiNode(); - pScene->mRootNode->mNumMeshes = 1; - pScene->mRootNode->mMeshes = new unsigned int[1]; - pScene->mRootNode->mMeshes[0] = 0; - pScene->mMaterials = new aiMaterial*[1]; - pScene->mMaterials[0] = new MaterialHelper(); - pScene->mNumMeshes = 1; - pScene->mMeshes = new aiMesh*[1]; - aiMesh* pcMesh = pScene->mMeshes[0] = new aiMesh(); - - // navigate to the begin of the frame data - const MD2::Frame* pcFrame = (const MD2::Frame*) ( - (unsigned char*)this->m_pcHeader + this->m_pcHeader->offsetFrames); - - // navigate to the begin of the triangle data - MD2::Triangle* pcTriangles = (MD2::Triangle*) ( - (unsigned char*)this->m_pcHeader + this->m_pcHeader->offsetTriangles); - - // navigate to the begin of the tex coords data - const MD2::TexCoord* pcTexCoords = (const MD2::TexCoord*) ( - (unsigned char*)this->m_pcHeader + this->m_pcHeader->offsetTexCoords); - - // navigate to the begin of the vertex data - const MD2::Vertex* pcVerts = (const MD2::Vertex*) (pcFrame->vertices); - - pcMesh->mNumFaces = this->m_pcHeader->numTriangles; - pcMesh->mFaces = new aiFace[this->m_pcHeader->numTriangles]; - - // allocate output storage - pcMesh->mNumVertices = (unsigned int)pcMesh->mNumFaces*3; - pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; - pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; - - // not sure whether there are MD2 files without texture coordinates - // NOTE: texture coordinates can be there without a texture, - // but a texture can't be there without a valid UV channel - if (this->m_pcHeader->numTexCoords && this->m_pcHeader->numSkins) - { - // navigate to the first texture associated with the mesh - const MD2::Skin* pcSkins = (const MD2::Skin*) ((unsigned char*)this->m_pcHeader + - this->m_pcHeader->offsetSkins); - - const int iMode = (int)aiShadingMode_Gouraud; - MaterialHelper* pcHelper = (MaterialHelper*)pScene->mMaterials[0]; - pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); - - aiColor3D clr; - clr.b = clr.g = clr.r = 1.0f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); - - clr.b = clr.g = clr.r = 0.05f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); - - if (pcSkins->name[0]) + // check magic number + if (this->m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_BE && + this->m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_LE) { - aiString szString; - const size_t iLen = ::strlen(pcSkins->name); - ::memcpy(szString.data,pcSkins->name,iLen); - szString.data[iLen] = '\0'; - szString.length = iLen; + throw new ImportErrorException( "Invalid md2 file: Magic bytes not found"); + } - pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); + // check file format version + if (this->m_pcHeader->version != 8) + { + DefaultLogger::get()->warn( "Unsupported md2 file version. Continuing happily ..."); + } + this->ValidateHeader(); + + // check some values whether they are valid + if (0 == this->m_pcHeader->numFrames) + { + throw new ImportErrorException( "Invalid md2 file: NUM_FRAMES is 0"); + } + if (this->m_pcHeader->offsetEnd > (int32_t)fileSize) + { + throw new ImportErrorException( "Invalid md2 file: File is too small"); + } + + // there won't be more than one mesh inside the file + pScene->mNumMaterials = 1; + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mRootNode->mMeshes[0] = 0; + pScene->mMaterials = new aiMaterial*[1]; + pScene->mMaterials[0] = new MaterialHelper(); + pScene->mNumMeshes = 1; + pScene->mMeshes = new aiMesh*[1]; + aiMesh* pcMesh = pScene->mMeshes[0] = new aiMesh(); + + // navigate to the begin of the frame data + const MD2::Frame* pcFrame = (const MD2::Frame*) ( + (unsigned char*)this->m_pcHeader + this->m_pcHeader->offsetFrames); + + // navigate to the begin of the triangle data + MD2::Triangle* pcTriangles = (MD2::Triangle*) ( + (unsigned char*)this->m_pcHeader + this->m_pcHeader->offsetTriangles); + + // navigate to the begin of the tex coords data + const MD2::TexCoord* pcTexCoords = (const MD2::TexCoord*) ( + (unsigned char*)this->m_pcHeader + this->m_pcHeader->offsetTexCoords); + + // navigate to the begin of the vertex data + const MD2::Vertex* pcVerts = (const MD2::Vertex*) (pcFrame->vertices); + + pcMesh->mNumFaces = this->m_pcHeader->numTriangles; + pcMesh->mFaces = new aiFace[this->m_pcHeader->numTriangles]; + + // allocate output storage + pcMesh->mNumVertices = (unsigned int)pcMesh->mNumFaces*3; + pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; + + // not sure whether there are MD2 files without texture coordinates + // NOTE: texture coordinates can be there without a texture, + // but a texture can't be there without a valid UV channel + if (this->m_pcHeader->numTexCoords && this->m_pcHeader->numSkins) + { + // navigate to the first texture associated with the mesh + const MD2::Skin* pcSkins = (const MD2::Skin*) ((unsigned char*)this->m_pcHeader + + this->m_pcHeader->offsetSkins); + + const int iMode = (int)aiShadingMode_Gouraud; + MaterialHelper* pcHelper = (MaterialHelper*)pScene->mMaterials[0]; + pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + aiColor3D clr; + clr.b = clr.g = clr.r = 1.0f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); + + clr.b = clr.g = clr.r = 0.05f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + + if (pcSkins->name[0]) + { + aiString szString; + const size_t iLen = ::strlen(pcSkins->name); + ::memcpy(szString.data,pcSkins->name,iLen); + szString.data[iLen] = '\0'; + szString.length = iLen; + + pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + else + { + DefaultLogger::get()->warn("Texture file name has zero length. It will be skipped."); + } } else { - DefaultLogger::get()->warn("Texture file name has zero length. It will be skipped."); + // apply a default material + const int iMode = (int)aiShadingMode_Gouraud; + MaterialHelper* pcHelper = (MaterialHelper*)pScene->mMaterials[0]; + pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + aiColor3D clr; + clr.b = clr.g = clr.r = 0.6f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); + + clr.b = clr.g = clr.r = 0.05f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + + aiString szName; + szName.Set(AI_DEFAULT_MATERIAL_NAME); + pcHelper->AddProperty(&szName,AI_MATKEY_NAME); } - } - else - { - // apply a default material - const int iMode = (int)aiShadingMode_Gouraud; - MaterialHelper* pcHelper = (MaterialHelper*)pScene->mMaterials[0]; - pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); - - aiColor3D clr; - clr.b = clr.g = clr.r = 0.6f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); - - clr.b = clr.g = clr.r = 0.05f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); - - aiString szName; - szName.Set(AI_DEFAULT_MATERIAL_NAME); - pcHelper->AddProperty(&szName,AI_MATKEY_NAME); - } - // now read all triangles of the first frame, apply scaling and translation - unsigned int iCurrent = 0; - if (this->m_pcHeader->numTexCoords) - { - // allocate storage for texture coordinates, too - pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; - pcMesh->mNumUVComponents[0] = 2; + // now read all triangles of the first frame, apply scaling and translation + unsigned int iCurrent = 0; - // check whether the skin width or height are zero (this would - // cause a division through zero) - float fDivisorU; - if (!this->m_pcHeader->skinWidth) + float fDivisorU,fDivisorV; + if (this->m_pcHeader->numTexCoords) { - DefaultLogger::get()->error("Skin width is zero but there are " - "valid absolute texture coordinates. Unable to compute " - "relative texture coordinates ranging from 0 to 1"); - fDivisorU = 1.0f; - } - else fDivisorU = (float)this->m_pcHeader->skinWidth; + // allocate storage for texture coordinates, too + pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNumUVComponents[0] = 2; - float fDivisorV; - if (!this->m_pcHeader->skinHeight) - { - DefaultLogger::get()->error("Skin height is zero but there are " - "valid absolute texture coordinates. Unable to compute " - "relative texture coordinates ranging from 0 to 1"); - fDivisorV = 1.0f; + // check whether the skin width or height are zero (this would + // cause a division through zero) + if (!this->m_pcHeader->skinWidth) + { + DefaultLogger::get()->error("Skin width is zero but there are " + "valid absolute texture coordinates. Unable to compute " + "relative texture coordinates ranging from 0 to 1"); + fDivisorU = 1.0f; + } + else fDivisorU = (float)this->m_pcHeader->skinWidth; + if (!this->m_pcHeader->skinHeight) + { + DefaultLogger::get()->error("Skin height is zero but there are " + "valid absolute texture coordinates. Unable to compute " + "relative texture coordinates ranging from 0 to 1"); + fDivisorV = 1.0f; + } + else fDivisorV = (float)this->m_pcHeader->skinHeight; } - else fDivisorV = (float)this->m_pcHeader->skinHeight; for (unsigned int i = 0; i < (unsigned int)this->m_pcHeader->numTriangles;++i) { @@ -357,22 +355,25 @@ void MD2Importer::InternReadFile( LookupNormalIndex(pcVerts[iIndex].lightNormalIndex,vNormal); vNormal.y *= -1.0f; - // validate texture coordinates - if (pcTriangles[iIndex].textureIndices[c] >= this->m_pcHeader->numTexCoords) + if (this->m_pcHeader->numTexCoords) { - DefaultLogger::get()->error("UV index is outside the allowed range"); - pcTriangles[iIndex].textureIndices[c] = this->m_pcHeader->numTexCoords-1; + // validate texture coordinates + if (pcTriangles[iIndex].textureIndices[c] >= this->m_pcHeader->numTexCoords) + { + DefaultLogger::get()->error("UV index is outside the allowed range"); + pcTriangles[iIndex].textureIndices[c] = this->m_pcHeader->numTexCoords-1; + } + + aiVector3D& pcOut = pcMesh->mTextureCoords[0][iCurrent]; + float u,v; + + // the texture coordinates are absolute values but we + // need relative values between 0 and 1 + u = (float)pcTexCoords[pcTriangles[i].textureIndices[c]].s / fDivisorU; + v = (float)pcTexCoords[pcTriangles[i].textureIndices[c]].t / fDivisorV; + pcOut.x = u; + pcOut.y = 1.0f - v; // FIXME: Is this correct for MD2? } - - aiVector3D& pcOut = pcMesh->mTextureCoords[0][iCurrent]; - float u,v; - - // the texture coordinates are absolute values but we - // need relative values between 0 and 1 - u = (float)pcTexCoords[pcTriangles[i].textureIndices[c]].s / fDivisorU; - v = (float)pcTexCoords[pcTriangles[i].textureIndices[c]].t / fDivisorV; - pcOut.x = u; - pcOut.y = 1.0f - v; // FIXME: Is this correct for MD2? } // FIX: flip the face order for use with OpenGL pScene->mMeshes[0]->mFaces[i].mIndices[0] = iTemp+2; @@ -380,53 +381,10 @@ void MD2Importer::InternReadFile( pScene->mMeshes[0]->mFaces[i].mIndices[2] = iTemp+0; } } - else + catch (ImportErrorException* ex) { - for (unsigned int i = 0; i < (unsigned int)this->m_pcHeader->numTriangles;++i) - { - // allocate the face - pScene->mMeshes[0]->mFaces[i].mIndices = new unsigned int[3]; - pScene->mMeshes[0]->mFaces[i].mNumIndices = 3; - - // copy texture coordinates - // check whether they are different from the previous value at this index. - // In this case, create a full separate set of vertices/normals/texcoords - unsigned int iTemp = iCurrent; - for (unsigned int c = 0; c < 3;++c,++iCurrent) - { - // validate vertex indices - if (pcTriangles[i].vertexIndices[c] >= this->m_pcHeader->numVertices) - { - DefaultLogger::get()->error("Vertex index is outside the allowed range"); - pcTriangles[i].vertexIndices[c] = this->m_pcHeader->numVertices-1; - } - - // copy face indices - unsigned int iIndex = (unsigned int)pcTriangles[i].vertexIndices[c]; - - // read x,y, and z component of the vertex - aiVector3D& vec = pcMesh->mVertices[iCurrent]; - - vec.x = (float)pcVerts[iIndex].vertex[0] * pcFrame->scale[0]; - vec.x += pcFrame->translate[0]; - vec.z = (float)pcVerts[iIndex].vertex[1] * pcFrame->scale[1]; - vec.z += pcFrame->translate[1]; - vec.y = (float)pcVerts[iIndex].vertex[2] * pcFrame->scale[2]; - vec.y += pcFrame->translate[2]; - vec.y *= -1.f; - - // read the normal vector from the precalculated normal table - aiVector3D& vNormal = pcMesh->mNormals[iCurrent]; - LookupNormalIndex(pcVerts[iIndex].lightNormalIndex,vNormal); - vNormal.y *= -1.0f; - } - // FIX: flip the face order for use with OpenGL - pScene->mMeshes[0]->mFaces[i].mIndices[0] = iTemp+2; - pScene->mMeshes[0]->mFaces[i].mIndices[1] = iTemp+1; - pScene->mMeshes[0]->mFaces[i].mIndices[2] = iTemp+0; - } + delete[] this->mBuffer; AI_DEBUG_INVALIDATE_PTR(this->mBuffer); + throw ex; } - // delete the file buffer and return - delete[] this->mBuffer; - return; + delete[] this->mBuffer; AI_DEBUG_INVALIDATE_PTR(this->mBuffer); } \ No newline at end of file diff --git a/code/MD3Loader.cpp b/code/MD3Loader.cpp index 76ddb6129..5eb4e2494 100644 --- a/code/MD3Loader.cpp +++ b/code/MD3Loader.cpp @@ -90,11 +90,20 @@ bool MD3Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler) const // ------------------------------------------------------------------------------------------------ void MD3Importer::ValidateHeaderOffsets() { + // check file format version + if (this->m_pcHeader->VERSION > 15) + DefaultLogger::get()->warn( "Unsupported md3 file version. Continuing happily ..."); + + // check some values whether they are valid + if (0 == this->m_pcHeader->NUM_FRAMES) + throw new ImportErrorException( "Invalid md3 file: NUM_FRAMES is 0"); + if (0 == this->m_pcHeader->NUM_SURFACES) + throw new ImportErrorException( "Invalid md3 file: NUM_SURFACES is 0"); + if (this->m_pcHeader->OFS_FRAMES >= this->fileSize || this->m_pcHeader->OFS_SURFACES >= this->fileSize || this->m_pcHeader->OFS_EOF > this->fileSize) { - delete[] this->mBuffer; throw new ImportErrorException("Invalid MD3 header: some offsets are outside the file"); } } @@ -109,7 +118,6 @@ void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurf) pcSurf->OFS_ST + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::TexCoord) > this->fileSize || pcSurf->OFS_XYZNORMAL + ofs + pcSurf->NUM_VERTICES * sizeof(MD3::Vertex) > this->fileSize) { - delete[] this->mBuffer; throw new ImportErrorException("Invalid MD3 surface header: some offsets are outside the file"); } @@ -147,277 +155,259 @@ void MD3Importer::InternReadFile( this->mBuffer = new unsigned char[fileSize]; file->Read( (void*)mBuffer, 1, fileSize); - this->m_pcHeader = (const MD3::Header*)this->mBuffer; - - // check magic number - if (this->m_pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE && - this->m_pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE) + try { - delete[] this->mBuffer; - throw new ImportErrorException( "Invalid md3 file: Magic bytes not found"); - } - // check file format version - if (this->m_pcHeader->VERSION > 15) - { - DefaultLogger::get()->warn( "Unsupported md3 file version. Continuing happily ..."); - } + this->m_pcHeader = (const MD3::Header*)this->mBuffer; - // check some values whether they are valid - if (0 == this->m_pcHeader->NUM_FRAMES) - { - delete[] this->mBuffer; - throw new ImportErrorException( "Invalid md3 file: NUM_FRAMES is 0"); - } - if (0 == this->m_pcHeader->NUM_SURFACES) - { - delete[] this->mBuffer; - throw new ImportErrorException( "Invalid md3 file: NUM_SURFACES is 0"); - } - this->ValidateHeaderOffsets(); - - // now navigate to the list of surfaces - const MD3::Surface* pcSurfaces = (const MD3::Surface*) - (this->mBuffer + this->m_pcHeader->OFS_SURFACES); - - // allocate output storage - pScene->mNumMeshes = this->m_pcHeader->NUM_SURFACES; - pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; - - pScene->mNumMaterials = this->m_pcHeader->NUM_SURFACES; - pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes]; - - // if an exception is thrown before the meshes are allocated -> - // otherwise the pointer value would be invalid and delete would crash - ::memset(pScene->mMeshes,0,pScene->mNumMeshes*sizeof(aiMesh*)); - ::memset(pScene->mMaterials,0,pScene->mNumMaterials*sizeof(aiMaterial*)); - - unsigned int iNum = this->m_pcHeader->NUM_SURFACES; - unsigned int iNumMaterials = 0; - unsigned int iDefaultMatIndex = 0xFFFFFFFF; - while (iNum-- > 0) - { - // validate the surface - this->ValidateSurfaceHeaderOffsets(pcSurfaces); - - // navigate to the vertex list of the surface - const MD3::Vertex* pcVertices = (const MD3::Vertex*) - (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL); - - // navigate to the triangle list of the surface - const MD3::Triangle* pcTriangles = (const MD3::Triangle*) - (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_TRIANGLES); - - // navigate to the texture coordinate list of the surface - const MD3::TexCoord* pcUVs = (const MD3::TexCoord*) - (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_ST); - - // navigate to the shader list of the surface - const MD3::Shader* pcShaders = (const MD3::Shader*) - (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_SHADERS); - - // if the submesh is empty ignore it - if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES) + // check magic number + if (this->m_pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE && + this->m_pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE) { - pcSurfaces = (const MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END); - pScene->mNumMeshes--; - continue; + throw new ImportErrorException( "Invalid md3 file: Magic bytes not found"); } + // validate the header + this->ValidateHeaderOffsets(); - // allocate the output mesh - pScene->mMeshes[iNum] = new aiMesh(); - aiMesh* pcMesh = pScene->mMeshes[iNum]; + // now navigate to the list of surfaces + const MD3::Surface* pcSurfaces = (const MD3::Surface*) + (this->mBuffer + this->m_pcHeader->OFS_SURFACES); - pcMesh->mNumVertices = pcSurfaces->NUM_TRIANGLES*3; - //pcMesh->mNumBones = 0; - //pcMesh->mColors[0] = pcMesh->mColors[1] = pcMesh->mColors[2] = pcMesh->mColors[3] = NULL; - pcMesh->mNumFaces = pcSurfaces->NUM_TRIANGLES; - pcMesh->mFaces = new aiFace[pcSurfaces->NUM_TRIANGLES]; - pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; - pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; - pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; - //pcMesh->mTextureCoords[1] = pcMesh->mTextureCoords[2] = pcMesh->mTextureCoords[3] = NULL; - pcMesh->mNumUVComponents[0] = 2; + // allocate output storage + pScene->mNumMeshes = this->m_pcHeader->NUM_SURFACES; + pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; - // fill in all triangles - unsigned int iCurrent = 0; - for (unsigned int i = 0; i < (unsigned int)pcSurfaces->NUM_TRIANGLES;++i) + pScene->mNumMaterials = this->m_pcHeader->NUM_SURFACES; + pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes]; + + // if an exception is thrown before the meshes are allocated -> + // otherwise the pointer value would be invalid and delete would crash + ::memset(pScene->mMeshes,0,pScene->mNumMeshes*sizeof(aiMesh*)); + ::memset(pScene->mMaterials,0,pScene->mNumMaterials*sizeof(aiMaterial*)); + + unsigned int iNum = this->m_pcHeader->NUM_SURFACES; + unsigned int iNumMaterials = 0; + unsigned int iDefaultMatIndex = 0xFFFFFFFF; + while (iNum-- > 0) { - pcMesh->mFaces[i].mIndices = new unsigned int[3]; - pcMesh->mFaces[i].mNumIndices = 3; + // validate the surface + this->ValidateSurfaceHeaderOffsets(pcSurfaces); - unsigned int iTemp = iCurrent; - for (unsigned int c = 0; c < 3;++c,++iCurrent) + // navigate to the vertex list of the surface + const MD3::Vertex* pcVertices = (const MD3::Vertex*) + (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL); + + // navigate to the triangle list of the surface + const MD3::Triangle* pcTriangles = (const MD3::Triangle*) + (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_TRIANGLES); + + // navigate to the texture coordinate list of the surface + const MD3::TexCoord* pcUVs = (const MD3::TexCoord*) + (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_ST); + + // navigate to the shader list of the surface + const MD3::Shader* pcShaders = (const MD3::Shader*) + (((unsigned char*)pcSurfaces) + pcSurfaces->OFS_SHADERS); + + // if the submesh is empty ignore it + if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES) { - // read vertices - pcMesh->mVertices[iCurrent].x = pcVertices[ pcTriangles->INDEXES[c]].X; - pcMesh->mVertices[iCurrent].y = pcVertices[ pcTriangles->INDEXES[c]].Y*-1.0f; - pcMesh->mVertices[iCurrent].z = pcVertices[ pcTriangles->INDEXES[c]].Z; - - // convert the normal vector to uncompressed float3 format - LatLngNormalToVec3(pcVertices[pcTriangles->INDEXES[c]].NORMAL, - (float*)&pcMesh->mNormals[iCurrent]); - - pcMesh->mNormals[iCurrent].y *= -1.0f; - - // read texture coordinates - pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[ pcTriangles->INDEXES[c]].U; - pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-pcUVs[ pcTriangles->INDEXES[c]].V; - } - // FIX: flip the face ordering for use with OpenGL - pcMesh->mFaces[i].mIndices[0] = iTemp+2; - pcMesh->mFaces[i].mIndices[1] = iTemp+1; - pcMesh->mFaces[i].mIndices[2] = iTemp+0; - pcTriangles++; - } - - // get the first shader (= texture?) assigned to the surface - if (0 != pcSurfaces->NUM_SHADER) - { - // make a relative path. - // if the MD3's internal path itself and the given path are using - // the same directory remove it - const char* szEndDir1 = ::strrchr((const char*)this->m_pcHeader->NAME,'\\'); - if (!szEndDir1)szEndDir1 = ::strrchr((const char*)this->m_pcHeader->NAME,'/'); - - const char* szEndDir2 = ::strrchr((const char*)pcShaders->NAME,'\\'); - if (!szEndDir2)szEndDir2 = ::strrchr((const char*)pcShaders->NAME,'/'); - - if (szEndDir1 && szEndDir2) - { - // both of them are valid - const unsigned int iLen1 = (unsigned int)(szEndDir1 - (const char*)this->m_pcHeader->NAME); - const unsigned int iLen2 = std::min (iLen1, (unsigned int)(szEndDir2 - (const char*)pcShaders->NAME) ); - - bool bSuccess = true; - for (unsigned int a = 0; a < iLen2;++a) - { - char sz = tolower ( pcShaders->NAME[a] ); - char sz2 = tolower ( this->m_pcHeader->NAME[a] ); - if (sz != sz2) - { - bSuccess = false; - break; - } - } - if (bSuccess) - { - // use the file name only - szEndDir2++; - } - else - { - // use the full path - szEndDir2 = (const char*)pcShaders->NAME; - } + pcSurfaces = (const MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END); + pScene->mNumMeshes--; + continue; } - // now try to find out whether we have this shader already - bool bHave = false; - for (unsigned int p = 0; p < iNumMaterials;++p) - { - if (iDefaultMatIndex == p)continue; + // allocate the output mesh + pScene->mMeshes[iNum] = new aiMesh(); + aiMesh* pcMesh = pScene->mMeshes[iNum]; - aiString szOut; - if(AI_SUCCESS == aiGetMaterialString ( (aiMaterial*)pScene->mMaterials[p], - AI_MATKEY_TEXTURE_DIFFUSE(0),&szOut)) + pcMesh->mNumVertices = pcSurfaces->NUM_TRIANGLES*3; + pcMesh->mNumFaces = pcSurfaces->NUM_TRIANGLES; + pcMesh->mFaces = new aiFace[pcSurfaces->NUM_TRIANGLES]; + pcMesh->mNormals = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mVertices = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mTextureCoords[0] = new aiVector3D[pcMesh->mNumVertices]; + pcMesh->mNumUVComponents[0] = 2; + + // fill in all triangles + unsigned int iCurrent = 0; + for (unsigned int i = 0; i < (unsigned int)pcSurfaces->NUM_TRIANGLES;++i) + { + pcMesh->mFaces[i].mIndices = new unsigned int[3]; + pcMesh->mFaces[i].mNumIndices = 3; + + unsigned int iTemp = iCurrent; + for (unsigned int c = 0; c < 3;++c,++iCurrent) { - if (0 == ASSIMP_stricmp(szOut.data,szEndDir2)) - { - // equal. reuse this material (texture) - bHave = true; - pcMesh->mMaterialIndex = p; - break; - } + // read vertices + pcMesh->mVertices[iCurrent].x = pcVertices[ pcTriangles->INDEXES[c]].X; + pcMesh->mVertices[iCurrent].y = pcVertices[ pcTriangles->INDEXES[c]].Y*-1.0f; + pcMesh->mVertices[iCurrent].z = pcVertices[ pcTriangles->INDEXES[c]].Z; + + // convert the normal vector to uncompressed float3 format + LatLngNormalToVec3(pcVertices[pcTriangles->INDEXES[c]].NORMAL, + (float*)&pcMesh->mNormals[iCurrent]); + + pcMesh->mNormals[iCurrent].y *= -1.0f; + + // read texture coordinates + pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[ pcTriangles->INDEXES[c]].U; + pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-pcUVs[ pcTriangles->INDEXES[c]].V; } + // FIX: flip the face ordering for use with OpenGL + pcMesh->mFaces[i].mIndices[0] = iTemp+2; + pcMesh->mFaces[i].mIndices[1] = iTemp+1; + pcMesh->mFaces[i].mIndices[2] = iTemp+0; + pcTriangles++; } - if (!bHave) + // get the first shader (= texture?) assigned to the surface + if (0 != pcSurfaces->NUM_SHADER) { - MaterialHelper* pcHelper = new MaterialHelper(); + // make a relative path. + // if the MD3's internal path itself and the given path are using + // the same directory remove it + const char* szEndDir1 = ::strrchr((const char*)this->m_pcHeader->NAME,'\\'); + if (!szEndDir1)szEndDir1 = ::strrchr((const char*)this->m_pcHeader->NAME,'/'); - if (szEndDir2) + const char* szEndDir2 = ::strrchr((const char*)pcShaders->NAME,'\\'); + if (!szEndDir2)szEndDir2 = ::strrchr((const char*)pcShaders->NAME,'/'); + + if (szEndDir1 && szEndDir2) { - if (szEndDir2[0]) - { - aiString szString; - const size_t iLen = ::strlen(szEndDir2); - ::memcpy(szString.data,szEndDir2,iLen); - szString.data[iLen] = '\0'; - szString.length = iLen; + // both of them are valid + const unsigned int iLen1 = (unsigned int)(szEndDir1 - (const char*)this->m_pcHeader->NAME); + const unsigned int iLen2 = std::min (iLen1, (unsigned int)(szEndDir2 - (const char*)pcShaders->NAME) ); - pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); - } - else + bool bSuccess = true; + for (unsigned int a = 0; a < iLen2;++a) { - DefaultLogger::get()->warn("Texture file name has zero length. " - "It will be skipped."); + char sz = ::tolower ( pcShaders->NAME[a] ); + char sz2 = ::tolower ( this->m_pcHeader->NAME[a] ); + if (sz != sz2) + { + bSuccess = false; + break; + } + } + if (bSuccess) + { + // use the file name only + szEndDir2++; + } + else + { + // use the full path + szEndDir2 = (const char*)pcShaders->NAME; } } - int iMode = (int)aiShadingMode_Gouraud; - pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); + // now try to find out whether we have this shader already + bool bHave = false; + for (unsigned int p = 0; p < iNumMaterials;++p) + { + if (iDefaultMatIndex == p)continue; - // add a small ambient color value - Quake 3 seems to have one - aiColor3D clr; - clr.b = clr.g = clr.r = 0.05f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + aiString szOut; + if(AI_SUCCESS == aiGetMaterialString ( (aiMaterial*)pScene->mMaterials[p], + AI_MATKEY_TEXTURE_DIFFUSE(0),&szOut)) + { + if (0 == ASSIMP_stricmp(szOut.data,szEndDir2)) + { + // equal. reuse this material (texture) + bHave = true; + pcMesh->mMaterialIndex = p; + break; + } + } + } - aiString szName; - szName.Set(AI_DEFAULT_MATERIAL_NAME); - pcHelper->AddProperty(&szName,AI_MATKEY_NAME); + if (!bHave) + { + MaterialHelper* pcHelper = new MaterialHelper(); - pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper; - pcMesh->mMaterialIndex = iNumMaterials++; - } - } - else - { - if (0xFFFFFFFF != iDefaultMatIndex) - { - pcMesh->mMaterialIndex = iDefaultMatIndex; + if (szEndDir2) + { + if (szEndDir2[0]) + { + aiString szString; + const size_t iLen = ::strlen(szEndDir2); + ::memcpy(szString.data,szEndDir2,iLen); + szString.data[iLen] = '\0'; + szString.length = iLen; + + pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); + } + else + { + DefaultLogger::get()->warn("Texture file name has zero length. " + "It will be skipped."); + } + } + + int iMode = (int)aiShadingMode_Gouraud; + pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); + + // add a small ambient color value - Quake 3 seems to have one + aiColor3D clr; + clr.b = clr.g = clr.r = 0.05f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + + aiString szName; + szName.Set(AI_DEFAULT_MATERIAL_NAME); + pcHelper->AddProperty(&szName,AI_MATKEY_NAME); + + pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper; + pcMesh->mMaterialIndex = iNumMaterials++; + } } else { - MaterialHelper* pcHelper = new MaterialHelper(); + if (0xFFFFFFFF != iDefaultMatIndex) + { + pcMesh->mMaterialIndex = iDefaultMatIndex; + } + else + { + MaterialHelper* pcHelper = new MaterialHelper(); - // fill in a default material - int iMode = (int)aiShadingMode_Gouraud; - pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); + // fill in a default material + int iMode = (int)aiShadingMode_Gouraud; + pcHelper->AddProperty(&iMode, 1, AI_MATKEY_SHADING_MODEL); - aiColor3D clr; - clr.b = clr.g = clr.r = 0.6f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); + aiColor3D clr; + clr.b = clr.g = clr.r = 0.6f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_DIFFUSE); + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_SPECULAR); - clr.b = clr.g = clr.r = 0.05f; - pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); + clr.b = clr.g = clr.r = 0.05f; + pcHelper->AddProperty(&clr, 1,AI_MATKEY_COLOR_AMBIENT); - pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper; - pcMesh->mMaterialIndex = iNumMaterials++; + pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper; + pcMesh->mMaterialIndex = iNumMaterials++; + } } + // go to the next surface + pcSurfaces = (const MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END); } - // go to the next surface - pcSurfaces = (const MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END); - } - if (0 == pScene->mNumMeshes) + if (0 == pScene->mNumMeshes) + throw new ImportErrorException( "Invalid md3 file: File contains no valid mesh"); + pScene->mNumMaterials = iNumMaterials; + + // now we need to generate an empty node graph + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; + pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; + + for (unsigned int i = 0; i < pScene->mNumMeshes;++i) + pScene->mRootNode->mMeshes[i] = i; + + } + catch (ImportErrorException* ex) { - // cleanup before returning - delete[] this->mBuffer; - throw new ImportErrorException( "Invalid md3 file: File contains no valid mesh"); + delete[] this->mBuffer; AI_DEBUG_INVALIDATE_PTR(this->mBuffer); + throw ex; } - pScene->mNumMaterials = iNumMaterials; - - // now we need to generate an empty node graph - pScene->mRootNode = new aiNode(); - pScene->mRootNode->mNumMeshes = pScene->mNumMeshes; - pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes]; - - for (unsigned int i = 0; i < pScene->mNumMeshes;++i) - pScene->mRootNode->mMeshes[i] = i; - - // delete the file buffer and return - delete[] this->mBuffer; - return; + delete[] this->mBuffer; AI_DEBUG_INVALIDATE_PTR(this->mBuffer); } \ No newline at end of file diff --git a/code/MD5Loader.cpp b/code/MD5Loader.cpp new file mode 100644 index 000000000..b7edd6848 --- /dev/null +++ b/code/MD5Loader.cpp @@ -0,0 +1,561 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development Team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the ASSIMP team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the ASSIMP Development Team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/** @file Implementation of the MD5 importer class */ + +// internal headers +#include "MaterialSystem.h" +#include "RemoveComments.h" +#include "MD5Loader.h" +#include "StringComparison.h" +#include "fast_atof.h" + +// public headers +#include "../include/DefaultLogger.h" +#include "../include/IOStream.h" +#include "../include/IOSystem.h" +#include "../include/aiMesh.h" +#include "../include/aiScene.h" +#include "../include/aiAssert.h" + +// boost headers +#include + +using namespace Assimp; + +// we're just doing this with static buffers whose size is known at +// compile time, so the compiler should automatically expand to +// sprintf(...) + +#if _MSC_VER >= 1400 +# define sprintf sprintf_s +#endif + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +MD5Importer::MD5Importer() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +MD5Importer::~MD5Importer() +{ + // nothing to do here +} +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool MD5Importer::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); + + if (extension.length() < 4)return false; + if (extension[0] != '.')return false; + + if (extension[1] != 'm' && extension[1] != 'M')return false; + if (extension[2] != 'd' && extension[2] != 'D')return false; + if (extension[3] != '5')return false; + return true; +} +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void MD5Importer::InternReadFile( + const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) +{ + // remove the file extension + std::string::size_type pos = pFile.find_last_of('.'); + this->mFile = pFile.substr(0,pos+1); + this->pIOHandler = pIOHandler; + this->pScene = pScene; + + bHadMD5Mesh = bHadMD5Anim = false; + + // load the animation keyframes + this->LoadMD5AnimFile(); + + // load the mesh vertices and bones + this->LoadMD5MeshFile(); + + // make sure we return no incomplete data + if (!bHadMD5Mesh && !bHadMD5Anim) + { + throw new ImportErrorException("Failed to read valid data from this MD5"); + } + if (!bHadMD5Mesh)pScene->mFlags |= AI_SCENE_FLAGS_ANIM_SKELETON_ONLY; +} +// ------------------------------------------------------------------------------------------------ +void MD5Importer::LoadFileIntoMemory (IOStream* file) +{ + ai_assert(NULL != file); + + this->fileSize = (unsigned int)file->FileSize(); + + // allocate storage and copy the contents of the file to a memory buffer + this->pScene = pScene; + this->mBuffer = new char[this->fileSize+1]; + file->Read( (void*)mBuffer, 1, this->fileSize); + this->iLineNumber = 1; + + // append a terminal 0 + this->mBuffer[this->fileSize] = '\0'; + + // now remove all line comments from the file + CommentRemover::RemoveLineComments("//",this->mBuffer,' '); +} +// ------------------------------------------------------------------------------------------------ +void MD5Importer::UnloadFileFromMemory () +{ + // delete the file buffer + delete[] this->mBuffer; + this->mBuffer = NULL; + this->fileSize = 0; +} +// ------------------------------------------------------------------------------------------------ +void MakeDataUnique (MD5::MeshDesc& meshSrc) +{ + std::vector abHad(meshSrc.mVertices.size(),false); + + // allocate enough storage to keep the output structures + const unsigned int iNewNum = (unsigned int)meshSrc.mFaces.size()*3; + unsigned int iNewIndex = (unsigned int)meshSrc.mVertices.size(); + meshSrc.mVertices.resize(iNewNum); + + // try to guess how much storage we'll need for new weights + const float fWeightsPerVert = meshSrc.mWeights.size() / (float)iNewIndex; + const unsigned int guess = (unsigned int)(fWeightsPerVert*iNewNum); + meshSrc.mWeights.reserve(guess + (guess >> 3)); // + 12.5% as buffer + + for (FaceList::const_iterator iter = meshSrc.mFaces.begin(),iterEnd = meshSrc.mFaces.end(); + iter != iterEnd;++iter) + { + const aiFace& face = *iter; + for (unsigned int i = 0; i < 3;++i) + { + if (abHad[face.mIndices[i]]) + { + // generate a new vertex + meshSrc.mVertices[iNewIndex] = meshSrc.mVertices[face.mIndices[i]]; + face.mIndices[i] = iNewIndex++; + + // FIX: removed this ... +#if 0 + // the algorithm in MD5Importer::LoadMD5MeshFile() doesn't work if + // a weight is referenced by more than one vertex. This shouldn't + // occur in MD5 files, but we must take care that we generate new + // weights now, too. + + vertNew.mFirstWeight = (unsigned int)meshSrc.mWeights.size(); + meshSrc.mWeights.resize(vertNew.mFirstWeight+vertNew.mNumWeights); + for (unsigned int q = 0; q < vertNew.mNumWeights;++q) + { + meshSrc.mWeights[vertNew.mFirstWeight+q] = meshSrc.mWeights[vertOld.mFirstWeight+q]; + } +#endif + } + else abHad[face.mIndices[i]] = true; + } + } +} +// ------------------------------------------------------------------------------------------------ +void AttachChilds(int iParentID,aiNode* piParent,BoneList& bones) +{ + ai_assert(NULL != piParent && !piParent->mNumChildren); + for (unsigned int i = 0; i < bones.size();++i) + { + // (avoid infinite recursion) + if (iParentID != i && bones[i].mParentIndex == iParentID) + { + // have it ... + ++piParent->mNumChildren; + } + } + if (piParent->mNumChildren) + { + piParent->mChildren = new aiNode*[piParent->mNumChildren]; + for (unsigned int i = 0; i < bones.size();++i) + { + // (avoid infinite recursion) + if (iParentID != i && bones[i].mParentIndex == iParentID) + { + aiNode* pc; + *piParent->mChildren++ = pc = new aiNode(); + pc->mName = aiString(bones[i].mName); + pc->mParent = piParent; + + // get the transformation matrix from rotation and translational components + aiQuaternion quat = aiQuaternion ( bones[i].mRotationQuat ); + //quat.w *= -1.0f; // DX to OGL + pc->mTransformation = aiMatrix4x4 ( quat.GetMatrix()); + aiMatrix4x4 mTranslate; + mTranslate.a4 = bones[i].mPositionXYZ.x; + mTranslate.b4 = bones[i].mPositionXYZ.y; + mTranslate.c4 = bones[i].mPositionXYZ.z; + pc->mTransformation = pc->mTransformation*mTranslate; + + // store it for later use + bones[i].mTransform = bones[i].mInvTransform = pc->mTransformation; + bones[i].mInvTransform.Inverse(); + + // the transformations for each bone are absolute, + // so we need to multiply them with the inverse + // of the absolut matrix of the parent + if (-1 != iParentID) + { + pc->mTransformation = bones[iParentID].mInvTransform*pc->mTransformation; + } + + // add children to this node, too + AttachChilds( i, pc, bones); + } + } + // undo our nice shift + piParent->mChildren -= piParent->mNumChildren; + } +} +// ------------------------------------------------------------------------------------------------ +void MD5Importer::LoadMD5MeshFile () +{ + std::string pFile = this->mFile + "MD5MESH"; + boost::scoped_ptr file( pIOHandler->Open( pFile, "rb")); + + // Check whether we can read from the file + if( file.get() == NULL) + { + DefaultLogger::get()->warn("Failed to read MD5 mesh file: " + pFile); + return; + } + bHadMD5Mesh = true; + + // now load the file into memory + this->LoadFileIntoMemory(file.get()); + + // now construct a parser and parse the file + MD5::MD5Parser parser(mBuffer,fileSize); + + // load the mesh information from it + MD5::MD5MeshParser meshParser(parser.mSections); + + // create the bone hierarchy - first the root node + // and dummy nodes for all meshes + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mNumChildren = 2; + pScene->mRootNode->mChildren = new aiNode*[2]; + + aiNode* pcNode = pScene->mRootNode->mChildren[0] = new aiNode(); + pcNode->mNumMeshes = (unsigned int)meshParser.mMeshes.size(); + pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes]; + pcNode->mName.Set("MD5Mesh"); + pcNode->mParent = pScene->mRootNode; + for (unsigned int i = 0; i < pcNode->mNumMeshes;++i) + { + pcNode->mMeshes[i] = i; + } + + // now create the hierarchy of animated bones + pcNode = pScene->mRootNode->mChildren[1] = new aiNode(); + pcNode->mName.Set("MD5Anim"); + pcNode->mParent = pScene->mRootNode; + AttachChilds(-1,pcNode,meshParser.mJoints); + + // generate all meshes + pScene->mNumMeshes = pScene->mNumMaterials = (unsigned int)meshParser.mMeshes.size(); + pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; + pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes]; + for (unsigned int i = 0; i < pScene->mNumMeshes;++i) + { + aiMesh* mesh = pScene->mMeshes[i] = new aiMesh(); + MD5::MeshDesc& meshSrc = meshParser.mMeshes[i]; + + // generate unique vertices in our internal verbose format + MakeDataUnique(meshSrc); + + mesh->mNumVertices = (unsigned int) meshSrc.mVertices.size(); + mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; + mesh->mNumUVComponents[0] = 2; + + // copy texture coordinates + aiVector3D* pv = mesh->mTextureCoords[0]; + for (MD5::VertexList::const_iterator + iter = meshSrc.mVertices.begin(); + iter != meshSrc.mVertices.end();++iter,++pv) + { + pv->x = (*iter).mUV.x; + pv->y = 1.0f-(*iter).mUV.y; // D3D to OpenGL + pv->z = 0.0f; + } + + // sort all bone weights - per bone + unsigned int* piCount = new unsigned int[meshParser.mJoints.size()]; + ::memset(piCount,0,sizeof(unsigned int)*meshParser.mJoints.size()); + + for (MD5::VertexList::const_iterator + iter = meshSrc.mVertices.begin(); + iter != meshSrc.mVertices.end();++iter,++pv) + { + for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights;++w) + { + MD5::WeightDesc& desc = meshSrc.mWeights[w]; + ++piCount[desc.mBone]; + } + } + + // check how many we will need + for (unsigned int p = 0; p < meshParser.mJoints.size();++p) + if (piCount[p])mesh->mNumBones++; + + if (mesh->mNumBones) // just for safety + { + mesh->mBones = new aiBone*[mesh->mNumBones]; + for (unsigned int q = 0,h = 0; q < meshParser.mJoints.size();++q) + { + if (!piCount[q])continue; + aiBone* p = mesh->mBones[h] = new aiBone(); + p->mNumWeights = piCount[q]; + p->mWeights = new aiVertexWeight[p->mNumWeights]; + p->mName = aiString(meshParser.mJoints[q].mName); + + // store the index for later use + meshParser.mJoints[q].mMap = h++; + } + + unsigned int g = 0; + pv = mesh->mVertices; + for (MD5::VertexList::const_iterator + iter = meshSrc.mVertices.begin(); + iter != meshSrc.mVertices.end();++iter,++pv) + { + // compute the final vertex position from all single weights + *pv = aiVector3D(); + + // there are models which have weights which don't sum to 1 ... + // granite.md5mesh for example + float fSum = 0.0f; + for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights;++w) + fSum += meshSrc.mWeights[w].mWeight; + if (!fSum)throw new ImportErrorException("The sum of all vertex bone weights is 0"); + + // process bone weights + for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights;++w) + { + MD5::WeightDesc& desc = meshSrc.mWeights[w]; + float fNewWeight = desc.mWeight / fSum; + + // transform the local position into worldspace + MD5::BoneDesc& boneSrc = meshParser.mJoints[desc.mBone]; + aiVector3D v = desc.vOffsetPosition; + aiQuaternion quat = aiQuaternion( boneSrc.mRotationQuat ); + //quat.w *= -1.0f; + v = quat.GetMatrix() * v; + v += boneSrc.mPositionXYZ; + + // use the original weight to compute the vertex position + // (some MD5s seem to depend on the invalid weight values ...) + pv->operator +=(v * desc.mWeight); + + aiBone* bone = mesh->mBones[boneSrc.mMap]; + *bone->mWeights++ = aiVertexWeight((unsigned int)(pv-mesh->mVertices),fNewWeight); + } + // convert from DOOM coordinate system to OGL + std::swap(pv->z,pv->y); + } + + // undo our nice offset tricks ... + for (unsigned int p = 0; p < mesh->mNumBones;++p) + mesh->mBones[p]->mWeights -= mesh->mBones[p]->mNumWeights; + } + + delete[] piCount; + + // now setup all faces - we can directly copy the list + // (however, take care that the aiFace destructor doesn't delete the mIndices array) + mesh->mNumFaces = (unsigned int)meshSrc.mFaces.size(); + mesh->mFaces = new aiFace[mesh->mNumFaces]; + for (unsigned int c = 0; c < mesh->mNumFaces;++c) + { + mesh->mFaces[c].mNumIndices = 3; + mesh->mFaces[c].mIndices = meshSrc.mFaces[c].mIndices; + meshSrc.mFaces[c].mIndices = NULL; + } + + // generate a material for the mesh + MaterialHelper* mat = new MaterialHelper(); + pScene->mMaterials[i] = mat; + mat->AddProperty(&meshSrc.mShader,AI_MATKEY_TEXTURE_DIFFUSE(0)); + mesh->mMaterialIndex = i; + } + + // delete the file again + this->UnloadFileFromMemory(); +} +// ------------------------------------------------------------------------------------------------ +void MD5Importer::LoadMD5AnimFile () +{ + std::string pFile = this->mFile + "MD5ANIM"; + boost::scoped_ptr file( pIOHandler->Open( pFile, "rb")); + + // Check whether we can read from the file + if( file.get() == NULL) + { + DefaultLogger::get()->warn("Failed to read MD5 anim file: " + pFile); + return; + } + bHadMD5Anim = true; + + // now load the file into memory + this->LoadFileIntoMemory(file.get()); + + // now construct a parser and parse the file + MD5::MD5Parser parser(mBuffer,fileSize); + + // load the animation information from it + MD5::MD5AnimParser animParser(parser.mSections); + + // generate and fill the output animation + if (!animParser.mAnimatedBones.empty()) + { + pScene->mNumAnimations = 1; + pScene->mAnimations = new aiAnimation*[1]; + aiAnimation* anim = pScene->mAnimations[0] = new aiAnimation(); + anim->mNumBones = (unsigned int)animParser.mAnimatedBones.size(); + anim->mBones = new aiBoneAnim*[anim->mNumBones]; + for (unsigned int i = 0; i < anim->mNumBones;++i) + { + aiBoneAnim* bone = anim->mBones[i] = new aiBoneAnim(); + bone->mBoneName = aiString( animParser.mAnimatedBones[i].mName ); + + // allocate storage for the keyframes + bone->mNumPositionKeys = bone->mNumRotationKeys = (unsigned int)animParser.mFrames.size(); + bone->mPositionKeys = new aiVectorKey[bone->mNumPositionKeys]; + bone->mRotationKeys = new aiQuatKey[bone->mNumPositionKeys]; + } + + // 1 tick == 1 frame + anim->mTicksPerSecond = animParser.fFrameRate; + + for (FrameList::const_iterator iter = animParser.mFrames.begin(), + iterEnd = animParser.mFrames.end();iter != iterEnd;++iter) + { + double dTime = (double)(*iter).iIndex; + if (!(*iter).mValues.empty()) + { + // now process all values in there ... read all joints + aiBoneAnim** pcAnimBone = anim->mBones; + MD5::BaseFrameDesc* pcBaseFrame = &animParser.mBaseFrames[0]; + for (AnimBoneList::const_iterator + iter2 = animParser.mAnimatedBones.begin(), + iterEnd2 = animParser.mAnimatedBones.end(); + iter2 != iterEnd2;++iter2,++pcAnimBone,++pcBaseFrame) + { + if((*iter2).iFirstKeyIndex >= (*iter).mValues.size()) + { + // TODO: add proper array checks for all cases here ... + DefaultLogger::get()->error("Keyframe index is out of range. "); + continue; + } + const float* fpCur = &(*iter).mValues[(*iter2).iFirstKeyIndex]; + + aiBoneAnim* pcCurAnimBone = *pcAnimBone; + aiVectorKey* vKey = pcCurAnimBone->mPositionKeys++; + + // translation on the x axis + if ((*iter2).iFlags & AI_MD5_ANIMATION_FLAG_TRANSLATE_X) + vKey->mValue.x = *fpCur++; + else vKey->mValue.x = pcBaseFrame->vPositionXYZ.x; + + // translation on the y axis + if ((*iter2).iFlags & AI_MD5_ANIMATION_FLAG_TRANSLATE_Y) + vKey->mValue.y = *fpCur++; + else vKey->mValue.y = pcBaseFrame->vPositionXYZ.y; + + // translation on the z axis + if ((*iter2).iFlags & AI_MD5_ANIMATION_FLAG_TRANSLATE_Z) + vKey->mValue.z = *fpCur++; + else vKey->mValue.z = pcBaseFrame->vPositionXYZ.z; + + + // rotation quaternion, x component + aiQuatKey* qKey = pcCurAnimBone->mRotationKeys++; + aiVector3D vTemp; + if ((*iter2).iFlags & AI_MD5_ANIMATION_FLAG_ROTQUAT_X) + vTemp.x = *fpCur++; + else vTemp.x = pcBaseFrame->vRotationQuat.x; + + // rotation quaternion, y component + if ((*iter2).iFlags & AI_MD5_ANIMATION_FLAG_ROTQUAT_Y) + vTemp.y = *fpCur++; + else vTemp.y = pcBaseFrame->vRotationQuat.y; + + // rotation quaternion, z component + if ((*iter2).iFlags & AI_MD5_ANIMATION_FLAG_ROTQUAT_Z) + vTemp.z = *fpCur++; + else vTemp.z = pcBaseFrame->vRotationQuat.z; + + // compute the w component of the quaternion - invert it (DX to OGL) + qKey->mValue = aiQuaternion(vTemp); + //qKey->mValue.w *= -1.0f; + + qKey->mTime = dTime; + vKey->mTime = dTime; + } + } + // compute the duration of the animation + anim->mDuration = std::max(dTime,anim->mDuration); + } + + // undo our offset computations + for (unsigned int i = 0; i < anim->mNumBones;++i) + { + aiBoneAnim* bone = anim->mBones[i]; + bone->mPositionKeys -= bone->mNumPositionKeys; + bone->mRotationKeys -= bone->mNumPositionKeys; + } + } + + // delete the file again + this->UnloadFileFromMemory(); +} \ No newline at end of file diff --git a/code/MD5Loader.h b/code/MD5Loader.h index 01144e006..695c703c1 100644 --- a/code/MD5Loader.h +++ b/code/MD5Loader.h @@ -39,21 +39,21 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -/** @file Definition of the .MD5 importer class. */ +/** @file Definition of the .MD5 importer class. +http://www.modwiki.net/wiki/MD5_(file_format) +*/ #ifndef AI_MD5LOADER_H_INCLUDED #define AI_MD5LOADER_H_INCLUDED #include "BaseImporter.h" +#include "MD5Parser.h" + #include "../include/aiTypes.h" -struct aiNode; -#include "MD5FileData.h" +namespace Assimp { -namespace Assimp -{ -class MaterialHelper; - -using namespace MD5; +class IOStream; +using namespace Assimp::MD5; // --------------------------------------------------------------------------- /** Used to load MD5 files @@ -96,11 +96,57 @@ protected: protected: - /** Header of the MD5 file */ - const MD5::Header* m_pcHeader; + + // ------------------------------------------------------------------- + /** Load the *.MD5MESH file. + * Must be called at first. + */ + void LoadMD5MeshFile (); + + // ------------------------------------------------------------------- + /** Load the *.MD5ANIM file. + */ + void LoadMD5AnimFile (); + + // ------------------------------------------------------------------- + /** Load the contents of a specific file into memory and + * alocates a buffer to keep it. + * + * mBuffer is changed to point to this buffer. + * Don't forget to delete it later ... + * @param pFile File stream to be read + */ + void LoadFileIntoMemory (IOStream* pFile); + void UnloadFileFromMemory (); + + + /** IOSystem to be used to access files */ + IOSystem* mIOHandler; + + /** Path to the file, excluding the file extension but + with the dot */ + std::string mFile; /** Buffer to hold the loaded file */ - const unsigned char* mBuffer; + char* mBuffer; + + /** Size of the file */ + unsigned int fileSize; + + /** Current line number. For debugging purposes */ + unsigned int iLineNumber; + + /** Scene to be filled */ + aiScene* pScene; + + /** (Custom) I/O handler implementation */ + IOSystem* pIOHandler; + + /** true if the MD5MESH file has already been parsed */ + bool bHadMD5Mesh; + + /** true if the MD5ANIM file has already been parsed */ + bool bHadMD5Anim; }; } // end of namespace Assimp diff --git a/code/MD5Parser.cpp b/code/MD5Parser.cpp new file mode 100644 index 000000000..f9796a595 --- /dev/null +++ b/code/MD5Parser.cpp @@ -0,0 +1,545 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development Team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the ASSIMP team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the ASSIMP Development Team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/** @file Implementation of the MD5 parser class */ + +// internal headers +#include "MD5Loader.h" +#include "MaterialSystem.h" +#include "fast_atof.h" +#include "ParsingUtils.h" +#include "StringComparison.h" + +// public ASSIMP headers +#include "../include/DefaultLogger.h" +#include "../include/IOStream.h" +#include "../include/IOSystem.h" +#include "../include/aiMesh.h" +#include "../include/aiScene.h" +#include "../include/aiAssert.h" + +using namespace Assimp; +using namespace Assimp::MD5; + +#if _MSC_VER >= 1400 +# define sprintf sprintf_s +#endif + +// ------------------------------------------------------------------------------------------------ +MD5Parser::MD5Parser(char* buffer, unsigned int fileSize) +{ + ai_assert(NULL != buffer && 0 != fileSize); + + this->buffer = buffer; + this->fileSize = fileSize; + this->lineNumber = 0; + + DefaultLogger::get()->debug("MD5Parser begin"); + + // parse the file header + this->ParseHeader(); + + // and read all sections until we're finished + while (true) + { + this->mSections.push_back(Section()); + Section& sec = this->mSections.back(); + if(!this->ParseSection(sec)) + { + break; + } + } + + if ( !DefaultLogger::isNullLogger()) + { + char szBuffer[128]; // should be sufficiently large + ::sprintf(szBuffer,"MD5Parser end. Parsed %i sections",this->mSections.size()); + DefaultLogger::get()->debug(szBuffer); + } +} +// ------------------------------------------------------------------------------------------------ +/*static*/ void MD5Parser::ReportError (char* error, unsigned int line) +{ + char szBuffer[1024]; // you, listen to me, you HAVE TO BE sufficiently large + ::sprintf(szBuffer,"Line %i: %s",line,error); + throw new ImportErrorException(szBuffer); +} +// ------------------------------------------------------------------------------------------------ +/*static*/ void MD5Parser::ReportWarning (char* warn, unsigned int line) +{ + char szBuffer[1024]; // you, listen to me, you HAVE TO BE sufficiently large + ::sprintf(szBuffer,"Line %i: %s",line,warn); + DefaultLogger::get()->warn(szBuffer); +} +// ------------------------------------------------------------------------------------------------ +void MD5Parser::ParseHeader() +{ + // parse and validate the file version + SkipSpaces(); + if (0 != ASSIMP_strincmp(buffer,"MD5Version",10) || + !IsSpace(*(buffer+=10))) + { + this->ReportError("Invalid MD5 file: MD5Version tag has not been found"); + } + SkipSpaces(); + unsigned int iVer = ::strtol10(buffer,(const char**)&buffer); + if (10 != iVer) + { + this->ReportWarning("MD5 version tag is unknown (10 is expected)"); + } + this->SkipLine(); + + // print the command line options to the console + char* sz = buffer; + while (!IsLineEnd( *buffer++)); + DefaultLogger::get()->info(std::string(sz,(uintptr_t)(buffer-sz))); + this->SkipSpacesAndLineEnd(); +} +// ------------------------------------------------------------------------------------------------ +bool MD5Parser::ParseSection(Section& out) +{ + // store the current line number for use in error messages + out.iLineNumber = this->lineNumber; + + // first parse the name of the section + char* sz = buffer; + while (!IsSpaceOrNewLine( *buffer))buffer++; + out.mName = std::string(sz,(uintptr_t)(buffer-sz)); + SkipSpaces(); + + while (true) + { + if ('{' == *buffer) + { + // it is a normal section so read all lines + buffer++; + while (true) + { + if (!SkipSpacesAndLineEnd()) + { + return false; // seems this was the last section + } + if ('}' == *buffer) + { + buffer++; + break; + } + + out.mElements.push_back(Element()); + Element& elem = out.mElements.back(); + + elem.iLineNumber = lineNumber; + elem.szStart = buffer; + + // terminate the line with zero - remove all spaces at the end + while (!IsLineEnd( *buffer))buffer++; + //const char* end = buffer; + do {buffer--;} + while (IsSpace(*buffer)); + buffer++; + *buffer++ = '\0'; + //if (*end) ++lineNumber; + } + break; + } + else if (!IsSpaceOrNewLine(*buffer)) + { + // it is an element at global scope. Parse its value and go on + // FIX: for MD5ANIm files - frame 0 {...} is allowed + sz = buffer; + while (!IsSpaceOrNewLine( *buffer++)); + out.mGlobalValue = std::string(sz,(uintptr_t)(buffer-sz)); + continue; + } + break; + } + return SkipSpacesAndLineEnd(); +} +// ------------------------------------------------------------------------------------------------ + +// skip all spaces ... handle EOL correctly +#define AI_MD5_SKIP_SPACES() if(!SkipSpaces(&sz)) \ + MD5Parser::ReportWarning("Unexpected end of line",(*eit).iLineNumber); + + // read a triple float in brackets: (1.0 1.0 1.0) +#define AI_MD5_READ_TRIPLE(vec) \ + AI_MD5_SKIP_SPACES(); \ + if ('(' != *sz++) \ + MD5Parser::ReportWarning("Unexpected token: ( was expected",(*eit).iLineNumber); \ + AI_MD5_SKIP_SPACES(); \ + sz = fast_atof_move(sz,vec.x); \ + AI_MD5_SKIP_SPACES(); \ + sz = fast_atof_move(sz,vec.y); \ + AI_MD5_SKIP_SPACES(); \ + sz = fast_atof_move(sz,vec.z); \ + AI_MD5_SKIP_SPACES(); \ + if (')' != *sz++) \ + MD5Parser::ReportWarning("Unexpected token: ) was expected",(*eit).iLineNumber); + + // parse a string, enclosed in quotation marks or not +#define AI_MD5_PARSE_STRING(out) \ + bool bQuota = *sz == '\"'; \ + const char* szStart = sz; \ + while (!IsSpaceOrNewLine(*sz))++sz; \ + const char* szEnd = sz; \ + if (bQuota) \ + { \ + szStart++; \ + if ('\"' != *(szEnd-=1)) \ + { \ + MD5Parser::ReportWarning("Expected closing quotation marks in string", \ + (*eit).iLineNumber); \ + } \ + } \ + out.length = (size_t)(szEnd - szStart); \ + ::memcpy(out.data,szStart,out.length); \ + out.data[out.length] = '\0'; + +// ------------------------------------------------------------------------------------------------ +MD5MeshParser::MD5MeshParser(SectionList& mSections) +{ + DefaultLogger::get()->debug("MD5MeshParser begin"); + + // now parse all sections + for (SectionList::const_iterator + iter = mSections.begin(), iterEnd = mSections.end(); + iter != iterEnd;++iter) + { + if ((*iter).mGlobalValue.length()) + { + if ( !::strcmp("numMeshes",(*iter).mName.c_str())) + { + unsigned int iNumMeshes; + if(iNumMeshes = ::strtol10((*iter).mGlobalValue.c_str())) + { + mMeshes.reserve(iNumMeshes); + } + } + else if ( !::strcmp("numJoints",(*iter).mName.c_str())) + { + unsigned int iNumJoints; + if(iNumJoints = ::strtol10((*iter).mGlobalValue.c_str())) + { + mJoints.reserve(iNumJoints); + } + } + } + else if (!::strcmp("joints",(*iter).mName.c_str())) + { + // now read all elements + // "origin" -1 ( -0.000000 0.016430 -0.006044 ) ( 0.707107 0.000000 0.707107 ) + for (ElementList::const_iterator + eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end(); + eit != eitEnd; ++eit) + { + mJoints.push_back(BoneDesc()); + BoneDesc& desc = mJoints.back(); + + const char* sz = (*eit).szStart; + + AI_MD5_PARSE_STRING(desc.mName); + AI_MD5_SKIP_SPACES(); + + // negative values can occur here ... + bool bNeg = false; + if ('-' == *sz){sz++;bNeg = true;} + else if ('+' == *sz){sz++;} + desc.mParentIndex = (int)::strtol10(sz,&sz); + if (bNeg)desc.mParentIndex *= -1; + + AI_MD5_READ_TRIPLE(desc.mPositionXYZ); + AI_MD5_READ_TRIPLE(desc.mRotationQuat); // normalized quaternion, so w is not there + } + } + else if (!::strcmp("mesh",(*iter).mName.c_str())) + { + mMeshes.push_back(MeshDesc()); + MeshDesc& desc = mMeshes.back(); + + // now read all elements + for (ElementList::const_iterator + eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end(); + eit != eitEnd; ++eit) + { + const char* sz = (*eit).szStart; + + // shader attribute + if (!ASSIMP_strincmp(sz,"shader",6) && + IsSpaceOrNewLine(*(sz+=6)++)) + { + // don't expect quotation marks + AI_MD5_SKIP_SPACES(); + AI_MD5_PARSE_STRING(desc.mShader); + } + // numverts attribute + else if (!ASSIMP_strincmp(sz,"numverts",8) && + IsSpaceOrNewLine(*(sz+=8)++)) + { + // reserve enough storage + AI_MD5_SKIP_SPACES(); + unsigned int iNumVertices; + if(iNumVertices = ::strtol10(sz)) + desc.mVertices.resize(iNumVertices); + } + // numtris attribute + else if (!ASSIMP_strincmp(sz,"numtris",7) && + IsSpaceOrNewLine(*(sz+=7)++)) + { + // reserve enough storage + AI_MD5_SKIP_SPACES(); + unsigned int iNumTris; + if(iNumTris = ::strtol10(sz)) + desc.mFaces.resize(iNumTris); + } + // numweights attribute + else if (!ASSIMP_strincmp(sz,"numweights",10) && + IsSpaceOrNewLine(*(sz+=10)++)) + { + // reserve enough storage + AI_MD5_SKIP_SPACES(); + unsigned int iNumWeights; + if(iNumWeights = ::strtol10(sz)) + desc.mWeights.resize(iNumWeights); + } + // vert attribute + // "vert 0 ( 0.394531 0.513672 ) 0 1" + else if (!ASSIMP_strincmp(sz,"vert",4) && + IsSpaceOrNewLine(*(sz+=4)++)) + { + AI_MD5_SKIP_SPACES(); + unsigned int idx = ::strtol10(sz,&sz); + AI_MD5_SKIP_SPACES(); + if (idx >= desc.mVertices.size()) + desc.mVertices.resize(idx+1); + + VertexDesc& vert = desc.mVertices[idx]; + if ('(' != *sz++) + MD5Parser::ReportWarning("Unexpected token: ( was expected",(*eit).iLineNumber); + AI_MD5_SKIP_SPACES(); + sz = fast_atof_move(sz,vert.mUV.x); + AI_MD5_SKIP_SPACES(); + sz = fast_atof_move(sz,vert.mUV.y); + AI_MD5_SKIP_SPACES(); + if (')' != *sz++) + MD5Parser::ReportWarning("Unexpected token: ) was expected",(*eit).iLineNumber); + AI_MD5_SKIP_SPACES(); + vert.mFirstWeight = ::strtol10(sz,&sz); + AI_MD5_SKIP_SPACES(); + vert.mNumWeights = ::strtol10(sz,&sz); + } + // tri attribute + // "tri 0 15 13 12" + else if (!ASSIMP_strincmp(sz,"tri",3) && + IsSpaceOrNewLine(*(sz+=3)++)) + { + AI_MD5_SKIP_SPACES(); + unsigned int idx = ::strtol10(sz,&sz); + if (idx >= desc.mFaces.size()) + desc.mFaces.resize(idx+1); + + aiFace& face = desc.mFaces[idx]; + face.mIndices = new unsigned int[face.mNumIndices = 3]; + for (unsigned int i = 0; i < 3;++i) + { + AI_MD5_SKIP_SPACES(); + face.mIndices[i] = ::strtol10(sz,&sz); + } + } + // weight attribute + // "weight 362 5 0.500000 ( -3.553583 11.893474 9.719339 )" + else if (!ASSIMP_strincmp(sz,"weight",6) && + IsSpaceOrNewLine(*(sz+=6)++)) + { + AI_MD5_SKIP_SPACES(); + unsigned int idx = ::strtol10(sz,&sz); + AI_MD5_SKIP_SPACES(); + if (idx >= desc.mWeights.size()) + desc.mWeights.resize(idx+1); + + WeightDesc& weight = desc.mWeights[idx]; + weight.mBone = ::strtol10(sz,&sz); + AI_MD5_SKIP_SPACES(); + sz = fast_atof_move(sz,weight.mWeight); + AI_MD5_READ_TRIPLE(weight.vOffsetPosition); + } + } + } + } + DefaultLogger::get()->debug("MD5MeshParser end"); +} + +// ------------------------------------------------------------------------------------------------ +MD5AnimParser::MD5AnimParser(SectionList& mSections) +{ + DefaultLogger::get()->debug("MD5AnimParser begin"); + + fFrameRate = 24.0f; + mNumAnimatedComponents = 0xffffffff; + + // now parse all sections + for (SectionList::const_iterator + iter = mSections.begin(), iterEnd = mSections.end(); + iter != iterEnd;++iter) + { + if (!::strcmp("hierarchy",(*iter).mName.c_str())) + { + // now read all elements + // "sheath" 0 63 6 + for (ElementList::const_iterator + eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end(); + eit != eitEnd; ++eit) + { + mAnimatedBones.push_back ( AnimBoneDesc () ); + AnimBoneDesc& desc = mAnimatedBones.back(); + + const char* sz = (*eit).szStart; + AI_MD5_PARSE_STRING(desc.mName); + AI_MD5_SKIP_SPACES(); + + // parent index + // negative values can occur here ... + bool bNeg = false; + if ('-' == *sz){sz++;bNeg = true;} + else if ('+' == *sz){sz++;} + desc.mParentIndex = (int)::strtol10(sz,&sz); + if (bNeg)desc.mParentIndex *= -1; + + // flags (highest is 2^6-1) + AI_MD5_SKIP_SPACES(); + if(63 < (desc.iFlags = ::strtol10(sz,&sz))) + { + MD5Parser::ReportWarning("Invalid flag combination in hierarchy section", + (*eit).iLineNumber); + } + AI_MD5_SKIP_SPACES(); + + // index of the first animation keyframe component for this joint + desc.iFirstKeyIndex = ::strtol10(sz,&sz); + } + } + else if(!::strcmp("baseframe",(*iter).mName.c_str())) + { + // now read all elements + // ( -0.000000 0.016430 -0.006044 ) ( 0.707107 0.000242 0.707107 ) + for (ElementList::const_iterator + eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end(); + eit != eitEnd; ++eit) + { + const char* sz = (*eit).szStart; + + mBaseFrames.push_back ( BaseFrameDesc () ); + BaseFrameDesc& desc = mBaseFrames.back(); + + AI_MD5_READ_TRIPLE(desc.vPositionXYZ); + AI_MD5_READ_TRIPLE(desc.vRotationQuat); + } + } + else if(!::strcmp("frame",(*iter).mName.c_str())) + { + if (!(*iter).mGlobalValue.length()) + { + MD5Parser::ReportWarning("A frame section must have a frame index", + (*iter).iLineNumber); + continue; + } + + mFrames.push_back ( FrameDesc () ); + FrameDesc& desc = mFrames.back(); + desc.iIndex = ::strtol10((*iter).mGlobalValue.c_str()); + + // we do already know how much storage we will presumably need + if (0xffffffff != mNumAnimatedComponents) + desc.mValues.reserve(mNumAnimatedComponents); + + // now read all elements + // (continous list of float values) + for (ElementList::const_iterator + eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end(); + eit != eitEnd; ++eit) + { + const char* sz = (*eit).szStart; + while (SkipSpaces(sz,&sz)) + { + float f; + sz = fast_atof_move(sz,f); + desc.mValues.push_back(f); + } + } + } + else if(!::strcmp("numFrames",(*iter).mName.c_str())) + { + unsigned int iNum; + if(iNum = ::strtol10((*iter).mGlobalValue.c_str())) + { + mFrames.reserve(iNum); + } + } + else if(!::strcmp("numJoints",(*iter).mName.c_str())) + { + unsigned int iNum; + if(iNum = ::strtol10((*iter).mGlobalValue.c_str())) + { + mAnimatedBones.reserve(iNum); + + // try to guess the number of animated components if that element is not given + if (0xffffffff == mNumAnimatedComponents) + mNumAnimatedComponents = iNum * 6; + } + } + else if(!::strcmp("numAnimatedComponents",(*iter).mName.c_str())) + { + unsigned int iNum; + if(iNum = ::strtol10((*iter).mGlobalValue.c_str())) + { + mAnimatedBones.reserve(iNum); + } + } + else if(!::strcmp("frameRate",(*iter).mName.c_str())) + { + fast_atof_move((*iter).mGlobalValue.c_str(),this->fFrameRate); + } + } + DefaultLogger::get()->debug("MD5AnimParser end"); +} + +#undef AI_MD5_SKIP_SPACES +#undef AI_MD5_READ_TRIPLE +#undef AI_MD5_PARSE_STRING diff --git a/code/MD5Parser.h b/code/MD5Parser.h new file mode 100644 index 000000000..a0e1dd8c0 --- /dev/null +++ b/code/MD5Parser.h @@ -0,0 +1,419 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development Team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the ASSIMP team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the ASSIMP Development Team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + + +/** @file Definition of the .MD5 parser class. +http://www.modwiki.net/wiki/MD5_(file_format) +*/ +#ifndef AI_MD5PARSER_H_INCLUDED +#define AI_MD5PARSER_H_INCLUDED + +#include "../include/aiTypes.h" +#include "ParsingUtils.h" +#include + +struct aiFace; + +namespace Assimp { +namespace MD5 { + + +// --------------------------------------------------------------------------- +/** Represents a single element in a MD5 file + * + * Elements are always contained in sections. +*/ +struct Element +{ + //! Points to the starting point of the element + //! Whitespace at the beginning and at the end have been removed, + //! Elements are terminated with \0 + char* szStart; + + //! Original line number (can be used in error messages + //! if a parsing error occurs) + unsigned int iLineNumber; +}; + +typedef std::vector< Element > ElementList; + +// --------------------------------------------------------------------------- +/** Represents a section of a MD5 file (such as the mesh or the joints section) + * + * A section is always enclosed in { and } brackets. +*/ +struct Section +{ + //! Original line number (can be used in error messages + //! if a parsing error occurs) + unsigned int iLineNumber; + + //! List of all elements which have been parsed in this section. + ElementList mElements; + + //! Name of the section + std::string mName; + + //! For global elements: the value of the element as string + //! Iif !length() the section is not a global element + std::string mGlobalValue; +}; + +typedef std::vector< Section> SectionList; + +// --------------------------------------------------------------------------- +/** Represents a bone (joint) descriptor in a MD5Mesh file +*/ +struct BoneDesc +{ + //! Name of the bone + aiString mName; + + //! Parent index of the bone + int mParentIndex; + + //! Relative position of the bone + aiVector3D mPositionXYZ; + + //! Relative rotation of the bone + aiVector3D mRotationQuat; + + //! Absolute transformation of the bone + //! (temporary) + aiMatrix4x4 mTransform; + + //! Inverse transformation of the bone + //! (temporary) + aiMatrix4x4 mInvTransform; + + //! Internal + unsigned int mMap; +}; + +typedef std::vector< BoneDesc > BoneList; + +// --------------------------------------------------------------------------- +/** Represents a bone (joint) descriptor in a MD5Anim file +*/ +struct AnimBoneDesc +{ + //! Name of the bone + aiString mName; + + //! Parent index of the bone + int mParentIndex; + + //! Flags (AI_MD5_ANIMATION_FLAG_xxx) + unsigned int iFlags; + + //! Index of the first key that corresponds to this anim bone + unsigned int iFirstKeyIndex; +}; + +typedef std::vector< AnimBoneDesc > AnimBoneList; + + +// --------------------------------------------------------------------------- +/** Represents a base frame descriptor in a MD5Anim file +*/ +struct BaseFrameDesc +{ + aiVector3D vPositionXYZ; + aiVector3D vRotationQuat; +}; + +typedef std::vector< BaseFrameDesc > BaseFrameList; + + +// --------------------------------------------------------------------------- +/** Represents a frame descriptor in a MD5Anim file +*/ +struct FrameDesc +{ + //! Index of the frame + unsigned int iIndex; + + //! Animation keyframes - a large blob of data at first + std::vector< float > mValues; +}; + +typedef std::vector< FrameDesc > FrameList; + +// --------------------------------------------------------------------------- +/** Represents a vertex descriptor in a MD5 file +*/ +struct VertexDesc +{ + VertexDesc() + : mFirstWeight (0) + , mNumWeights (0) + {} + + //! UV cordinate of the vertex + aiVector2D mUV; + + //! Index of the first weight of the vertex in + //! the vertex weight list + unsigned int mFirstWeight; + + //! Number of weights assigned to this vertex + unsigned int mNumWeights; +}; + +typedef std::vector< VertexDesc > VertexList; + +// --------------------------------------------------------------------------- +/** Represents a vertex weight descriptor in a MD5 file +*/ +struct WeightDesc +{ + //! Index of the bone to which this weight refers + unsigned int mBone; + + //! The weight value + float mWeight; + + //! The offset position of this weight + // ! (in the coordinate system defined by the parent bone) + aiVector3D vOffsetPosition; +}; + +typedef std::vector< WeightDesc > WeightList; +typedef std::vector< aiFace > FaceList; + +// --------------------------------------------------------------------------- +/** Represents a mesh in a MD5 file +*/ +struct MeshDesc +{ + //! Weights of the mesh + WeightList mWeights; + + //! Vertices of the mesh + VertexList mVertices; + + //! Faces of the mesh + FaceList mFaces; + + //! Name of the shader (=texture) to be assigned to the mesh + aiString mShader; +}; + +typedef std::vector< MeshDesc > MeshList; + +// --------------------------------------------------------------------------- +/** Parses the data sections of a MD5 mesh file +*/ +class MD5MeshParser +{ +public: + + // ------------------------------------------------------------------- + /** Constructs a new MD5MeshParser instance from an existing + * preparsed list of file sections. + * + * @param mSections List of file sections (output of MD5Parser) + */ + MD5MeshParser(SectionList& mSections); + + //! List of all meshes + MeshList mMeshes; + + //! List of all joints + BoneList mJoints; +}; + +#define AI_MD5_ANIMATION_FLAG_TRANSLATE_X 0x1 +#define AI_MD5_ANIMATION_FLAG_TRANSLATE_Y 0x2 +#define AI_MD5_ANIMATION_FLAG_TRANSLATE_Z 0x4 + +#define AI_MD5_ANIMATION_FLAG_ROTQUAT_X 0x8 +#define AI_MD5_ANIMATION_FLAG_ROTQUAT_Y 0x10 +#define AI_MD5_ANIMATION_FLAG_ROTQUAT_Z 0x12 + +// remove this flag if you need to the bounding box data +#define AI_MD5_PARSE_NO_BOUNDS + +// --------------------------------------------------------------------------- +/** Parses the data sections of a MD5 animation file +*/ +class MD5AnimParser +{ +public: + + // ------------------------------------------------------------------- + /** Constructs a new MD5AnimParser instance from an existing + * preparsed list of file sections. + * + * @param mSections List of file sections (output of MD5Parser) + */ + MD5AnimParser(SectionList& mSections); + + + //! Output frame rate + float fFrameRate; + + //! List of animation bones + AnimBoneList mAnimatedBones; + + //! List of base frames + BaseFrameList mBaseFrames; + + //! List of animation frames + FrameList mFrames; + + //! Number of animated components + unsigned int mNumAnimatedComponents; +}; + +// --------------------------------------------------------------------------- +/** Parses the block structure of MD5MESH and MD5ANIM files (but does no + * further processing) +*/ +class MD5Parser +{ +public: + + // ------------------------------------------------------------------- + /** Constructs a new MD5Parser instance from an existing buffer. + * + * @param buffer File buffer + * @param fileSize Length of the file in bytes (excluding a terminal 0) + */ + MD5Parser(char* buffer, unsigned int fileSize); + + + // ------------------------------------------------------------------- + /** Report a specific error message and throw an exception + * @param error Error message to be reported + * @param line Index of the line where the error occured + */ + static void ReportError (char* error, unsigned int line); + + // ------------------------------------------------------------------- + /** Report a specific warning + * @param warn Warn message to be reported + * @param line Index of the line where the error occured + */ + static void ReportWarning (char* warn, unsigned int line); + + + inline void ReportError (char* error) + {return ReportError(error, this->lineNumber);} + + inline void ReportWarning (char* warn) + {return ReportWarning(warn, this->lineNumber);} + + +public: + + //! List of all sections which have been read + SectionList mSections; + +private: + + // ------------------------------------------------------------------- + /** Parses a file section. The current file pointer must be outside + * of a section. + * @param out Receives the section data + * @return true if the end of the file has been reached + * @throws ImportErrorException if an error occurs + */ + bool ParseSection(Section& out); + + // ------------------------------------------------------------------- + /** Parses the file header + * @throws ImportErrorException if an error occurs + */ + void ParseHeader(); + + + // override these functions to make sure the line counter gets incremented + // ------------------------------------------------------------------- + inline bool SkipLine( const char* in, const char** out) + { + ++lineNumber; + return ::SkipLine(in,out); + } + // ------------------------------------------------------------------- + inline bool SkipLine( ) + { + return SkipLine(buffer,(const char**)&buffer); + } + // ------------------------------------------------------------------- + inline bool SkipSpacesAndLineEnd( const char* in, const char** out) + { + bool bHad = false; + while (true) + { + if( *in == '\r' || *in == '\n') + { + if (!bHad) // we open files in binary mode, so there could be \r\n sequences ... + { + bHad = true; + ++lineNumber; + } + } + else if (*in == '\t' || *in == ' ')bHad = false; + else break; + in++; + } + + *out = in; + return *in != '\0'; + } + // ------------------------------------------------------------------- + inline bool SkipSpacesAndLineEnd( ) + { + return SkipSpacesAndLineEnd(buffer,(const char**)&buffer); + } + // ------------------------------------------------------------------- + inline bool SkipSpaces( ) + { + return ::SkipSpaces((const char**)&buffer); + } + + char* buffer; + unsigned int fileSize; + unsigned int lineNumber; +}; +}} + +#endif // AI_MD5PARSER_H_INCLUDED \ No newline at end of file diff --git a/code/MDLLoader.cpp b/code/MDLLoader.cpp index f2ea2df28..fdf779abf 100644 --- a/code/MDLLoader.cpp +++ b/code/MDLLoader.cpp @@ -55,6 +55,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "../include/aiMesh.h" #include "../include/aiScene.h" #include "../include/aiAssert.h" +#include "../include/assimp.hpp" // boost headers #include @@ -127,6 +128,17 @@ void MDLImporter::InternReadFile( throw new ImportErrorException( "Failed to open MDL file " + pFile + "."); } + // The AI_CONFIG_IMPORT_MDL_KEYFRAME option overrides the + // AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option. +#if 0 + if(0xffffffff == (this->configFrameID = this->mImporter->GetProperty( + AI_CONFIG_IMPORT_MDL_KEYFRAME,0xffffffff))) + { + this->configFrameID = this->mImporter->GetProperty( + AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0); + } +#endif + // this should work for all other types of MDL files, too ... // the quake header is one of the smallest, afaik this->iFileSize = (unsigned int)file->FileSize(); @@ -236,17 +248,11 @@ void MDLImporter::InternReadFile( throw ex; } - // make sure that the normals are facing outwards - // (mainly this applies to MDL7 (due to the FlipNormals option in MED). - // However there are some invalid models in other format, too) - for (unsigned int i = 0; i < pScene->mNumMeshes;++i) - this->FlipNormals(pScene->mMeshes[i]); - // delete the file buffer and cleanup delete[] this->mBuffer; - DEBUG_INVALIDATE_PTR(this->mBuffer); - DEBUG_INVALIDATE_PTR(this->pIOHandler); - DEBUG_INVALIDATE_PTR(this->pScene); + AI_DEBUG_INVALIDATE_PTR(this->mBuffer); + AI_DEBUG_INVALIDATE_PTR(this->pIOHandler); + AI_DEBUG_INVALIDATE_PTR(this->pScene); return; } // ------------------------------------------------------------------------------------------------ @@ -254,7 +260,7 @@ void MDLImporter::SizeCheck(const void* szPos) { if (!szPos || (const unsigned char*)szPos > this->mBuffer + this->iFileSize) { - throw new ImportErrorException("Invalid file. The file is too small " + throw new ImportErrorException("Invalid MDL file. The file is too small " "or contains invalid data."); } } @@ -278,7 +284,7 @@ void MDLImporter::SizeCheck(const void* szPos, const char* szFile, unsigned int #else ::sprintf(szBuffer, #endif - "Invalid file. The file is too small " + "Invalid MDL file. The file is too small " "or contains invalid data (File: %s Line: %i)",szFilePtr,iLine); throw new ImportErrorException(szBuffer); @@ -301,32 +307,35 @@ void MDLImporter::ValidateHeader_Quake1(const MDL::Header* pcHeader) throw new ImportErrorException( "[Quake 1 MDL] There are no triangles in the file"); } - // check whether the maxima are exceeded ... - if (pcHeader->num_verts > AI_MDL_MAX_VERTS) + // check whether the maxima are exceeded ...however, this applies for Quake 1 MDLs only + if (!this->iGSFileVersion) { - DefaultLogger::get()->warn("Quake 1 MDL model has more than AI_MDL_MAX_VERTS vertices"); - } - if (pcHeader->num_tris > AI_MDL_MAX_TRIANGLES) - { - DefaultLogger::get()->warn("Quake 1 MDL model has more than AI_MDL_MAX_TRIANGLES triangles"); - } - if (pcHeader->num_frames > AI_MDL_MAX_FRAMES) - { - DefaultLogger::get()->warn("Quake 1 MDL model has more than AI_MDL_MAX_FRAMES frames"); - } - // (this does not apply for 3DGS MDLs) - if (!this->iGSFileVersion && pcHeader->version != AI_MDL_VERSION) - { - DefaultLogger::get()->warn("Quake 1 MDL model has an unknown version: AI_MDL_VERSION (=6) is " - "the expected file format version"); - } - - if (pcHeader->num_skins) - { - if(!pcHeader->skinwidth || !pcHeader->skinheight) + if (pcHeader->num_verts > AI_MDL_MAX_VERTS) { - DefaultLogger::get()->warn("Skin width or height are 0. Division through " - "zero would occur ..."); + DefaultLogger::get()->warn("Quake 1 MDL model has more than AI_MDL_MAX_VERTS vertices"); + } + if (pcHeader->num_tris > AI_MDL_MAX_TRIANGLES) + { + DefaultLogger::get()->warn("Quake 1 MDL model has more than AI_MDL_MAX_TRIANGLES triangles"); + } + if (pcHeader->num_frames > AI_MDL_MAX_FRAMES) + { + DefaultLogger::get()->warn("Quake 1 MDL model has more than AI_MDL_MAX_FRAMES frames"); + } + // (this does not apply for 3DGS MDLs) + if (!this->iGSFileVersion && pcHeader->version != AI_MDL_VERSION) + { + DefaultLogger::get()->warn("Quake 1 MDL model has an unknown version: AI_MDL_VERSION (=6) is " + "the expected file format version"); + } + + if (pcHeader->num_skins) + { + if(!pcHeader->skinwidth || !pcHeader->skinheight) + { + DefaultLogger::get()->warn("Skin width or height are 0. Division through " + "zero would occur ..."); + } } } } @@ -994,8 +1003,9 @@ void MDLImporter::ReadFaces_3DGS_MDL7( unsigned int iIndex = pcGroupTris->v_index[c]; if(iIndex > (unsigned int)groupInfo.pcGroup->numverts) { - // LOG - iIndex = groupInfo.pcGroup->numverts-1; + // (we might need to read this section a second time - to process + // frame vertices correctly) + const_cast(pcGroupTris)->v_index[c] = iIndex = groupInfo.pcGroup->numverts-1; DefaultLogger::get()->warn("Index overflow in MDL7 vertex list"); } @@ -1022,14 +1032,6 @@ void MDLImporter::ReadFaces_3DGS_MDL7( vNormal.x = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[0]; vNormal.z = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[1]; vNormal.y = _AI_MDL7_ACCESS_VERT(groupInfo.pcGroupVerts,iIndex,pcHeader->mainvertex_stc_size) .norm[2]; - - // FIX: It seems to be necessary to invert all normals - // FIX2: No, it is not necessary :-) -#if 0 - vNormal.x *= -1.0f; - vNormal.y *= -1.0f; - vNormal.z *= -1.0f; -#endif } else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size) { @@ -1108,6 +1110,7 @@ void MDLImporter::ReadFaces_3DGS_MDL7( } // ------------------------------------------------------------------------------------------------ bool MDLImporter::ProcessFrames_3DGS_MDL7(const MDL::IntGroupInfo_MDL7& groupInfo, + MDL::IntGroupData_MDL7& groupData, MDL::IntSharedData_MDL7& shared, const unsigned char* szCurrent, const unsigned char** szCurrentOut) @@ -1119,9 +1122,10 @@ bool MDLImporter::ProcessFrames_3DGS_MDL7(const MDL::IntGroupInfo_MDL7& groupInf // if we have no bones we can simply skip all frames, // otherwise we'll need to process them. + // FIX: If we need another frame than the first we must apply frame vertex replacements ... for(unsigned int iFrame = 0; iFrame < (unsigned int)groupInfo.pcGroup->numframes;++iFrame) { - MDL::IntFrameInfo_MDL7 frame((const MDL::Frame_MDL7*)szCurrent,iFrame); + MDL::IntFrameInfo_MDL7 frame ((const MDL::Frame_MDL7*)szCurrent,iFrame); const unsigned int iAdd = pcHeader->frame_stc_size + frame.pcFrame->vertices_count * pcHeader->framevertex_stc_size + @@ -1129,15 +1133,82 @@ bool MDLImporter::ProcessFrames_3DGS_MDL7(const MDL::IntGroupInfo_MDL7& groupInf if (((const char*)szCurrent - (const char*)pcHeader) + iAdd > (unsigned int)pcHeader->data_size) { - DefaultLogger::get()->warn("Index overflow in frame area. Ignoring all frames and " - "all further groups, too."); + DefaultLogger::get()->warn("Index overflow in frame area. " + "Ignoring all frames and all further mesh groups, too."); // don't parse more groups if we can't even read one // FIXME: sometimes this seems to occur even for valid files ... *szCurrentOut = szCurrent; return false; } + // our output frame? + if (configFrameID == iFrame) + { + const MDL::Vertex_MDL7* pcFrameVertices = (const MDL::Vertex_MDL7*) + (szCurrent + pcHeader->framevertex_stc_size); + for (unsigned int qq = 0; qq < frame.pcFrame->vertices_count;++qq) + { + // I assume this are simple replacements for normal + // vertices, the bone index serving as the index of the + // vertex to be replaced. + uint16_t iIndex = _AI_MDL7_ACCESS(pcFrameVertices,qq, + pcHeader->framevertex_stc_size,MDL::Vertex_MDL7).vertindex; + + if (iIndex >= groupInfo.pcGroup->numverts) + { + DefaultLogger::get()->warn("Invalid vertex index in frame vertex section. " + "Skipping this frame vertex"); + continue; + } + + aiVector3D vPosition,vNormal; + + vPosition.x = _AI_MDL7_ACCESS_VERT(pcFrameVertices,qq,pcHeader->framevertex_stc_size) .x; + vPosition.z = _AI_MDL7_ACCESS_VERT(pcFrameVertices,qq,pcHeader->framevertex_stc_size) .y; + vPosition.y = _AI_MDL7_ACCESS_VERT(pcFrameVertices,qq,pcHeader->framevertex_stc_size) .z; + + // now read the normal vector + if (AI_MDL7_FRAMEVERTEX030305_STCSIZE <= pcHeader->mainvertex_stc_size) + { + // read the full normal vector + vNormal.x = _AI_MDL7_ACCESS_VERT(pcFrameVertices,qq,pcHeader->framevertex_stc_size) .norm[0]; + vNormal.z = _AI_MDL7_ACCESS_VERT(pcFrameVertices,qq,pcHeader->framevertex_stc_size) .norm[1]; + vNormal.y = _AI_MDL7_ACCESS_VERT(pcFrameVertices,qq,pcHeader->framevertex_stc_size) .norm[2]; + } + else if (AI_MDL7_FRAMEVERTEX120503_STCSIZE <= pcHeader->mainvertex_stc_size) + { + // read the normal vector from Quake2's smart table + MD2::LookupNormalIndex(_AI_MDL7_ACCESS_VERT(pcFrameVertices,qq, + pcHeader->framevertex_stc_size) .norm162index,vNormal); + + std::swap(vNormal.z,vNormal.y); + } + + // FIXME: O(n^2) at the moment ... + // shouldn't be too worse, frame vertices aren't required more + // than once a century ... + const MDL::Triangle_MDL7* pcGroupTris = groupInfo.pcGroupTris; + unsigned int iOutIndex = 0; + for (unsigned int iTriangle = 0; iTriangle < (unsigned int)groupInfo.pcGroup->numtris; ++iTriangle) + { + // iterate through all indices of the current triangle + for (unsigned int c = 0; c < 3;++c,++iOutIndex) + { + // replace the vertex with the new data + const unsigned int iCurIndex = pcGroupTris->v_index[c]; + if (iCurIndex == iIndex) + { + groupData.vPositions[iOutIndex] = vPosition; + groupData.vNormals[iOutIndex] = vNormal; + } + } + // get the next triangle in the list + pcGroupTris = (const MDL::Triangle_MDL7*)((const char*)pcGroupTris + + pcHeader->triangle_stc_size); + } + } + } // parse bone trafo matrix keys (only if there are bones ...) if (shared.apcOutBones) { @@ -1331,9 +1402,13 @@ void MDLImporter::InternReadFile_3DGS_MDL7( ) } // store the name of the group - ::memcpy(&aszGroupNameBuffer[iGroup*AI_MDL7_MAX_GROUPNAMESIZE], + const unsigned int ofs = iGroup*AI_MDL7_MAX_GROUPNAMESIZE; + ::memcpy(&aszGroupNameBuffer[ofs], groupInfo.pcGroup->name,AI_MDL7_MAX_GROUPNAMESIZE); + // make sure '\0' is at the end + aszGroupNameBuffer[ofs+AI_MDL7_MAX_GROUPNAMESIZE-1] = '\0'; + // read all skins sharedData.pcMats.reserve(sharedData.pcMats.size() + groupInfo.pcGroup->numskins); sharedData.abNeedMaterials.resize(sharedData.abNeedMaterials.size() + @@ -1362,6 +1437,8 @@ void MDLImporter::InternReadFile_3DGS_MDL7( ) aiString szName; szName.Set(AI_DEFAULT_MATERIAL_NAME); pcHelper->AddProperty(&szName,AI_MATKEY_NAME); + + sharedData.abNeedMaterials.resize(1,false); } // now get a pointer to all texture coords in the group @@ -1379,10 +1456,9 @@ void MDLImporter::InternReadFile_3DGS_MDL7( ) VALIDATE_FILE_SIZE(szCurrent); MDL::IntSplittedGroupData_MDL7 splittedGroupData(sharedData,avOutList[iGroup]); + MDL::IntGroupData_MDL7 groupData; if (groupInfo.pcGroup->numtris && groupInfo.pcGroup->numverts) { - MDL::IntGroupData_MDL7 groupData; - // build output vectors const unsigned int iNumVertices = groupInfo.pcGroup->numtris*3; groupData.vPositions.resize(iNumVertices); @@ -1426,7 +1502,7 @@ void MDLImporter::InternReadFile_3DGS_MDL7( ) "vertices or faces. It will be skipped."); // process all frames - if(!ProcessFrames_3DGS_MDL7(groupInfo,sharedData,szCurrent,&szCurrent)) + if(!ProcessFrames_3DGS_MDL7(groupInfo,groupData, sharedData,szCurrent,&szCurrent)) { break; } @@ -1497,8 +1573,8 @@ void MDLImporter::InternReadFile_3DGS_MDL7( ) delete[] avOutList; delete[] aszGroupNameBuffer; - DEBUG_INVALIDATE_PTR(avOutList); - DEBUG_INVALIDATE_PTR(aszGroupNameBuffer); + AI_DEBUG_INVALIDATE_PTR(avOutList); + AI_DEBUG_INVALIDATE_PTR(aszGroupNameBuffer); // build a final material list. this->CopyMaterials_3DGS_MDL7(sharedData); @@ -1550,7 +1626,7 @@ void MDLImporter::CopyMaterials_3DGS_MDL7(MDL::IntSharedData_MDL7 &shared) { // destruction is done by the destructor of sh delete shared.pcMats[i]; - DEBUG_INVALIDATE_PTR(shared.pcMats[i]); + AI_DEBUG_INVALIDATE_PTR(shared.pcMats[i]); continue; } this->pScene->mMaterials[p] = shared.pcMats[i]; @@ -1955,55 +2031,6 @@ void MDLImporter::JoinSkins_3DGS_MDL7( } } // ------------------------------------------------------------------------------------------------ -void MDLImporter::FlipNormals(aiMesh* pcMesh) -{ - ai_assert(NULL != pcMesh); - - // compute the bounding box of both the model vertices + normals and - // the umodified model vertices. Then check whether the first BB - // is smaller than the second. In this case we can assume that the - // normals need to be flipped, although there are a few special cases .. - // convex, concave, planar models ... - - aiVector3D vMin0(1e10f,1e10f,1e10f); - aiVector3D vMin1(1e10f,1e10f,1e10f); - aiVector3D vMax0(-1e10f,-1e10f,-1e10f); - aiVector3D vMax1(-1e10f,-1e10f,-1e10f); - - for (unsigned int i = 0; i < pcMesh->mNumVertices;++i) - { - vMin1.x = std::min(vMin1.x,pcMesh->mVertices[i].x); - vMin1.y = std::min(vMin1.y,pcMesh->mVertices[i].y); - vMin1.z = std::min(vMin1.z,pcMesh->mVertices[i].z); - - vMax1.x = std::max(vMax1.x,pcMesh->mVertices[i].x); - vMax1.y = std::max(vMax1.y,pcMesh->mVertices[i].y); - vMax1.z = std::max(vMax1.z,pcMesh->mVertices[i].z); - - aiVector3D vWithNormal = pcMesh->mVertices[i] + pcMesh->mNormals[i]; - - vMin0.x = std::min(vMin0.x,vWithNormal.x); - vMin0.y = std::min(vMin0.y,vWithNormal.y); - vMin0.z = std::min(vMin0.z,vWithNormal.z); - - vMax0.x = std::max(vMax0.x,vWithNormal.x); - vMax0.y = std::max(vMax0.y,vWithNormal.y); - vMax0.z = std::max(vMax0.z,vWithNormal.z); - } - - if (::fabsf((vMax0.x - vMin0.x) * (vMax0.y - vMin0.y) * (vMax0.z - vMin0.z)) <= - ::fabsf((vMax1.x - vMin1.x) * (vMax1.y - vMin1.y) * (vMax1.z - vMin1.z))) - { - DefaultLogger::get()->info("The models normals are facing inwards " - "(or the model is too planar or concave). Flipping the normal set ..."); - - for (unsigned int i = 0; i < pcMesh->mNumVertices;++i) - { - pcMesh->mNormals[i] *= -1.0f; - } - } -} -// ------------------------------------------------------------------------------------------------ void MDLImporter::InternReadFile_HL2( ) { const MDL::Header_HL2* pcHeader = (const MDL::Header_HL2*)this->mBuffer; diff --git a/code/MDLLoader.h b/code/MDLLoader.h index 76add04a6..e528f9e87 100644 --- a/code/MDLLoader.h +++ b/code/MDLLoader.h @@ -393,6 +393,7 @@ protected: * some tiny and unsolved problems ... ) */ bool ProcessFrames_3DGS_MDL7(const MDL::IntGroupInfo_MDL7& groupInfo, + MDL::IntGroupData_MDL7& groupData, MDL::IntSharedData_MDL7& shared, const unsigned char* szCurrent, const unsigned char** szCurrentOut); @@ -428,17 +429,11 @@ protected: MDL::IntGroupData_MDL7& groupData, MDL::IntSplittedGroupData_MDL7& splittedGroupData); - // ------------------------------------------------------------------- - /** Try to determine whether the normals of the model are flipped - * Some MDL7 models seem to have flipped normals (and there is also - * an option "flip normals" in MED). However, I don't see a proper - * way to read from the file whether all normals are correctly - * facing outwards ... - */ - void FlipNormals(aiMesh* pcMesh); - protected: + /** Configuration option: frame to be loaded */ + unsigned int configFrameID; + /** Buffer to hold the loaded file */ unsigned char* mBuffer; diff --git a/code/MDLMaterialLoader.cpp b/code/MDLMaterialLoader.cpp index eef6f3468..db42ee9b3 100644 --- a/code/MDLMaterialLoader.cpp +++ b/code/MDLMaterialLoader.cpp @@ -565,7 +565,9 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7( // sometimes there are MDL7 files which have a monochrome // texture instead of material colors ... posssible they have // been converted to MDL7 from other formats, such as MDL5 - aiColor4D clrTexture = this->ReplaceTextureWithColor(pcNew); + aiColor4D clrTexture; + if (pcNew)clrTexture = this->ReplaceTextureWithColor(pcNew); + else clrTexture.r = std::numeric_limits::quiet_NaN(); // check whether a material definition is contained in the skin if (iType & AI_MDL7_SKINTYPE_MATERIAL) @@ -654,21 +656,27 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7( // data structures in the aiScene instance if (pcNew && this->pScene->mNumTextures <= 999) { - - // place this as diffuse texture - char szCurrent[5]; - ::sprintf(szCurrent,"*%i",this->pScene->mNumTextures); - aiString szFile; - const size_t iLen = strlen((const char*)szCurrent); - size_t iLen2 = iLen+1; - iLen2 = iLen2 > MAXLEN ? MAXLEN : iLen2; - ::memcpy(szFile.data,(const char*)szCurrent,iLen2); - szFile.length = iLen; + // place this as diffuse texture + char szCurrent[5]; + ::sprintf(szCurrent,"*%i",this->pScene->mNumTextures); - pcMatOut->AddProperty(&szFile,AI_MATKEY_TEXTURE_DIFFUSE(0)); + aiString szFile; + const size_t iLen = strlen((const char*)szCurrent); + ::memcpy(szFile.data,(const char*)szCurrent,iLen+1); + szFile.length = iLen; - // store the texture + pcMatOut->AddProperty(&szFile,AI_MATKEY_TEXTURE_DIFFUSE(0)); + + // store the texture + if (!this->pScene->mNumTextures) + { + this->pScene->mNumTextures = 1; + this->pScene->mTextures = new aiTexture*[1]; + this->pScene->mTextures[0] = pcNew; + } + else + { aiTexture** pc = this->pScene->mTextures; this->pScene->mTextures = new aiTexture*[this->pScene->mNumTextures+1]; for (unsigned int i = 0; i < this->pScene->mNumTextures;++i) @@ -677,7 +685,7 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7( this->pScene->mTextures[this->pScene->mNumTextures] = pcNew; this->pScene->mNumTextures++; delete[] pc; - + } } VALIDATE_FILE_SIZE(szCurrent); *szCurrentOut = szCurrent; @@ -716,6 +724,9 @@ void MDLImporter::SkipSkinLump_3DGS_MDL7( tex.mWidth = iWidth; this->ParseTextureColorData(szCurrent,iMasked,&iSkip,&tex); + // FIX: Important, otherwise the destructor will crash + tex.pcData = NULL; + // skip length of texture data szCurrent += iSkip; } @@ -761,12 +772,13 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7( pcSkin->typ,pcSkin->width,pcSkin->height); // place the name of the skin in the material - const size_t iLen = strlen(pcSkin->texture_name); - if (0 != iLen) + if (pcSkin->texture_name[0]) { + // the 0 termination could be there or not - we can't know aiString szFile; - memcpy(szFile.data,pcSkin->texture_name,sizeof(pcSkin->texture_name)); - szFile.length = iLen; + ::memcpy(szFile.data,pcSkin->texture_name,sizeof(pcSkin->texture_name)); + szFile.data[sizeof(pcSkin->texture_name)] = '\0'; + szFile.length = ::strlen(szFile.data); pcMatOut->AddProperty(&szFile,AI_MATKEY_NAME); } diff --git a/code/MaterialSystem.cpp b/code/MaterialSystem.cpp index 1a63d7c0e..9ddd4e880 100644 --- a/code/MaterialSystem.cpp +++ b/code/MaterialSystem.cpp @@ -40,74 +40,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "MaterialSystem.h" #include "StringComparison.h" +#include "Hash.h" #include "../include/aiMaterial.h" #include "../include/aiAssert.h" using namespace Assimp; - -// ------------------------------------------------------------------------------------------------ -// hashing function taken from -// http://www.azillionmonkeys.com/qed/hash.html -// (incremental version of the hashing function) -// (stdint.h should have been been included here) -#undef get16bits -#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ - || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) -#define get16bits(d) (*((const uint16_t *) (d))) +// we are using sprintf only on fixed-size buffers, so the +// compiler should automatically expand the template sprintf_s<> +#if _MSC_VER >= 1400 +# define sprintf sprintf_s #endif -#if !defined (get16bits) -#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ - +(uint32_t)(((const uint8_t *)(d))[0]) ) -#endif - -// ------------------------------------------------------------------------------------------------ -uint32_t SuperFastHash (const char * data, int len, uint32_t hash = 0) { -uint32_t tmp; -int rem; - - if (len <= 0 || data == NULL) return 0; - - rem = len & 3; - len >>= 2; - - /* Main loop */ - for (;len > 0; len--) { - hash += get16bits (data); - tmp = (get16bits (data+2) << 11) ^ hash; - hash = (hash << 16) ^ tmp; - data += 2*sizeof (uint16_t); - hash += hash >> 11; - } - - /* Handle end cases */ - switch (rem) { - case 3: hash += get16bits (data); - hash ^= hash << 16; - hash ^= data[sizeof (uint16_t)] << 18; - hash += hash >> 11; - break; - case 2: hash += get16bits (data); - hash ^= hash << 11; - hash += hash >> 17; - break; - case 1: hash += *data; - hash ^= hash << 10; - hash += hash >> 1; - } - - /* Force "avalanching" of final 127 bits */ - hash ^= hash << 3; - hash += hash >> 5; - hash ^= hash << 4; - hash += hash >> 17; - hash ^= hash << 25; - hash += hash >> 6; - - return hash; -} // ------------------------------------------------------------------------------------------------ aiReturn aiGetMaterialProperty(const aiMaterial* pMat, const char* pKey, @@ -121,7 +66,7 @@ aiReturn aiGetMaterialProperty(const aiMaterial* pMat, { if (NULL != pMat->mProperties[i]) { - if (0 == ASSIMP_stricmp( pMat->mProperties[i]->mKey->data, pKey )) + if (0 == ASSIMP_stricmp( pMat->mProperties[i]->mKey.data, pKey )) { *pPropOut = pMat->mProperties[i]; return AI_SUCCESS; @@ -145,14 +90,13 @@ aiReturn aiGetMaterialFloatArray(const aiMaterial* pMat, { if (NULL != pMat->mProperties[i]) { - if (0 == ASSIMP_stricmp( pMat->mProperties[i]->mKey->data, pKey )) + if (0 == ASSIMP_stricmp( pMat->mProperties[i]->mKey.data, pKey )) { // data is given in floats, simply copy it if( aiPTI_Float == pMat->mProperties[i]->mType || aiPTI_Buffer == pMat->mProperties[i]->mType) { - unsigned int iWrite = pMat->mProperties[i]-> - mDataLength / sizeof(float); + unsigned int iWrite = pMat->mProperties[i]->mDataLength / sizeof(float); if (NULL != pMax) iWrite = *pMax < iWrite ? *pMax : iWrite; @@ -204,7 +148,7 @@ aiReturn aiGetMaterialIntegerArray(const aiMaterial* pMat, { if (NULL != pMat->mProperties[i]) { - if (0 == ASSIMP_stricmp( pMat->mProperties[i]->mKey->data, pKey )) + if (0 == ASSIMP_stricmp( pMat->mProperties[i]->mKey.data, pKey )) { // data is given in ints, simply copy it if( aiPTI_Integer == pMat->mProperties[i]->mType || @@ -275,12 +219,12 @@ aiReturn aiGetMaterialString(const aiMaterial* pMat, { if (NULL != pMat->mProperties[i]) { - if (0 == ASSIMP_stricmp( pMat->mProperties[i]->mKey->data, pKey )) + if (0 == ASSIMP_stricmp( pMat->mProperties[i]->mKey.data, pKey )) { if( aiPTI_String == pMat->mProperties[i]->mType) { - memcpy (pOut, pMat->mProperties[i]->mData, - sizeof(aiString)); + const aiString* pcSrc = (const aiString*)pMat->mProperties[i]->mData; + ::memcpy (pOut->data, pcSrc->data, (pOut->length = pcSrc->length)+1); } // wrong type else return AI_FAILURE; @@ -291,6 +235,29 @@ aiReturn aiGetMaterialString(const aiMaterial* pMat, return AI_FAILURE; } // ------------------------------------------------------------------------------------------------ +MaterialHelper::MaterialHelper() +{ + // allocate 5 entries by default + this->mNumProperties = 0; + this->mNumAllocated = 5; + this->mProperties = new aiMaterialProperty*[5]; + return; +} +// ------------------------------------------------------------------------------------------------ +MaterialHelper::~MaterialHelper() +{ + for (unsigned int i = 0; i < this->mNumProperties;++i) + { + // be careful ... + if(NULL != this->mProperties[i]) + { + delete[] this->mProperties[i]->mData; + delete this->mProperties[i]; + } + } + return; +} +// ------------------------------------------------------------------------------------------------ uint32_t MaterialHelper::ComputeHash() { uint32_t hash = 1503; // magic start value, choosen to be my birthday :-) @@ -299,9 +266,9 @@ uint32_t MaterialHelper::ComputeHash() aiMaterialProperty* prop; // NOTE: We need to exclude the material name from the hash - if ((prop = this->mProperties[i]) && 0 != ::strcmp(prop->mKey->data,AI_MATKEY_NAME)) + if ((prop = this->mProperties[i]) && 0 != ::strcmp(prop->mKey.data,AI_MATKEY_NAME)) { - hash = SuperFastHash(prop->mKey->data,prop->mKey->length,hash); + hash = SuperFastHash(prop->mKey.data,prop->mKey.length,hash); hash = SuperFastHash(prop->mData,prop->mDataLength,hash); } } @@ -316,7 +283,7 @@ aiReturn MaterialHelper::RemoveProperty (const char* pKey) { if (this->mProperties[i]) // just for safety { - if (0 == ASSIMP_stricmp( this->mProperties[i]->mKey->data, pKey )) + if (0 == ASSIMP_stricmp( this->mProperties[i]->mKey.data, pKey )) { // delete this entry delete[] this->mProperties[i]->mData; @@ -352,7 +319,7 @@ aiReturn MaterialHelper::AddBinaryProperty (const void* pInput, { if (this->mProperties[i]) { - if (0 == ASSIMP_stricmp( this->mProperties[i]->mKey->data, pKey )) + if (0 == ASSIMP_stricmp( this->mProperties[i]->mKey.data, pKey )) { // delete this entry delete[] this->mProperties[i]->mData; @@ -365,16 +332,15 @@ aiReturn MaterialHelper::AddBinaryProperty (const void* pInput, aiMaterialProperty* pcNew = new aiMaterialProperty(); // fill this - pcNew->mKey = new aiString(); pcNew->mType = pType; pcNew->mDataLength = pSizeInBytes; pcNew->mData = new char[pSizeInBytes]; memcpy (pcNew->mData,pInput,pSizeInBytes); - pcNew->mKey->length = strlen(pKey); - ai_assert ( MAXLEN > pcNew->mKey->length); - strcpy( pcNew->mKey->data, pKey ); + pcNew->mKey.length = ::strlen(pKey); + ai_assert ( MAXLEN > pcNew->mKey.length); + ::strcpy( pcNew->mKey.data, pKey ); if (0xFFFFFFFF != iOutIndex) { @@ -391,7 +357,7 @@ aiReturn MaterialHelper::AddBinaryProperty (const void* pInput, aiMaterialProperty** ppTemp = new aiMaterialProperty*[this->mNumAllocated]; if (NULL == ppTemp)return AI_OUTOFMEMORY; - memcpy (ppTemp,this->mProperties,iOld * sizeof(void*)); + ::memcpy (ppTemp,this->mProperties,iOld * sizeof(void*)); delete[] this->mProperties; this->mProperties = ppTemp; @@ -404,8 +370,10 @@ aiReturn MaterialHelper::AddBinaryProperty (const void* pInput, aiReturn MaterialHelper::AddProperty (const aiString* pInput, const char* pKey) { + // fix ... don't keep the whole string buffer return this->AddBinaryProperty(pInput, - sizeof(aiString),pKey,aiPTI_String); + pInput->length+1+ (size_t)((uint8_t*)&pInput->data - (uint8_t*)&pInput->length), + pKey,aiPTI_String); } // ------------------------------------------------------------------------------------------------ void MaterialHelper::CopyPropertyList(MaterialHelper* pcDest, @@ -421,21 +389,41 @@ void MaterialHelper::CopyPropertyList(MaterialHelper* pcDest, aiMaterialProperty** pcOld = pcDest->mProperties; pcDest->mProperties = new aiMaterialProperty*[pcDest->mNumAllocated]; - if (pcOld) + if (iOldNum && pcOld) { for (unsigned int i = 0; i < iOldNum;++i) pcDest->mProperties[i] = pcOld[i]; - delete[] pcDest->mProperties; + delete[] pcOld; } for (unsigned int i = iOldNum; i< pcDest->mNumProperties;++i) { - pcDest->mProperties[i]->mKey = new aiString(*pcSrc->mProperties[i]->mKey); - pcDest->mProperties[i]->mDataLength = pcSrc->mProperties[i]->mDataLength; - pcDest->mProperties[i]->mType = pcSrc->mProperties[i]->mType; - pcDest->mProperties[i]->mData = new char[pcDest->mProperties[i]->mDataLength]; - memcpy(pcDest->mProperties[i]->mData,pcSrc->mProperties[i]->mData, - pcDest->mProperties[i]->mDataLength); + aiMaterialProperty* propSrc = pcSrc->mProperties[i]; + + // search whether we have already a property with this name + // (if yes we overwrite the old one) + aiMaterialProperty* prop; + for (unsigned int q = 0; q < iOldNum;++q) + { + prop = pcDest->mProperties[q]; + if (propSrc->mKey.length == prop->mKey.length && + !ASSIMP_stricmp(propSrc->mKey.data,prop->mKey.data)) + { + delete prop; + + // collapse the whole array ... + ::memmove(&pcDest->mProperties[q],&pcDest->mProperties[q+1],i-q); + i--; + pcDest->mNumProperties--; + } + } + + prop = pcDest->mProperties[i] = new aiMaterialProperty(); + prop->mKey = propSrc->mKey; + prop->mDataLength = propSrc->mDataLength; + prop->mType = propSrc->mType; + prop->mData = new char[propSrc->mDataLength]; + ::memcpy(prop->mData,propSrc->mData,prop->mDataLength); } return; } @@ -545,12 +533,7 @@ aiReturn aiGetMaterialTexture(const aiMaterial* pcMat, if (iIndex > 100)return AI_FAILURE; // get the path to the texture -#if _MSC_VER >= 1400 - if(0 >= sprintf_s(szKey,"%s[%i]",szPathBase,iIndex))DummyAssertFunction(); -#else if(0 >= sprintf(szKey,"%s[%i]",szPathBase,iIndex))DummyAssertFunction(); -#endif - if (AI_SUCCESS != aiGetMaterialString(pcMat,szKey,szOut)) { return AI_FAILURE; @@ -559,11 +542,7 @@ aiReturn aiGetMaterialTexture(const aiMaterial* pcMat, if (piUVIndex) { int iUV; -#if _MSC_VER >= 1400 - if(0 >= sprintf_s(szKey,"%s[%i]",szUVBase,iIndex))DummyAssertFunction(); -#else if(0 >= sprintf(szKey,"%s[%i]",szUVBase,iIndex))DummyAssertFunction(); -#endif if (AI_SUCCESS != aiGetMaterialInteger(pcMat,szKey,&iUV)) iUV = 0; @@ -573,11 +552,7 @@ aiReturn aiGetMaterialTexture(const aiMaterial* pcMat, if (pfBlendFactor) { float fBlend; -#if _MSC_VER >= 1400 - if(0 >= sprintf_s(szKey,"%s[%i]",szBlendBase,iIndex))DummyAssertFunction(); -#else if(0 >= sprintf(szKey,"%s[%i]",szBlendBase,iIndex))DummyAssertFunction(); -#endif if (AI_SUCCESS != aiGetMaterialFloat(pcMat,szKey,&fBlend)) fBlend = 1.0f; @@ -588,11 +563,7 @@ aiReturn aiGetMaterialTexture(const aiMaterial* pcMat, if (peTextureOp) { aiTextureOp op; -#if _MSC_VER >= 1400 - if(0 >= sprintf_s(szKey,"%s[%i]",szOpBase,iIndex))DummyAssertFunction(); -#else if(0 >= sprintf(szKey,"%s[%i]",szOpBase,iIndex))DummyAssertFunction(); -#endif if (AI_SUCCESS != aiGetMaterialInteger(pcMat,szKey,(int*)&op)) op = aiTextureOp_Multiply; @@ -605,11 +576,7 @@ aiReturn aiGetMaterialTexture(const aiMaterial* pcMat, aiTextureMapMode eMode; for (unsigned int q = 0; q < 3;++q) { -#if _MSC_VER >= 1400 - if(0 >= sprintf_s(szKey,"%s[%i]",aszMapModeBase[q],iIndex))DummyAssertFunction(); -#else if(0 >= sprintf(szKey,"%s[%i]",aszMapModeBase[q],iIndex))DummyAssertFunction(); -#endif if (AI_SUCCESS != aiGetMaterialInteger(pcMat,szKey,(int*)&eMode)) { eMode = aiTextureMapMode_Wrap; diff --git a/code/MaterialSystem.h b/code/MaterialSystem.h index 309bc5e8d..e9ca949cb 100644 --- a/code/MaterialSystem.h +++ b/code/MaterialSystem.h @@ -55,8 +55,8 @@ class ASSIMP_API MaterialHelper : public ::aiMaterial { public: - inline MaterialHelper(); - inline ~MaterialHelper(); + MaterialHelper(); + ~MaterialHelper(); // ------------------------------------------------------------------- /** Add a property with a given key and type info to the material @@ -125,36 +125,6 @@ public: }; -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- -inline MaterialHelper::MaterialHelper() - { - // allocate 5 entries by default - this->mNumProperties = 0; - this->mNumAllocated = 5; - this->mProperties = new aiMaterialProperty*[5]; - return; - } - - -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- -inline MaterialHelper::~MaterialHelper() - { - for (unsigned int i = 0; i < this->mNumProperties;++i) - { - // be careful ... - if(NULL != this->mProperties[i]) - { - delete[] this->mProperties[i]->mKey; - delete[] this->mProperties[i]->mData; - delete this->mProperties[i]; - } - } - return; - } - - // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- template diff --git a/code/RemoveRedundantMaterials.cpp b/code/RemoveRedundantMaterials.cpp index 87b909454..eb2d1617a 100644 --- a/code/RemoveRedundantMaterials.cpp +++ b/code/RemoveRedundantMaterials.cpp @@ -122,13 +122,13 @@ void RemoveRedundantMatsProcess::Execute( aiScene* pScene) { // generate new names for all modified materials const unsigned int idx = aiMappingTable[p]; - if (!ppcMaterials[idx]) + if (ppcMaterials[idx]) { aiString sz; sz.length = ::sprintf(sz.data,"aiMaterial #%i",p); - ppcMaterials[idx] = pScene->mMaterials[p]; - ((MaterialHelper*)pScene->mMaterials[p])->AddProperty(&sz,AI_MATKEY_NAME); + ((MaterialHelper*)ppcMaterials[idx])->AddProperty(&sz,AI_MATKEY_NAME); } + else ppcMaterials[idx] = pScene->mMaterials[p]; } // update all material indices for (unsigned int p = 0; p < pScene->mNumMeshes;++p) diff --git a/code/STLLoader.cpp b/code/STLLoader.cpp new file mode 100644 index 000000000..71d30f37f --- /dev/null +++ b/code/STLLoader.cpp @@ -0,0 +1,398 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (ASSIMP) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development Team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the ASSIMP team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the ASSIMP Development Team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +/** @file Implementation of the STL importer class */ + +// internal headers +#include "STLLoader.h" +#include "MaterialSystem.h" +#include "ParsingUtils.h" +#include "fast_atof.h" + +// public assimp headers +#include "../include/IOStream.h" +#include "../include/IOSystem.h" +#include "../include/aiMesh.h" +#include "../include/aiScene.h" +#include "../include/aiAssert.h" +#include "../include/DefaultLogger.h" + +// boost headers +#include + +using namespace Assimp; + + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +STLImporter::STLImporter() +{ +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +STLImporter::~STLImporter() +{ +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool STLImporter::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); + + if (extension.length() < 4)return false; + if (extension[0] != '.')return false; + + if (extension[1] != 's' && extension[1] != 'S')return false; + if (extension[2] != 't' && extension[2] != 'T')return false; + if (extension[3] != 'l' && extension[3] != 'L')return false; + + return true; +} +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void STLImporter::InternReadFile( + const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) +{ + boost::scoped_ptr file( pIOHandler->Open( pFile, "rb")); + + // Check whether we can read from the file + if( file.get() == NULL) + { + throw new ImportErrorException( "Failed to open STL file " + pFile + "."); + } + + this->fileSize = (unsigned int)file->FileSize(); + + // allocate storage and copy the contents of the file to a memory buffer + // (terminate it with zero) + this->mBuffer = new char[fileSize+1]; + this->pScene = pScene; + file->Read( (void*)mBuffer, 1, fileSize); + const_cast(this->mBuffer)[fileSize] = '\0'; + + // the default vertex color is white + clrColorDefault.r = clrColorDefault.g = clrColorDefault.b = clrColorDefault.a = 1.0f; + + // allocate one mesh + pScene->mNumMeshes = 1; + pScene->mMeshes = new aiMesh*[1]; + aiMesh* pMesh = pScene->mMeshes[0] = new aiMesh(); + pMesh->mMaterialIndex = 0; + + // allocate a single node + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mRootNode->mMeshes[0] = 0; + + bool bMatClr = false; + try + { + // check whether the file starts with 'solid' - + // in this case we can simply assume it IS a text file. finished. + if (!::strncmp(mBuffer,"solid",5)) + this->LoadASCIIFile(); + else bMatClr = this->LoadBinaryFile(); + + // now copy faces + pMesh->mFaces = new aiFace[pMesh->mNumFaces]; + for (unsigned int i = 0, p = 0; i < pMesh->mNumFaces;++i) + { + aiFace& face = pMesh->mFaces[i]; + face.mIndices = new unsigned int[face.mNumIndices = 3]; + for (unsigned int o = 0; o < 3;++o,++p) + face.mIndices[o] = p; + } + } + catch (ImportErrorException* ex) + { + delete[] this->mBuffer;AI_DEBUG_INVALIDATE_PTR(this->mBuffer); + throw ex; + } + + // create a single default material - everything white, as + // we have vertex colors + MaterialHelper* pcMat = new MaterialHelper(); + aiString s; + s.Set(AI_DEFAULT_MATERIAL_NAME); + pcMat->AddProperty(&s, AI_MATKEY_NAME); + + aiColor4D clrDiffuse(1.0f,1.0f,1.0f,1.0f); + if (bMatClr)clrDiffuse = this->clrColorDefault; + pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_DIFFUSE); + pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_SPECULAR); + clrDiffuse = aiColor4D(0.05f,0.05f,0.05f,1.0f); + pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_AMBIENT); + + pScene->mNumMaterials = 1; + pScene->mMaterials = new aiMaterial*[1]; + pScene->mMaterials[0] = pcMat; + + delete[] this->mBuffer;AI_DEBUG_INVALIDATE_PTR(this->mBuffer); +} +// ------------------------------------------------------------------------------------------------ +// Read an ASCII STL file +void STLImporter::LoadASCIIFile() +{ + aiMesh* pMesh = pScene->mMeshes[0]; + + const char* sz = mBuffer + 5; // skip the "solid" + SkipSpaces(&sz); + const char* szMe = sz; + while (!::IsSpaceOrNewLine(*sz))sz++; + unsigned int temp; + + // setup the name of the node + if (temp = unsigned int(sz-szMe)) + { + pScene->mRootNode->mName.length = temp; + ::memcpy(pScene->mRootNode->mName.data,szMe,temp); + pScene->mRootNode->mName.data[temp] = '\0'; + } + else pScene->mRootNode->mName.Set(""); + + // try to guess how many vertices we could have + // assume we'll need 160 bytes for each face + pMesh->mNumVertices = ( pMesh->mNumFaces = fileSize / 160 ) * 3; + pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; + pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + + unsigned int curFace = 0, curVertex = 0; + while (true) + { + // go to the next token + if(!SkipSpacesAndLineEnd(&sz)) + { + // seems we're finished although there was no end marker + DefaultLogger::get()->warn("STL: unexpected EOF. \'endsolid\' keyword was expected"); + break; + } + // facet normal -0.13 -0.13 -0.98 + if (!::strncmp(sz,"facet",5) && ::IsSpaceOrNewLine(*(sz+5))) + { + if (3 != curVertex)DefaultLogger::get()->warn("STL: A new facet begins but the old is not yet complete"); + if (pMesh->mNumFaces == curFace) + { + // need to resize the arrays, our size estimate was wrong + unsigned int iNeededSize = unsigned int(sz-mBuffer) / pMesh->mNumFaces; + if (iNeededSize <= 160)iNeededSize >>= 1; // prevent endless looping + unsigned int add = unsigned int((mBuffer+fileSize)-sz) / iNeededSize; + add += add >> 3; // add 12.5% as buffer + iNeededSize = (pMesh->mNumFaces + add)*3; + aiVector3D* pv = new aiVector3D[iNeededSize]; + ::memcpy(pv,pMesh->mVertices,pMesh->mNumVertices*sizeof(aiVector3D)); + delete[] pMesh->mVertices; + pMesh->mVertices = pv; + pv = new aiVector3D[iNeededSize]; + ::memcpy(pv,pMesh->mNormals,pMesh->mNumVertices*sizeof(aiVector3D)); + delete[] pMesh->mNormals; + pMesh->mNormals = pv; + + pMesh->mNumVertices = iNeededSize; + pMesh->mNumFaces += add; + } + aiVector3D* vn = &pMesh->mNormals[curFace++*3]; + + sz += 6; + curVertex = 0; + SkipSpaces(&sz); + if (::strncmp(sz,"normal",6)) + { + DefaultLogger::get()->warn("STL: a facet normal vector was expected but not found"); + } + else + { + sz += 7; + SkipSpaces(&sz); + sz = fast_atof_move(sz, vn->x ); + SkipSpaces(&sz); + sz = fast_atof_move(sz, vn->y ); + SkipSpaces(&sz); + sz = fast_atof_move(sz, vn->z ); + *(vn+1) = *vn; + *(vn+2) = *vn; + } + } + // vertex 1.50000 1.50000 0.00000 + else if (!::strncmp(sz,"vertex",6) && ::IsSpaceOrNewLine(*(sz+6))) + { + if (3 == curVertex) + { + DefaultLogger::get()->error("STL: a facet with more than 3 vertices has been found"); + } + else + { + sz += 7; + SkipSpaces(&sz); + aiVector3D* vn = &pMesh->mVertices[(curFace-1)*3 + curVertex++]; + sz = fast_atof_move(sz, vn->x ); + SkipSpaces(&sz); + sz = fast_atof_move(sz, vn->y ); + SkipSpaces(&sz); + sz = fast_atof_move(sz, vn->z ); + } + } + else if (!::strncmp(sz,"endsolid",8)) + { + // finished! + break; + } + // else skip the whole identifier + else while (!::IsSpaceOrNewLine(*sz))++sz; + } + + if (!curFace) + { + pMesh->mNumFaces = 0; + throw new ImportErrorException("STL: ASCII file is empty or invalid; no data loaded"); + } + pMesh->mNumFaces = curFace; + pMesh->mNumVertices = curFace*3; + // we are finished! +} +// ------------------------------------------------------------------------------------------------ +// Read a binary STL file +bool STLImporter::LoadBinaryFile() +{ + // skip the first 80 bytes + if (fileSize < 84) + throw new ImportErrorException("STL: file is too small for the header"); + + bool bIsMaterialise = false; + + // search for an occurence of "COLOR=" in the header + const char* sz2 = (const char*)mBuffer; + const char* const szEnd = sz2+80; + while (sz2 < szEnd) + { + if ('C' == *sz2++ && 'O' == *sz2++ && 'L' == *sz2++ && + 'O' == *sz2++ && 'R' == *sz2++ && '=' == *sz2++) + { + // read the default vertex color for facets + bIsMaterialise = true; + this->clrColorDefault.r = (*sz2++) / 255.0f; + this->clrColorDefault.g = (*sz2++) / 255.0f; + this->clrColorDefault.b = (*sz2++) / 255.0f; + this->clrColorDefault.a = (*sz2++) / 255.0f; + break; + } + } + const unsigned char* sz = (const unsigned char*)mBuffer + 80; + + // now read the number of facets + aiMesh* pMesh = pScene->mMeshes[0]; + pScene->mRootNode->mName.Set(""); + + pMesh->mNumFaces = *((uint32_t*)sz); + sz += 4; + + if (fileSize < 84 + pMesh->mNumFaces*50) + throw new ImportErrorException("STL: file is too small to keep all facets"); + if (!pMesh->mNumFaces) + throw new ImportErrorException("STL: file is empty. There are no facets defined"); + + pMesh->mNumVertices = pMesh->mNumFaces*3; + + aiVector3D* vp,*vn; + vp = pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; + vn = pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + + for (unsigned int i = 0; i < pMesh->mNumFaces;++i) + { + *vn = *((aiVector3D*)sz); + sz += sizeof(aiVector3D); + *(vn+1) = *vn; + *(vn+2) = *vn; + + *vp++ = *((aiVector3D*)sz); + sz += sizeof(aiVector3D); + + *vp++ = *((aiVector3D*)sz); + sz += sizeof(aiVector3D); + + *vp++ = *((aiVector3D*)sz); + sz += sizeof(aiVector3D); + + uint16_t color = *((uint16_t*)sz); + sz += 2; + + if (color & (1 << 15)) + { + // seems we need to take the color + if (!pMesh->mColors[0]) + { + pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices]; + for (unsigned int i = 0; i mNumVertices;++i) + *pMesh->mColors[0]++ = this->clrColorDefault; + pMesh->mColors[0] -= pMesh->mNumVertices; + } + aiColor4D* clr = &pMesh->mColors[0][pMesh->mNumFaces*3]; + clr->a = 1.0f; + if (bIsMaterialise) // fuck, this is reversed + { + clr->r = (color & 0x31u) / 31.0f; + clr->g = ((color & (0x31u<<5))>>5u) / 31.0f; + clr->b = ((color & (0x31u<<10))>>10u) / 31.0f; + } + else + { + clr->b = (color & 0x31u) / 31.0f; + clr->g = ((color & (0x31u<<5))>>5u) / 31.0f; + clr->r = ((color & (0x31u<<10))>>10u) / 31.0f; + } + // assign the color to all vertices of the face + *(clr+1) = *clr; + *(clr+2) = *clr; + } + } + if (bIsMaterialise && !pMesh->mColors[0]) + { + // use the color was diffuse material color + return true; + } + return false; +} diff --git a/code/STLLoader.h b/code/STLLoader.h new file mode 100644 index 000000000..9ffaaacf6 --- /dev/null +++ b/code/STLLoader.h @@ -0,0 +1,119 @@ +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development Team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the ASSIMP team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the ASSIMP Development Team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file Declaration of the STL importer class. */ +#ifndef AI_STLLOADER_H_INCLUDED +#define AI_STLLOADER_H_INCLUDED + +#include "BaseImporter.h" +#include "../include/aiTypes.h" + +namespace Assimp +{ + +// --------------------------------------------------------------------------- +/** Clas to load STL files +*/ +class STLImporter : public BaseImporter +{ + friend class Importer; + +protected: + /** Constructor to be privately used by Importer */ + STLImporter(); + + /** Destructor, private as well */ + ~STLImporter(); + +public: + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. */ + bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const; + +protected: + + // ------------------------------------------------------------------- + /** Called by Importer::GetExtensionList() for each loaded importer. + * See BaseImporter::GetExtensionList() for details + */ + void GetExtensionList(std::string& append) + { + append.append("*.stl"); + } + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile( const std::string& pFile, aiScene* pScene, + IOSystem* pIOHandler); + + + // ------------------------------------------------------------------- + /** Loads a binary .stl file + * @return true if the default vertex color should be used as material color + */ + bool LoadBinaryFile(); + + // ------------------------------------------------------------------- + /** Loads a ASCII text .stl file + */ + void LoadASCIIFile(); + +protected: + + /** Buffer to hold the loaded file */ + const char* mBuffer; + + /** Size of the file, in bytes */ + unsigned int fileSize; + + /** Output scene */ + aiScene* pScene; + + /** Default vertex color */ + aiColor4D clrColorDefault; +}; + +} // end of namespace Assimp + +#endif // AI_3DSIMPORTER_H_IN \ No newline at end of file diff --git a/code/SplitLargeMeshes.cpp b/code/SplitLargeMeshes.cpp index 9f8bfb14a..4c7113a51 100644 --- a/code/SplitLargeMeshes.cpp +++ b/code/SplitLargeMeshes.cpp @@ -265,7 +265,7 @@ void SplitLargeMeshesProcess_Triangle::SplitMesh( aiBone* pc = new aiBone(); pcMesh->mBones[pcMesh->mNumBones++] = pc; pc->mName = aiString(bone->mName); - pc->mNumWeights = avTempWeights.size(); + pc->mNumWeights = (unsigned int)avTempWeights.size(); pc->mOffsetMatrix = bone->mOffsetMatrix; // no need to reallocate the array for the last submesh. @@ -615,7 +615,7 @@ void SplitLargeMeshesProcess_Vertex::SplitMesh( *ppCurrent++ = pcOut = new aiBone(); pcOut->mName = aiString(pcOldBone->mName); pcOut->mOffsetMatrix = pcOldBone->mOffsetMatrix; - pcOut->mNumWeights = pcWeightList->size(); + pcOut->mNumWeights = (unsigned int)pcWeightList->size(); pcOut->mWeights = new aiVertexWeight[pcOut->mNumWeights]; // copy the vertex weights diff --git a/code/ValidateDataStructure.cpp b/code/ValidateDataStructure.cpp index 049546601..cfd6d7612 100644 --- a/code/ValidateDataStructure.cpp +++ b/code/ValidateDataStructure.cpp @@ -108,6 +108,7 @@ void ValidateDSProcess::ReportError(const char* msg,...) throw new ImportErrorException("Idiot ... learn coding!"); } va_end(args); + ai_assert(false); throw new ImportErrorException("Validation failed: " + std::string(szBuffer,iLen)); } // ------------------------------------------------------------------------------------------------ @@ -357,6 +358,7 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh) { if (!pMesh->mBones[i]) { + delete[] afSum; this->ReportError("aiMesh::mBones[%i] is NULL (aiMesh::mNumBones is %i)", i,pMesh->mNumBones); } @@ -366,6 +368,7 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh) { if (pMesh->mBones[i]->mName == pMesh->mBones[a]->mName) { + delete[] afSum; this->ReportError("aiMesh::mBones[%i] has the same name as " "aiMesh::mBones[%i]",i,a); } @@ -376,9 +379,7 @@ void ValidateDSProcess::Validate( const aiMesh* pMesh) { if (afSum[i] && (afSum[i] <= 0.995 || afSum[i] >= 1.005)) { - delete[] afSum; - this->ReportError("aiMesh::mVertices[%i]: The sum of all bone " - "weights isn't 1.0f (sum is %f)",i,afSum[i]); + this->ReportWarning("aiMesh::mVertices[%i]: bone weight sum != 1.0 (sum is %f)",i,afSum[i]); } } delete[] afSum; @@ -457,9 +458,9 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial, for (unsigned int i = 0; i < pMaterial->mNumProperties;++i) { aiMaterialProperty* prop = pMaterial->mProperties[i]; - if (0 == ASSIMP_strincmp( prop->mKey->data, szBaseBuf, iLen )) + if (0 == ASSIMP_strincmp( prop->mKey.data, szBaseBuf, iLen )) { - const char* sz = &prop->mKey->data[iLen]; + const char* sz = &prop->mKey.data[iLen]; if (*sz) { ++sz; @@ -488,12 +489,12 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial, for (unsigned int i = 0; i < pMaterial->mNumProperties;++i) { aiMaterialProperty* prop = pMaterial->mProperties[i]; - if (0 == ASSIMP_strincmp( prop->mKey->data, szBaseBuf, iLen )) + if (0 == ASSIMP_strincmp( prop->mKey.data, szBaseBuf, iLen )) { if (aiPTI_Integer != prop->mType || sizeof(int) > prop->mDataLength) this->ReportError("Material property %s is expected to be an integer",prop->mKey); - const char* sz = &prop->mKey->data[iLen]; + const char* sz = &prop->mKey.data[iLen]; if (*sz) { ++sz; @@ -546,7 +547,8 @@ void ValidateDSProcess::Validate( const aiMaterial* pMaterial) // check all predefined types if (aiPTI_String == prop->mType) { - if (prop->mDataLength < sizeof(aiString)) + // FIX: strings are now stored in a less expensive way ... + if (prop->mDataLength < sizeof(size_t) + ((const aiString*)prop->mData)->length + 1) { this->ReportError("aiMaterial::mProperties[%i].mDataLength is " "too small to contain a string (%i, needed: %i)", diff --git a/code/aiAssert.cpp b/code/aiAssert.cpp index ee9ef7497..f30083be3 100644 --- a/code/aiAssert.cpp +++ b/code/aiAssert.cpp @@ -12,7 +12,7 @@ void Assimp::aiAssert (bool expression, const std::string &message, unsigned int { if (!expression) { - std::cerr << "File :" << file << ", line " << uiLine << " : " << message << std::endl; + std::cout << "File :" << file << ", line " << uiLine << " : " << message << std::endl; #ifdef _WIN32 #ifndef __GNUC__ diff --git a/code/fast_atof.h b/code/fast_atof.h index 7d60b2a81..c4788f2c1 100644 --- a/code/fast_atof.h +++ b/code/fast_atof.h @@ -48,6 +48,25 @@ inline unsigned int strtol10( const char* in, const char** out=0) return value; } + +// specal version of the function, providing higher accuracy +inline uint64_t strtol10_64( const char* in, const char** out=0) +{ + uint64_t value = 0; + + while ( 1 ) + { + if ( *in < '0' || *in > '9' ) + break; + + value = ( value * 10 ) + ( *in - '0' ); + ++in; + } + if (out) + *out = in; + return value; +} + //! Provides a fast function for converting a string into a float, //! about 6 times faster than atof in win32. // If you find any bugs, please send them to me, niko (at) irrlicht3d.org. @@ -64,17 +83,24 @@ inline const char* fast_atof_move( const char* c, float& out) } //f = (float)strtol(c, &t, 10); - f = (float) strtol10 ( c, &c ); + f = (float) strtol10_64 ( c, &c ); if (*c == '.') { ++c; + // NOTE: The original implementation is highly unaccurate here + // The precision of a single IEEE 754 float is not high enough + // everything behind the 6th digit tends to be more inaccurate + // than it would need to be. + // Casting to double seems to solve the problem. + // strtol_64 is used to prevent integer overflow. + //float pl = (float)strtol(c, &t, 10); - float pl = (float) strtol10 ( c, &t ); + double pl = (double) strtol10_64 ( c, &t ); pl *= fast_atof_table[t-c]; - f += pl; + f += (float)pl; c = t; diff --git a/include/aiDefines.h b/include/aiDefines.h index 47bbb644b..bab44f3a0 100644 --- a/include/aiDefines.h +++ b/include/aiDefines.h @@ -94,4 +94,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # endif #endif +#ifdef _DEBUG +# define AI_DEBUG_INVALIDATE_PTR(ptr) ptr = 0; +#else +# define AI_DEBUG_INVALIDATE_PTR(ptr) +#endif + #endif // !! AI_DEFINES_H_INC diff --git a/include/aiMaterial.h b/include/aiMaterial.h index 0a18b60d3..269972741 100644 --- a/include/aiMaterial.h +++ b/include/aiMaterial.h @@ -213,7 +213,7 @@ struct aiMaterialProperty * * Keys are case insensitive. */ - C_STRUCT aiString* mKey; + C_STRUCT aiString mKey; /** Size of the buffer mData is pointing to, in bytes * This value may not be 0. diff --git a/include/aiQuaternion.h b/include/aiQuaternion.h index 963886d82..b2be71906 100644 --- a/include/aiQuaternion.h +++ b/include/aiQuaternion.h @@ -66,6 +66,9 @@ struct aiQuaternion /** Construct from an axis angle pair */ aiQuaternion( aiVector3D axis, float angle); + /** Construct from a normalized quaternion stored in a vec3 */ + aiQuaternion( aiVector3D normalized); + /** Returns a matrix representation of the quaternion */ aiMatrix3x3 GetMatrix() const; @@ -170,7 +173,22 @@ inline aiQuaternion::aiQuaternion( aiVector3D axis, float angle) z = axis.z * sin_a; w = cos_a; } +// --------------------------------------------------------------------------- +// Construction from am existing, normalized quaternion +inline aiQuaternion::aiQuaternion( aiVector3D normalized) +{ + x = normalized.x; + y = normalized.y; + z = normalized.z; + float t = 1.0f - (normalized.x * normalized.x) - + (normalized.y * normalized.y) - (normalized.z * normalized.z); + + if (t < 0.0f) + w = 0.0f; + else w = sqrt (t); + +} diff --git a/test/STL/triangle.stl b/test/STL/triangle.stl new file mode 100644 index 000000000..e2dea2f8a --- /dev/null +++ b/test/STL/triangle.stl @@ -0,0 +1,9 @@ +solid testTriangle + facet normal 0.0 0.0 1.0 + outer loop + vertex 1.0 1.0 0.0 + vertex -1.0 1.0 0.0 + vertex 0.0 -1.0 0.0 + endloop + endfacet +endsolid \ No newline at end of file diff --git a/test/unit/ExecuteStepWin32.bat b/test/unit/ExecuteUnitTestWin32.bat similarity index 100% rename from test/unit/ExecuteStepWin32.bat rename to test/unit/ExecuteUnitTestWin32.bat diff --git a/test/unit/ExecuteStepWin64.bat b/test/unit/ExecuteUnitTestWin64.bat similarity index 100% rename from test/unit/ExecuteStepWin64.bat rename to test/unit/ExecuteUnitTestWin64.bat diff --git a/test/unit/utGenNormals.cpp b/test/unit/utGenNormals.cpp index e69de29bb..7de461ea5 100644 --- a/test/unit/utGenNormals.cpp +++ b/test/unit/utGenNormals.cpp @@ -0,0 +1,34 @@ + +#include "utGenNormals.h" + + +CPPUNIT_TEST_SUITE_REGISTRATION (GenNormalsTest); + +void GenNormalsTest :: setUp (void) +{ + this->piProcess = new GenVertexNormalsProcess(); + this->pcMesh = new aiMesh(); + pcMesh->mNumFaces = 1; + pcMesh->mFaces = new aiFace[1]; + pcMesh->mFaces[0].mIndices = new unsigned int[pcMesh->mFaces[0].mNumIndices = 3]; + pcMesh->mFaces[0].mIndices[0] = 0; + pcMesh->mFaces[0].mIndices[1] = 1; + pcMesh->mFaces[0].mIndices[2] = 1; + pcMesh->mNumVertices = 3; + pcMesh->mVertices = new aiVector3D[3]; + pcMesh->mVertices[0] = aiVector3D(0.0f,1.0f,6.0f); + pcMesh->mVertices[1] = aiVector3D(2.0f,3.0f,1.0f); + pcMesh->mVertices[2] = aiVector3D(3.0f,2.0f,4.0f); +} + +void GenNormalsTest :: tearDown (void) +{ + delete this->pcMesh; + delete this->piProcess; +} + +void GenNormalsTest :: testSimpleTriangle (void) +{ + this->piProcess->GenMeshVertexNormals(pcMesh); + CPPUNIT_ASSERT(0 != pcMesh->mNormals); +} diff --git a/test/unit/utGenNormals.h b/test/unit/utGenNormals.h new file mode 100644 index 000000000..ea5a7cb09 --- /dev/null +++ b/test/unit/utGenNormals.h @@ -0,0 +1,37 @@ +#ifndef TESTNORMALS_H +#define TESTNORMALS_H + +#include +#include + +#include +#include +#include +#include + + +using namespace std; +using namespace Assimp; + +class GenNormalsTest : public CPPUNIT_NS :: TestFixture +{ + CPPUNIT_TEST_SUITE (GenNormalsTest); + CPPUNIT_TEST (testSimpleTriangle); + CPPUNIT_TEST_SUITE_END (); + + public: + void setUp (void); + void tearDown (void); + + protected: + + void testSimpleTriangle (void); + + private: + + + aiMesh* pcMesh; + GenVertexNormalsProcess* piProcess; +}; + +#endif diff --git a/test/unit/utMaterialSystem.cpp b/test/unit/utMaterialSystem.cpp index e69de29bb..ebfb067b6 100644 --- a/test/unit/utMaterialSystem.cpp +++ b/test/unit/utMaterialSystem.cpp @@ -0,0 +1,82 @@ + +#include "utMaterialSystem.h" + + +CPPUNIT_TEST_SUITE_REGISTRATION (MaterialSystemTest); + +void MaterialSystemTest :: setUp (void) +{ + this->pcMat = new MaterialHelper(); +} + +void MaterialSystemTest :: tearDown (void) +{ + delete this->pcMat; +} + + +void MaterialSystemTest :: testFloatProperty (void) +{ + float pf = 150392.63f; + this->pcMat->AddProperty(&pf,1,"testKey1"); + pf = 0.0f; + + CPPUNIT_ASSERT(AI_SUCCESS == pcMat->Get("testKey1",pf)); + CPPUNIT_ASSERT(pf == 150392.63f); +} + +void MaterialSystemTest :: testFloatArrayProperty (void) +{ + float pf[] = {0.0f,1.0f,2.0f,3.0f}; + unsigned int pMax = sizeof(pf) / sizeof(float); + this->pcMat->AddProperty(&pf,pMax,"testKey2"); + pf[0] = pf[1] = pf[2] = pf[3] = 12.0f; + + CPPUNIT_ASSERT(AI_SUCCESS == pcMat->Get("testKey2",pf,&pMax)); + CPPUNIT_ASSERT(pMax == sizeof(pf) / sizeof(float)); + CPPUNIT_ASSERT(!pf[0] && 1.0f == pf[1] && 2.0f == pf[2] && 3.0f == pf[3] ); +} + +void MaterialSystemTest :: testIntProperty (void) +{ + int pf = 15039263; + this->pcMat->AddProperty(&pf,1,"testKey3"); + pf = 12; + + CPPUNIT_ASSERT(AI_SUCCESS == pcMat->Get("testKey3",pf)); + CPPUNIT_ASSERT(pf == 15039263); +} + +void MaterialSystemTest :: testIntArrayProperty (void) +{ + int pf[] = {0,1,2,3}; + unsigned int pMax = sizeof(pf) / sizeof(int); + this->pcMat->AddProperty(&pf,pMax,"testKey4"); + pf[0] = pf[1] = pf[2] = pf[3] = 12; + + CPPUNIT_ASSERT(AI_SUCCESS == pcMat->Get("testKey4",pf,&pMax)); + CPPUNIT_ASSERT(pMax == sizeof(pf) / sizeof(int)); + CPPUNIT_ASSERT(!pf[0] && 1 == pf[1] && 2 == pf[2] && 3 == pf[3] ); +} + +void MaterialSystemTest :: testColorProperty (void) +{ + aiColor4D clr; + clr.r = 2.0f;clr.g = 3.0f;clr.b = 4.0f;clr.a = 5.0f; + this->pcMat->AddProperty(&clr,1,"testKey5"); + clr.b = 1.0f; + clr.a = clr.g = clr.r = 0.0f; + + CPPUNIT_ASSERT(AI_SUCCESS == pcMat->Get("testKey5",clr)); + CPPUNIT_ASSERT(clr.r == 2.0f && clr.g == 3.0f && clr.b == 4.0f && clr.a == 5.0f); +} + +void MaterialSystemTest :: testStringProperty (void) +{ + aiString s; + s.Set("Hello, this is a small test"); + this->pcMat->AddProperty(&s,"testKey6"); + s.Set("358358"); + CPPUNIT_ASSERT(AI_SUCCESS == pcMat->Get("testKey6",s)); + CPPUNIT_ASSERT(!::strcmp(s.data,"Hello, this is a small test")); +} diff --git a/test/unit/utMaterialSystem.h b/test/unit/utMaterialSystem.h new file mode 100644 index 000000000..37eaeebe0 --- /dev/null +++ b/test/unit/utMaterialSystem.h @@ -0,0 +1,45 @@ +#ifndef TESTMATERIALS_H +#define TESTMATERIALS_H + +#include +#include + +#include +#include +#include +#include + + +using namespace std; +using namespace Assimp; + +class MaterialSystemTest : public CPPUNIT_NS :: TestFixture +{ + CPPUNIT_TEST_SUITE (MaterialSystemTest); + CPPUNIT_TEST (testFloatProperty); + CPPUNIT_TEST (testFloatArrayProperty); + CPPUNIT_TEST (testIntProperty); + CPPUNIT_TEST (testIntArrayProperty); + CPPUNIT_TEST (testColorProperty); + CPPUNIT_TEST (testStringProperty); + CPPUNIT_TEST_SUITE_END (); + + public: + void setUp (void); + void tearDown (void); + + protected: + + void testFloatProperty (void); + void testFloatArrayProperty (void); + void testIntProperty (void); + void testIntArrayProperty (void); + void testColorProperty (void); + void testStringProperty (void); + + private: + + MaterialHelper* pcMat; +}; + +#endif diff --git a/test/unit/utGenSmoothNormals.cpp b/test/unit/utPretransformVertices.h similarity index 100% rename from test/unit/utGenSmoothNormals.cpp rename to test/unit/utPretransformVertices.h diff --git a/test/unit/utRemoveRedundantMaterials.cpp b/test/unit/utRemoveRedundantMaterials.cpp new file mode 100644 index 000000000..a404968da --- /dev/null +++ b/test/unit/utRemoveRedundantMaterials.cpp @@ -0,0 +1,101 @@ +#include "utRemoveRedundantMaterials.h" +#include "aiPostProcess.h" +#include + +CPPUNIT_TEST_SUITE_REGISTRATION (RemoveRedundantMatsTest); + + + +aiMaterial* getUniqueMaterial1() +{ + // setup an unique name for each material - this shouldn't care + aiString mTemp; + mTemp.Set("UniqueMat1"); + + MaterialHelper* pcMat = new MaterialHelper(); + pcMat->AddProperty(&mTemp,AI_MATKEY_NAME); + float f = 2.0f; + pcMat->AddProperty(&f, 1, AI_MATKEY_BUMPSCALING); + pcMat->AddProperty(&f, 1, AI_MATKEY_SHININESS_STRENGTH); + return pcMat; +} + +aiMaterial* getUniqueMaterial2() +{ + // setup an unique name for each material - this shouldn't care + aiString mTemp; + mTemp.Set("UniqueMat2"); + + MaterialHelper* pcMat = new MaterialHelper(); + pcMat->AddProperty(&mTemp,AI_MATKEY_NAME); + float f = 4.0f;int i = 1; + pcMat->AddProperty(&f, 1, AI_MATKEY_BUMPSCALING); + pcMat->AddProperty(&i, 1, AI_MATKEY_ENABLE_WIREFRAME); + return pcMat; +} + +aiMaterial* getUniqueMaterial3() +{ + // setup an unique name for each material - this shouldn't care + aiString mTemp; + mTemp.Set("UniqueMat3"); + + MaterialHelper* pcMat = new MaterialHelper(); + pcMat->AddProperty(&mTemp,AI_MATKEY_NAME); + return pcMat; +} + +void RemoveRedundantMatsTest :: setUp (void) +{ + // construct the process + this->piProcess = new RemoveRedundantMatsProcess(); + + // create a scene with 5 materials (2 is a duplicate of 0, 3 of 1) + this->pcScene1 = new aiScene(); + this->pcScene1->mNumMaterials = 5; + this->pcScene1->mMaterials = new aiMaterial*[5]; + + this->pcScene1->mMaterials[0] = getUniqueMaterial1(); + this->pcScene1->mMaterials[1] = getUniqueMaterial2(); + this->pcScene1->mMaterials[4] = getUniqueMaterial3(); + + // setup an unique name for each material - this shouldn't care + aiString mTemp; + mTemp.length = 1; + mTemp.data[0] = 48; + mTemp.data[1] = 0; + + MaterialHelper* pcMat; + this->pcScene1->mMaterials[2] = pcMat = new MaterialHelper(); + MaterialHelper::CopyPropertyList(pcMat,(const MaterialHelper*)this->pcScene1->mMaterials[0]); + pcMat->AddProperty(&mTemp,AI_MATKEY_NAME); + mTemp.data[0]++; + + this->pcScene1->mMaterials[3] = pcMat = new MaterialHelper(); + MaterialHelper::CopyPropertyList(pcMat,(const MaterialHelper*)this->pcScene1->mMaterials[1]); + pcMat->AddProperty(&mTemp,AI_MATKEY_NAME); + mTemp.data[0]++; +} + +void RemoveRedundantMatsTest :: tearDown (void) +{ + delete this->piProcess; + delete this->pcScene1; +} + +void RemoveRedundantMatsTest :: testRedundantMaterials (void) +{ + this->piProcess->Execute(this->pcScene1); + CPPUNIT_ASSERT_EQUAL(this->pcScene1->mNumMaterials,3u); + CPPUNIT_ASSERT(0 != this->pcScene1->mMaterials && + 0 != this->pcScene1->mMaterials[0] && + 0 != this->pcScene1->mMaterials[1] && + 0 != this->pcScene1->mMaterials[2]); + + aiString sName; + CPPUNIT_ASSERT(AI_SUCCESS == aiGetMaterialString(this->pcScene1->mMaterials[2], + AI_MATKEY_NAME,&sName)); + + CPPUNIT_ASSERT(!::strcmp( sName.data, "UniqueMat3" )); + +} diff --git a/test/unit/utRemoveRedundantMaterials.h b/test/unit/utRemoveRedundantMaterials.h new file mode 100644 index 000000000..fb9e508af --- /dev/null +++ b/test/unit/utRemoveRedundantMaterials.h @@ -0,0 +1,39 @@ +#ifndef TESTRRM_H +#define TESTRRM_H + +#include +#include + +#include +#include +#include +#include +#include + +using namespace std; +using namespace Assimp; + +class RemoveRedundantMatsTest : public CPPUNIT_NS :: TestFixture +{ + CPPUNIT_TEST_SUITE (RemoveRedundantMatsTest); + CPPUNIT_TEST (testRedundantMaterials); + CPPUNIT_TEST_SUITE_END (); + + public: + void setUp (void); + void tearDown (void); + + protected: + + void testRedundantMaterials (void); + + + private: + + RemoveRedundantMatsProcess* piProcess; + + aiScene* pcScene1; + aiScene* pcScene2; +}; + +#endif diff --git a/tools/assimp_view/Display.cpp b/tools/assimp_view/Display.cpp index b1533196d..5573ffc9a 100644 --- a/tools/assimp_view/Display.cpp +++ b/tools/assimp_view/Display.cpp @@ -475,7 +475,7 @@ int CDisplay::AddTextureToDisplayList(unsigned int iType, // find out whether this is the default texture or not - if (piTexture) + if (piTexture && *piTexture) { // {9785DA94-1D96-426b-B3CB-BADC36347F5E} static const GUID guidPrivateData = diff --git a/workspaces/vc8/UnitTest.vcproj b/workspaces/vc8/UnitTest.vcproj index 3b3464522..623ebeac6 100644 --- a/workspaces/vc8/UnitTest.vcproj +++ b/workspaces/vc8/UnitTest.vcproj @@ -683,10 +683,6 @@ RelativePath="..\..\test\unit\utGenNormals.cpp" > - - @@ -711,6 +707,10 @@ RelativePath="..\..\test\unit\utRemoveComments.cpp" > + + @@ -729,14 +729,30 @@ Filter="h;hpp;hxx;hm;inl;inc;xsd" UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" > + + + + + + + + diff --git a/workspaces/vc8/assimp.vcproj b/workspaces/vc8/assimp.vcproj index 391c38fcf..3d1dec99b 100644 --- a/workspaces/vc8/assimp.vcproj +++ b/workspaces/vc8/assimp.vcproj @@ -730,6 +730,10 @@ RelativePath="..\..\code\GenVertexNormalsProcess.h" > + + @@ -799,7 +803,7 @@ > + + + + + + + + + + + + + + + +