diff --git a/code/BaseImporter.cpp b/code/BaseImporter.cpp index 39f411364..8c7ce5db1 100644 --- a/code/BaseImporter.cpp +++ b/code/BaseImporter.cpp @@ -153,8 +153,10 @@ struct LoadRequest , refCnt (1) , scene (NULL) , loaded (false) - , map (*_map) - {} + { + if (_map) + map = *_map; + } const std::string file; unsigned int flags; @@ -163,8 +165,9 @@ struct LoadRequest bool loaded; BatchLoader::PropertyMap map; - bool operator== (const std::string& f) - {return file == f;} + bool operator== (const std::string& f) { + return file == f; + } }; // ------------------------------------------------------------------------------------------------ diff --git a/code/MD3Loader.cpp b/code/MD3Loader.cpp index a20a35fbd..786eabac8 100644 --- a/code/MD3Loader.cpp +++ b/code/MD3Loader.cpp @@ -48,13 +48,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "MaterialSystem.h" #include "StringComparison.h" #include "ByteSwap.h" +#include "SceneCombiner.h" +#include "GenericProperty.h" using namespace Assimp; - // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer MD3Importer::MD3Importer() +: configFrameID (0) +, configHandleMP (true) {} // ------------------------------------------------------------------------------------------------ @@ -107,7 +110,7 @@ void MD3Importer::ValidateHeaderOffsets() void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurf) { // calculate the relative offset of the surface - int32_t ofs = int32_t((const unsigned char*)pcSurf-this->mBuffer); + const int32_t ofs = int32_t((const unsigned char*)pcSurf-this->mBuffer); if (pcSurf->OFS_TRIANGLES + ofs + pcSurf->NUM_TRIANGLES * sizeof(MD3::Triangle) > fileSize || pcSurf->OFS_SHADERS + ofs + pcSurf->NUM_SHADER * sizeof(MD3::Shader) > fileSize || @@ -125,6 +128,13 @@ void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurf) if (pcSurf->NUM_FRAMES > AI_MD3_MAX_FRAMES) DefaultLogger::get()->warn("The model contains more frames than Quake 3 supports"); } + +// ------------------------------------------------------------------------------------------------ +void MD3Importer::GetExtensionList(std::string& append) +{ + append.append("*.md3"); +} + // ------------------------------------------------------------------------------------------------ // Setup configuration properties void MD3Importer::SetupProperties(const Importer* pImp) @@ -136,13 +146,107 @@ void MD3Importer::SetupProperties(const Importer* pImp) if(0xffffffff == configFrameID) { configFrameID = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0); } + + // AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART + configHandleMP = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART,1)); +} + +// ------------------------------------------------------------------------------------------------ +// Read a multi-part Q3 player model +bool MD3Importer::ReadMultipartFile() +{ + std::string::size_type s = mFile.find_last_of('/'); + if (s == std::string::npos) { + s = mFile.find_last_of('\\'); + } + if (s == std::string::npos) { + s = 0; + } + else ++s; + std::string filename = mFile.substr(s), path = mFile.substr(0,s); + for( std::string::iterator it = filename .begin(); it != filename.end(); ++it) + *it = tolower( *it); + + if (filename == "lower.md3" || filename == "upper.md3" || filename == "head.md3"){ + std::string lower = path + "lower.md3"; + std::string upper = path + "upper.md3"; + std::string head = path + "head.md3"; + + // ensure we won't try to load ourselves recursively + BatchLoader::PropertyMap props; + SetGenericProperty( props.ints, AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART, 0, NULL); + + // now read these three files + BatchLoader batch(mIOHandler); + batch.AddLoadRequest(lower,0,&props); + batch.AddLoadRequest(upper,0,&props); + batch.AddLoadRequest(head,0,&props); + batch.LoadAll(); + + // now construct a dummy scene to place these three parts in + aiScene* master = new aiScene(); + aiNode* nd = master->mRootNode = new aiNode(); + nd->mName.Set(""); + + // ... and get them. We need all of them. + aiScene* scene_lower = batch.GetImport(lower); + if (!scene_lower) + throw new ImportErrorException("M3D: Failed to read multipart model, lower.md3 fails to load"); + + aiScene* scene_upper = batch.GetImport(upper); + if (!scene_upper) + throw new ImportErrorException("M3D: Failed to read multipart model, upper.md3 fails to load"); + + aiScene* scene_head = batch.GetImport(head); + if (!scene_head) + throw new ImportErrorException("M3D: Failed to read multipart model, head.md3 fails to load"); + + // build attachment infos. search for typical Q3 tags + std::vector attach; + + // original root + attach.push_back(AttachmentInfo(scene_lower, nd)); + + // tag_torso + aiNode* tag_torso = scene_lower->mRootNode->FindNode("tag_torso"); + if (!tag_torso) { + throw new ImportErrorException("M3D: Unable to find attachment tag: tag_torso expected"); + } + attach.push_back(AttachmentInfo(scene_upper,tag_torso)); + + // tag_head + aiNode* tag_head = scene_upper->mRootNode->FindNode("tag_head"); + if (!tag_head) { + throw new ImportErrorException("M3D: Unable to find attachment tag: tag_head expected"); + } + attach.push_back(AttachmentInfo(scene_head,tag_head)); + + // and merge the scenes + SceneCombiner::MergeScenes(&mScene,master, attach, + AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | + AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES | + AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS); + + return true; + } + return false; } // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. -void MD3Importer::InternReadFile( - const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) +void MD3Importer::InternReadFile( const std::string& pFile, + aiScene* pScene, IOSystem* pIOHandler) { + mFile = pFile; + mScene = pScene; + mIOHandler = pIOHandler; + + // Load multi-part model file, if necessary + if (configHandleMP) { + if (ReadMultipartFile()) + return; + } + boost::scoped_ptr file( pIOHandler->Open( pFile)); // Check whether we can read from the file @@ -301,8 +405,6 @@ void MD3Importer::InternReadFile( 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; @@ -355,18 +457,18 @@ void MD3Importer::InternReadFile( MaterialHelper* pcHelper = new MaterialHelper(); if (szEndDir2) { + aiString szString; 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. Skipping"); + DefaultLogger::get()->warn("Texture file name has zero length. Using default name"); + szString.Set("dummy_texture.bmp"); } + pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0)); } int iMode = (int)aiShadingMode_Gouraud; @@ -449,8 +551,8 @@ void MD3Importer::InternReadFile( // copy rest of transformation for (unsigned int a = 0; a < 3;++a) { for (unsigned int m = 0; m < 3;++m) { - nd->mTransformation[a][m] = pcTags->orientation[a][m]; - AI_SWAP4(nd->mTransformation[a][m]); + nd->mTransformation[m][a] = pcTags->orientation[a][m]; + AI_SWAP4(nd->mTransformation[m][a]); } } } diff --git a/code/MD3Loader.h b/code/MD3Loader.h index 59922dc7b..35653dac0 100644 --- a/code/MD3Loader.h +++ b/code/MD3Loader.h @@ -90,30 +90,35 @@ protected: /** Called by Importer::GetExtensionList() for each loaded importer. * See BaseImporter::GetExtensionList() for details */ - void GetExtensionList(std::string& append) - { - append.append("*.md3"); - } + void GetExtensionList(std::string& append); // ------------------------------------------------------------------- /** Imports the given file into the given scene structure. - * See BaseImporter::InternReadFile() for details - */ + * See BaseImporter::InternReadFile() for details + */ void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); - // ------------------------------------------------------------------- /** Validate offsets in the header - */ + */ void ValidateHeaderOffsets(); void ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurfHeader); + // ------------------------------------------------------------------- + /** Read a Q3 multipart file + * @return true if multi part has been processed + */ + bool ReadMultipartFile(); + protected: /** Configuration option: frame to be loaded */ unsigned int configFrameID; + /** Configuration option: process multi-part files */ + bool configHandleMP; + /** Header of the MD3 file */ BE_NCONST MD3::Header* pcHeader; @@ -122,6 +127,15 @@ protected: /** Size of the file, in bytes */ unsigned int fileSize; + + /** Current file name */ + std::string mFile; + + /** Output scene to be filled */ + aiScene* mScene; + + /** IO system to be used to access the data*/ + IOSystem* mIOHandler; }; } // end of namespace Assimp diff --git a/code/SceneCombiner.cpp b/code/SceneCombiner.cpp index 44ad161f9..1d426e201 100644 --- a/code/SceneCombiner.cpp +++ b/code/SceneCombiner.cpp @@ -129,7 +129,6 @@ inline void PrefixString(aiString& string,const char* prefix, unsigned int len) void SceneCombiner::AddNodePrefixes(aiNode* node, const char* prefix, unsigned int len) { ai_assert(NULL != prefix); - PrefixString(node->mName,prefix,len); // Process all children recursively @@ -177,8 +176,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest,std::vector& src, master->mRootNode->mName.Set(""); std::vector srcList (src.size()); - for (unsigned int i = 0; i < srcList.size();++i) - { + for (unsigned int i = 0; i < srcList.size();++i) { srcList[i] = AttachmentInfo(src[i],master->mRootNode); } @@ -190,7 +188,6 @@ void SceneCombiner::MergeScenes(aiScene** _dest,std::vector& src, void SceneCombiner::AttachToGraph (aiNode* attach, std::vector& srcList) { unsigned int cnt; - for (cnt = 0; cnt < attach->mNumChildren;++cnt) AttachToGraph(attach->mChildren[cnt],srcList); @@ -198,15 +195,13 @@ void SceneCombiner::AttachToGraph (aiNode* attach, std::vector::iterator it = srcList.begin(); it != srcList.end(); ++it) { - if ((*it).attachToNode == attach) + if ((*it).attachToNode == attach && !(*it).resolved) ++cnt; } - if (cnt) - { + if (cnt) { aiNode** n = new aiNode*[cnt+attach->mNumChildren]; - if (attach->mNumChildren) - { + if (attach->mNumChildren) { ::memcpy(n,attach->mChildren,sizeof(void*)*attach->mNumChildren); delete[] attach->mChildren; } @@ -215,14 +210,15 @@ void SceneCombiner::AttachToGraph (aiNode* attach, std::vectormNumChildren; attach->mNumChildren += cnt; - for (unsigned int i = 0; i < srcList.size();++i) - { + for (unsigned int i = 0; i < srcList.size();++i) { NodeAttachmentInfo& att = srcList[i]; - if (att.attachToNode == attach) - { + if (att.attachToNode == attach && !att.resolved) { *n = att.node; (**n).mParent = attach; ++n; + + // mark this attachment as resolved + att.resolved = true; } } } @@ -261,8 +257,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, std::vector src (srcList.size()+1); src[0].scene = master; - for (unsigned int i = 0; i < srcList.size();++i) - { + for (unsigned int i = 0; i < srcList.size();++i) { src[i+1] = SceneHelper( srcList[i].scene ); } @@ -272,7 +267,6 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, // this helper array is used as lookup table several times std::vector offset(src.size()); - // Find duplicate scenes for (unsigned int i = 0; i < src.size();++i) { @@ -316,8 +310,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, { SceneHelper* cur = &src[n]; - if (n == duplicates[n] || flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) - { + if (n == duplicates[n] || flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) { dest->mNumTextures += (*cur)->mNumTextures; dest->mNumMaterials += (*cur)->mNumMaterials; dest->mNumMeshes += (*cur)->mNumMeshes; @@ -378,11 +371,8 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, if ((*cur)->mNumTextures != dest->mNumTextures) { - // We need to update all texture indices of the mesh. - // So we need to search for a material property like - // that follows the following pattern: "$tex.file.." - // where s is the texture type (i.e. diffuse) and n is - // the index of the texture. + // We need to update all texture indices of the mesh. So we need to search for + // a material property called '$tex.file' for (unsigned int a = 0; a < (*pip)->mNumProperties;++a) { @@ -393,8 +383,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, // In this case the property looks like this: *, // where n is the index of the texture. aiString& s = *((aiString*)prop->mData); - if ('*' == s.data[0]) - { + if ('*' == s.data[0]) { // Offset the index and write it back .. const unsigned int idx = strtol10(&s.data[1]) + offset[n]; ASSIMP_itoa10(&s.data[1],sizeof(s.data)-1,idx); @@ -428,8 +417,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, SceneHelper* cur = &src[n]; for (unsigned int i = 0; i < (*cur)->mNumMeshes;++i) { - if (n != duplicates[n]) - { + if (n != duplicates[n]) { if ( flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) Copy(pip, (*cur)->mMeshes[i]); @@ -439,7 +427,6 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, // update the material index of the mesh (*pip)->mMaterialIndex += offset[n]; - ++pip; } @@ -481,8 +468,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, { Copy( &node, (*cur)->mRootNode ); - if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) - { + if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) { // (note:) they are already 'offseted' by offset[duplicates[n]] OffsetNodeMeshIndices(node,offset[n] - offset[duplicates[n]]); } @@ -493,7 +479,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, OffsetNodeMeshIndices(node,offset[n]); } if (n) // src[0] is the master node - nodes.push_back(NodeAttachmentInfo( node,srcList[n-1].attachToNode )); + nodes.push_back(NodeAttachmentInfo( node,srcList[n-1].attachToNode,n )); // -------------------------------------------------------------------- // Copy light sources @@ -508,8 +494,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, // -------------------------------------------------------------------- // Copy cameras - for (unsigned int i = 0; i < (*cur)->mNumCameras;++i,++ppCameras) - { + for (unsigned int i = 0; i < (*cur)->mNumCameras;++i,++ppCameras) { if (n != duplicates[n]) // duplicate scene? { Copy(ppCameras, (*cur)->mCameras[i]); @@ -519,8 +504,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, // -------------------------------------------------------------------- // Copy animations - for (unsigned int i = 0; i < (*cur)->mNumAnimations;++i,++ppAnims) - { + for (unsigned int i = 0; i < (*cur)->mNumAnimations;++i,++ppAnims) { if (n != duplicates[n]) // duplicate scene? { Copy(ppAnims, (*cur)->mAnimations[i]); @@ -529,8 +513,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, } } - for ( unsigned int n = 1; n < src.size();++n ) - { + for ( unsigned int n = 1; n < src.size();++n ) { SceneHelper* cur = &src[n]; // -------------------------------------------------------------------- // Add prefixes @@ -542,8 +525,7 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, for (unsigned int i = 0; i < (*cur)->mNumCameras;++i) PrefixString(dest->mCameras[i]->mName,(*cur).id,(*cur).idlen); - for (unsigned int i = 0; i < (*cur)->mNumAnimations;++i) - { + for (unsigned int i = 0; i < (*cur)->mNumAnimations;++i) { aiAnimation* anim = dest->mAnimations[i]; PrefixString(anim->mName,(*cur).id,(*cur).idlen); @@ -551,7 +533,6 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, for (unsigned int a = 0; a < anim->mNumChannels;++a) PrefixString(anim->mChannels[a]->mNodeName,(*cur).id,(*cur).idlen); } - AddNodePrefixes(nodes[n-1].node,(*cur).id,(*cur).idlen); } } @@ -560,10 +541,31 @@ void SceneCombiner::MergeScenes(aiScene** _dest, aiScene* master, AttachToGraph ( master, nodes); dest->mRootNode = master->mRootNode; + // Check whether we succeeded at building the output graph + for (std::vector ::iterator it = nodes.begin(); + it != nodes.end(); ++it) + { + if (!(*it).resolved) { + if (flags & AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS) { + // search for this attachment point in all other imported scenes, too. + for ( unsigned int n = 0; n < src.size();++n ) { + if (n != (*it).src_idx) { + AttachToGraph(src[n].scene,nodes); + if ((*it).resolved) + break; + } + } + } + if (!(*it).resolved) { + DefaultLogger::get()->error(std::string("SceneCombiner: Failed to resolve attachment ") + + (*it).node->mName.data + " " + (*it).attachToNode->mName.data); + } + } + } + // now delete all input scenes. Make sure duplicate scenes aren't // deleted more than one time - for ( unsigned int n = 0; n < src.size();++n ) - { + for ( unsigned int n = 0; n < src.size();++n ) { if (n != duplicates[n]) // duplicate scene? continue; diff --git a/code/SceneCombiner.h b/code/SceneCombiner.h index 5095d13d3..769403bfa 100644 --- a/code/SceneCombiner.h +++ b/code/SceneCombiner.h @@ -75,33 +75,60 @@ struct NodeAttachmentInfo NodeAttachmentInfo() : node (NULL) , attachToNode (NULL) + , resolved (false) + , src_idx (0xffffffff) {} - NodeAttachmentInfo(aiNode* _scene, aiNode* _attachToNode) + NodeAttachmentInfo(aiNode* _scene, aiNode* _attachToNode,size_t idx) : node (_scene) , attachToNode (_attachToNode) + , resolved (false) + , src_idx (idx) {} aiNode* node; aiNode* attachToNode; + bool resolved; + size_t src_idx; }; -// generate unique names for all named scene items +// --------------------------------------------------------------------------- +/** @def AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES + * Generate unique names for all named scene items + */ #define AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES 0x1 -// generate unique names for materials, too + +/** @def AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES + * Generate unique names for materials, too. + * This is not absolutely required to pass the validation. + */ #define AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES 0x2 -// use deep copies of duplicate scenes + +/** @def AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY + * Use deep copies of duplicate scenes + */ #define AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY 0x4 +/** @def AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS + * If attachment nodes are not found in the given master scene, + * search the other imported scenes for them in an any order. + */ +#define AI_INT_MERGE_SCENE_RESOLVE_CROSS_ATTACHMENTS 0x8 + +/** @def AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY + * Can be combined with AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES. + * Unique names are generated, but only if this is absolutely + * required (if there would be conflicts otherwuse.) + */ +#define AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY 0x10 + typedef std::pair BoneSrcIndex; // --------------------------------------------------------------------------- -/** \brief Helper data structure for SceneCombiner::MergeBones. - * +/** @brief Helper data structure for SceneCombiner::MergeBones. */ -struct BoneWithHash : public std::pair -{ +struct BoneWithHash : public std::pair { std::vector pSrcBones; }; diff --git a/include/aiConfig.h b/include/aiConfig.h index 862e55ac5..d074bcc62 100644 --- a/include/aiConfig.h +++ b/include/aiConfig.h @@ -136,6 +136,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS "imp.ase.reconn" +// --------------------------------------------------------------------------- +/** @brief Configures the M3D loader to process multi-part player models. + * + * These models usually consist of 3 files, lower.md3, upper.md3 and + * head.md3. If this property is set to true, Assimp will try to load and + * combine all three files if one of them is loaded. + * Property type: integer (0: false; !0: true). Default value: true. + */ +#define AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART "IMPORT_MD3_HANDLE_MULTIPART" + // --------------------------------------------------------------------------- /** @brief Configures the LWO loader to load just one layer from the model.