2
0
Эх сурвалжийг харах

MD3
- rough support for multi-part player models.

SceneCombiner
- added support for cross-attachments of nodes in the whole graph

git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@345 67173fc5-114c-0410-ac8e-9d2fd5bffc1f

aramis_acg 16 жил өмнө
parent
commit
d70c092b71

+ 7 - 4
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;
+	}
 };
 
 // ------------------------------------------------------------------------------------------------

+ 114 - 12
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("<M3D_Player>");
+
+		// ... 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<AttachmentInfo> 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<IOStream> 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]);
 				}
 			}
 		}

+ 22 - 8
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

+ 44 - 42
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<aiScene*>& src,
 	master->mRootNode->mName.Set("<MergeRoot>");
 
 	std::vector<AttachmentInfo> 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<aiScene*>& src,
 void SceneCombiner::AttachToGraph (aiNode* attach, std::vector<NodeAttachmentInfo>& 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<NodeAttachmentInf
 	for (std::vector<NodeAttachmentInfo>::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::vector<NodeAttachmentInf
 		n += attach->mNumChildren;
 		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<SceneHelper> 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<unsigned int> 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.<s>.<n>"
-					// 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: *<n>,
 							// 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 <NodeAttachmentInfo> ::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;
 

+ 35 - 8
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<aiBone*,unsigned int> BoneSrcIndex;
 
 // ---------------------------------------------------------------------------
-/** \brief Helper data structure for SceneCombiner::MergeBones.
- *
+/** @brief Helper data structure for SceneCombiner::MergeBones.
  */
-struct BoneWithHash : public std::pair<uint32_t,aiString*>
-{
+struct BoneWithHash : public std::pair<uint32_t,aiString*>	{
 	std::vector<BoneSrcIndex> pSrcBones;
 };
 

+ 10 - 0
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.