Przeglądaj źródła

General
- added Conjugate() and Rotate() to aiQuaternion
- SkeletonMeshBuilder can now start hierarchy construction at a given node

MD5
- MD5MESH and MD5 anim now working.
- MD5ANIM files can be loaded without corresponding MD5MESHES
- Config option to prevent automatic loading of MD5ANIMs
- WIP version of MD5CAMERA support.
- added test files. No anim test file yet.

BHV
- fixing formatting

LimitBoneWeights
- prints now statistics to the logging system

Viewer
- does now specify the LBW post-processing step.

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

aramis_acg 16 lat temu
rodzic
commit
7080ba231c

+ 27 - 27
code/BVHLoader.cpp

@@ -431,16 +431,16 @@ void BVHLoader::CreateAnimation( aiScene* pScene)
 				poskey->mTime = double( fr);
 
 				// Now compute all translations in the right order
-        for( unsigned int channel = 0; channel < 3; ++channel)
-        {
-				  switch( node.mChannels[channel])
-				  {	
-				    case Channel_PositionX: poskey->mValue.x = node.mChannelValues[fr * node.mChannels.size() + channel]; break;
-            case Channel_PositionY: poskey->mValue.y = node.mChannelValues[fr * node.mChannels.size() + channel]; break;
-				    case Channel_PositionZ: poskey->mValue.z = node.mChannelValues[fr * node.mChannels.size() + channel]; break;
-				    default: throw new ImportErrorException( "Unexpected animation channel setup at node " + nodeName );
-				  }
-        }
+				for( unsigned int channel = 0; channel < 3; ++channel)
+				{
+					switch( node.mChannels[channel])
+					{	
+					case Channel_PositionX: poskey->mValue.x = node.mChannelValues[fr * node.mChannels.size() + channel]; break;
+					case Channel_PositionY: poskey->mValue.y = node.mChannelValues[fr * node.mChannels.size() + channel]; break;
+					case Channel_PositionZ: poskey->mValue.z = node.mChannelValues[fr * node.mChannels.size() + channel]; break;
+					default: throw new ImportErrorException( "Unexpected animation channel setup at node " + nodeName );
+					}
+				}
 				++poskey;
 			}
 		} else
@@ -468,23 +468,23 @@ void BVHLoader::CreateAnimation( aiScene* pScene)
 			aiQuatKey* rotkey = nodeAnim->mRotationKeys;
 			for( unsigned int fr = 0; fr < mAnimNumFrames; ++fr)
 			{
-        aiMatrix4x4 temp;
-        aiMatrix3x3 rotMatrix;
-
-        for( unsigned int channel = 0; channel < 3; ++channel)
-        {
-				  // translate ZXY euler angels into a quaternion
-				  const float angle = node.mChannelValues[fr * node.mChannels.size() + rotOffset + channel] * float( AI_MATH_PI) / 180.0f;
-
-				  // Compute rotation transformations in the right order
-				  switch (node.mChannels[rotOffset+channel]) 
-				  {
-				    case Channel_RotationX: aiMatrix4x4::RotationX( angle, temp); rotMatrix *= aiMatrix3x3( temp); break;
-				    case Channel_RotationY: aiMatrix4x4::RotationY( angle, temp); rotMatrix *= aiMatrix3x3( temp);	break;
-				    case Channel_RotationZ: aiMatrix4x4::RotationZ( angle, temp); rotMatrix *= aiMatrix3x3( temp); break;
-				    default: throw new ImportErrorException( "Unexpected animation channel setup at node " + nodeName );
-				  }
-        }
+				aiMatrix4x4 temp;
+				aiMatrix3x3 rotMatrix;
+
+				for( unsigned int channel = 0; channel < 3; ++channel)
+				{
+					// translate ZXY euler angels into a quaternion
+					const float angle = node.mChannelValues[fr * node.mChannels.size() + rotOffset + channel] * float( AI_MATH_PI) / 180.0f;
+
+					// Compute rotation transformations in the right order
+					switch (node.mChannels[rotOffset+channel]) 
+					{
+					case Channel_RotationX: aiMatrix4x4::RotationX( angle, temp); rotMatrix *= aiMatrix3x3( temp); break;
+					case Channel_RotationY: aiMatrix4x4::RotationY( angle, temp); rotMatrix *= aiMatrix3x3( temp);	break;
+					case Channel_RotationZ: aiMatrix4x4::RotationZ( angle, temp); rotMatrix *= aiMatrix3x3( temp); break;
+					default: throw new ImportErrorException( "Unexpected animation channel setup at node " + nodeName );
+					}
+				}
 
 				rotkey->mTime = double( fr);
 				rotkey->mValue = aiQuaternion( rotMatrix);

+ 16 - 9
code/LimitBoneWeightsProcess.cpp

@@ -72,8 +72,11 @@ bool LimitBoneWeightsProcess::IsActive( unsigned int pFlags) const
 // Executes the post processing step on the given imported data.
 void LimitBoneWeightsProcess::Execute( aiScene* pScene)
 {
+	DefaultLogger::get()->debug("LimitBoneWeightsProcess begin");
 	for( unsigned int a = 0; a < pScene->mNumMeshes; a++)
 		ProcessMesh( pScene->mMeshes[a]);
+
+	DefaultLogger::get()->debug("LimitBoneWeightsProcess end");
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -106,6 +109,8 @@ void LimitBoneWeightsProcess::ProcessMesh( aiMesh* pMesh)
 		}
 	}
 
+	unsigned int removed = 0, old_bones = pMesh->mNumBones;
+
 	// now cut the weight count if it exceeds the maximum
 	bool bChanged = false;
 	for( WeightsPerVertex::iterator vit = vertexWeights.begin(); vit != vertexWeights.end(); ++vit)
@@ -120,7 +125,9 @@ void LimitBoneWeightsProcess::ProcessMesh( aiMesh* pMesh)
 		std::sort( vit->begin(), vit->end());
 
 		// now kill everything beyond the maximum count
+		unsigned int m = vit->size();
 		vit->erase( vit->begin() + mMaxWeights, vit->end());
+		removed += m-vit->size();
 
 		// and renormalize the weights
 		float sum = 0.0f;
@@ -130,9 +137,7 @@ void LimitBoneWeightsProcess::ProcessMesh( aiMesh* pMesh)
 			it->mWeight /= sum;
 	}
 
-	if (bChanged)
-	{
-
+	if (bChanged)	{
 		// rebuild the vertex weight array for all bones 
 		typedef std::vector< std::vector< aiVertexWeight > > WeightsPerBone;
 		WeightsPerBone boneWeights( pMesh->mNumBones);
@@ -183,12 +188,8 @@ void LimitBoneWeightsProcess::ProcessMesh( aiMesh* pMesh)
 			aiBone** ppcCur = pMesh->mBones;
 			aiBone** ppcSrc = ppcCur;
 
-			for (std::vector<bool>::const_iterator
-				iter  = abNoNeed.begin();
-				iter != abNoNeed.end()  ;++iter)
-			{
-				if (*iter)
-				{
+			for (std::vector<bool>::const_iterator iter  = abNoNeed.begin();iter != abNoNeed.end()  ;++iter)	{
+				if (*iter)	{
 					delete *ppcSrc;
 					--pMesh->mNumBones;
 				}
@@ -196,5 +197,11 @@ void LimitBoneWeightsProcess::ProcessMesh( aiMesh* pMesh)
 				++ppcSrc;
 			}
 		}
+
+		if (!DefaultLogger::isNullLogger()) {
+			char buffer[1024];
+			::sprintf(buffer,"Removed %i weights. Input bones: %i. Output bones: %i",removed,old_bones,pMesh->mNumBones);
+			DefaultLogger::get()->info(buffer);
+		}
 	}
 }

+ 281 - 187
code/MD5Loader.cpp

@@ -52,12 +52,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "MD5Loader.h"
 #include "StringComparison.h"
 #include "fast_atof.h"
+#include "SkeletonMeshBuilder.h"
 
 using namespace Assimp;
 
+// Minimum weight value. Weights inside [-n ... n] are ignored
+#define AI_MD5_WEIGHT_EPSILON 1e-5f
+
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 MD5Importer::MD5Importer()
+: configNoAutoLoad (false)
 {}
 
 // ------------------------------------------------------------------------------------------------
@@ -71,7 +76,7 @@ bool MD5Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool
 {
 	const std::string extension = GetExtension(pFile);
 
-	if (extension == "md5anim" || extension == "md5mesh")
+	if (extension == "md5anim" || extension == "md5mesh" || extension == "md5camera")
 		return true;
 	else if (!extension.length() || checkSig)	{
 		if (!pIOHandler)
@@ -86,72 +91,106 @@ bool MD5Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool
 // Get list of all supported extensions
 void MD5Importer::GetExtensionList(std::string& append)
 {
-	append.append("*.md5mesh;*.md5anim");
+	append.append("*.md5mesh;*.md5anim;*.md5camera");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup import properties
+void MD5Importer::SetupProperties(const Importer* pImp)
+{
+	// AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD
+	configNoAutoLoad = (0 !=  pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD,0));
 }
 
 // ------------------------------------------------------------------------------------------------
 // Imports the given file into the given scene structure. 
-void MD5Importer::InternReadFile( 
-	const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
+void MD5Importer::InternReadFile( const std::string& pFile, 
+								 aiScene* _pScene, IOSystem* _pIOHandler)
 {
+	pIOHandler = _pIOHandler;
+	pScene     = _pScene;
+	bHadMD5Mesh = bHadMD5Anim = bHadMD5Camera = false;
+
 	// remove the file extension
 	std::string::size_type pos = pFile.find_last_of('.');
-	mFile = pFile.substr(0,pos+1);
-	this->pIOHandler = pIOHandler;
-	this->pScene = pScene;
-
-	bHadMD5Mesh = bHadMD5Anim = false;
+	mFile = (std::string::npos == pos ? pFile : pFile.substr(0,pos+1));
 
-	// load the animation keyframes
-	LoadMD5AnimFile();
-
-	// load the mesh vertices and bones
-	LoadMD5MeshFile();
+	const std::string extension = GetExtension(pFile);
+	try {
+		if (extension == "md5camera") {
+			LoadMD5CameraFile();
+		}
+		else if (configNoAutoLoad || extension == "md5anim") {
+			// determine file extension and process just *one* file
+			if (extension.length() == 0) {
+				/* fixme */
+			}
+			if (extension == "md5anim") {
+				LoadMD5AnimFile();
+			}
+			else if (extension == "md5mesh") {
+				LoadMD5MeshFile();
+			}
+		}
+		else {
+			LoadMD5MeshFile();
+			LoadMD5AnimFile();
+		}
+	}
+	catch ( ImportErrorException* ex) {
+		UnloadFileFromMemory();
+		throw ex;
+	}
 
-	// make sure we return no incomplete data
+	// make sure we have at least one file
 	if (!bHadMD5Mesh && !bHadMD5Anim)
-		throw new ImportErrorException("Failed to read valid data from this MD5");
-	
-	if (!bHadMD5Mesh)pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
+		throw new ImportErrorException("Failed to read valid contents from this MD5 data set");
+
+	// the output scene wouldn't pass the validation without this flag
+	if (!bHadMD5Mesh)
+		pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
 }
 
 // ------------------------------------------------------------------------------------------------
+// Load a file into a memory buffer
 void MD5Importer::LoadFileIntoMemory (IOStream* file)
 {
 	ai_assert(NULL != file);
 
-	this->fileSize = (unsigned int)file->FileSize();
+	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;
+	pScene = pScene;
+	mBuffer = new char[fileSize+1];
+	file->Read( (void*)mBuffer, 1, fileSize);
+	iLineNumber = 1;
 
 	// append a terminal 0
-	this->mBuffer[this->fileSize] = '\0';
+	mBuffer[fileSize] = '\0';
 
 	// now remove all line comments from the file
-	CommentRemover::RemoveLineComments("//",this->mBuffer,' ');
+	CommentRemover::RemoveLineComments("//",mBuffer,' ');
 }
 
 // ------------------------------------------------------------------------------------------------
+// Unload the current memory buffer
 void MD5Importer::UnloadFileFromMemory ()
 {
 	// delete the file buffer
-	delete[] this->mBuffer;
-	this->mBuffer = NULL;
-	this->fileSize = 0;
+	delete[] mBuffer;
+	mBuffer = NULL;
+	fileSize = 0;
 }
 
 // ------------------------------------------------------------------------------------------------
-void MakeDataUnique (MD5::MeshDesc& meshSrc)
+// Build unique vertices
+void MD5Importer::MakeDataUnique (MD5::MeshDesc& meshSrc)
 {
 	std::vector<bool> 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();
+	const unsigned int iNewNum = meshSrc.mFaces.size()*3;
+	unsigned int iNewIndex = meshSrc.mVertices.size();
 	meshSrc.mVertices.resize(iNewNum);
 
 	// try to guess how much storage we'll need for new weights
@@ -165,26 +204,10 @@ void MakeDataUnique (MD5::MeshDesc& meshSrc)
 		const aiFace& face = *iter;
 		for (unsigned int i = 0; i < 3;++i)
 		{
-			if (abHad[face.mIndices[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;
 		}
@@ -192,77 +215,109 @@ void MakeDataUnique (MD5::MeshDesc& meshSrc)
 }
 
 // ------------------------------------------------------------------------------------------------
-void AttachChilds(int iParentID,aiNode* piParent,BoneList& bones)
+// Recursive node graph construction from a MD5MESH
+void MD5Importer::AttachChilds_Mesh(int iParentID,aiNode* piParent, BoneList& bones)
 {
 	ai_assert(NULL != piParent && !piParent->mNumChildren);
-	for (int i = 0; i < (int)bones.size();++i)
-	{
-		// (avoid infinite recursion)
-		if (iParentID != i && bones[i].mParentIndex == iParentID)
-		{
-			// have it ...
+
+	// First find out how many children we'll have
+	for (int i = 0; i < (int)bones.size();++i)	{
+		if (iParentID != i && bones[i].mParentIndex == iParentID)	{
 			++piParent->mNumChildren;
 		}
 	}
-	if (piParent->mNumChildren)
-	{
+	if (piParent->mNumChildren)	{
 		piParent->mChildren = new aiNode*[piParent->mNumChildren];
-		for (int i = 0; i < (int)bones.size();++i)
-		{
+		for (int i = 0; i < (int)bones.size();++i)	{
 			// (avoid infinite recursion)
-			if (iParentID != i && bones[i].mParentIndex == iParentID)
-			{
+			if (iParentID != i && bones[i].mParentIndex == iParentID)	{
 				aiNode* pc;
+				// setup a new node
 				*piParent->mChildren++ = pc = new aiNode();
-				pc->mName = aiString(bones[i].mName);
+				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;
+				aiQuaternion quat; 
+				MD5::ConvertQuaternion ( bones[i].mRotationQuat, quat );
+
+				bones[i].mTransform = aiMatrix4x4 ( quat.GetMatrix());
+				bones[i].mTransform.a4 = bones[i].mPositionXYZ.x;
+				bones[i].mTransform.b4 = bones[i].mPositionXYZ.y;
+				bones[i].mTransform.c4 = bones[i].mPositionXYZ.z;
 
 				// store it for later use
-				bones[i].mTransform = bones[i].mInvTransform = pc->mTransformation;
+				pc->mTransformation = bones[i].mInvTransform = bones[i].mTransform;
 				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;
+				// the transformations for each bone are absolute, so we need to multiply them
+				// with the inverse of the absolute matrix of the parent joint
+				if (-1 != iParentID)	{
+					pc->mTransformation = bones[iParentID].mInvTransform * pc->mTransformation;
 				}
 
 				// add children to this node, too
-				AttachChilds( i, pc, bones);
+				AttachChilds_Mesh( i, pc, bones);
 			}
 		}
-		// undo our nice shift
+		// undo offset computations
 		piParent->mChildren -= piParent->mNumChildren;
 	}
 }
 
 // ------------------------------------------------------------------------------------------------
+// Recursive node graph construction from a MD5ANIM
+void MD5Importer::AttachChilds_Anim(int iParentID,aiNode* piParent, AnimBoneList& bones,const aiNodeAnim** node_anims)
+{
+	ai_assert(NULL != piParent && !piParent->mNumChildren);
+
+	// First find out how many children we'll have
+	for (int i = 0; i < (int)bones.size();++i)	{
+		if (iParentID != i && bones[i].mParentIndex == iParentID)	{
+			++piParent->mNumChildren;
+		}
+	}
+	if (piParent->mNumChildren)	{
+		piParent->mChildren = new aiNode*[piParent->mNumChildren];
+		for (int i = 0; i < (int)bones.size();++i)	{
+			// (avoid infinite recursion)
+			if (iParentID != i && bones[i].mParentIndex == iParentID)
+			{
+				aiNode* pc;
+				// setup a new node
+				*piParent->mChildren++ = pc = new aiNode();
+				pc->mName = aiString(bones[i].mName); 
+				pc->mParent = piParent;
+
+				// get the corresponding animation channel and its first frame
+				const aiNodeAnim** cur = node_anims;
+				while ((**cur).mNodeName != pc->mName)++cur;
+
+				aiMatrix4x4::Translation((**cur).mPositionKeys[0].mValue,pc->mTransformation);
+				pc->mTransformation = pc->mTransformation * aiMatrix4x4((**cur).mRotationKeys[0].mValue.GetMatrix()) ;
+
+				// add children to this node, too
+				AttachChilds_Anim( i, pc, bones,node_anims);
+			}
+		}
+		// undo offset computations
+		piParent->mChildren -= piParent->mNumChildren;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// Load a MD5MESH file
 void MD5Importer::LoadMD5MeshFile ()
 {
-	std::string pFile = this->mFile + "MD5MESH";
+	std::string pFile = mFile + "md5mesh";
 	boost::scoped_ptr<IOStream> 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);
+	if( file.get() == NULL)	{
+		DefaultLogger::get()->warn("Failed to read MD5MESH file: " + pFile);
 		return;
 	}
 	bHadMD5Mesh = true;
-
-	// now load the file into memory
 	LoadFileIntoMemory(file.get());
 
 	// now construct a parser and parse the file
@@ -271,29 +326,29 @@ void MD5Importer::LoadMD5MeshFile ()
 	// 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();
+	// create the bone hierarchy - first the root node and dummy nodes for all meshes
+	pScene->mRootNode = new aiNode("<MD5_Root>");
 	pScene->mRootNode->mNumChildren = 2;
 	pScene->mRootNode->mChildren = new aiNode*[2];
 
-	// now create the hierarchy of animated bones
+	// build the hierarchy from the MD5MESH file
 	aiNode* pcNode = pScene->mRootNode->mChildren[1] = new aiNode();
-	pcNode->mName.Set("MD5Anim");
+	pcNode->mName.Set("<MD5_Hierarchy>");
 	pcNode->mParent = pScene->mRootNode;
-	AttachChilds(-1,pcNode,meshParser.mJoints);
+	AttachChilds_Mesh(-1,pcNode,meshParser.mJoints);
 
 	pcNode = pScene->mRootNode->mChildren[0] = new aiNode();
-	pcNode->mName.Set("MD5Mesh");
+	pcNode->mName.Set("<MD5_Mesh>");
 	pcNode->mParent = pScene->mRootNode;
 
+#if 0
+	if (pScene->mRootNode->mChildren[1]->mNumChildren) /* start at the right hierarchy level */
+		SkeletonMeshBuilder skeleton_maker(pScene,pScene->mRootNode->mChildren[1]->mChildren[0]);
+#else
 	std::vector<MD5::MeshDesc>::const_iterator end = meshParser.mMeshes.end();
 
 	// FIX: MD5 files exported from Blender can have empty meshes
-	for (std::vector<MD5::MeshDesc>::const_iterator 
-		 it  = meshParser.mMeshes.begin(),
-		 end = meshParser.mMeshes.end(); it != end;++it)
-	{
+	for (std::vector<MD5::MeshDesc>::const_iterator it  = meshParser.mMeshes.begin(),end = meshParser.mMeshes.end(); it != end;++it) {
 		if (!(*it).mFaces.empty() && !(*it).mVertices.empty())
 			++pScene->mNumMaterials;
 	}
@@ -310,10 +365,7 @@ void MD5Importer::LoadMD5MeshFile ()
 		pcNode->mMeshes[m] = m;
 
 	unsigned int n = 0;
-	for (std::vector<MD5::MeshDesc>::iterator 
-		 it  = meshParser.mMeshes.begin(),
-		 end = meshParser.mMeshes.end(); it != end;++it)
-	{
+	for (std::vector<MD5::MeshDesc>::iterator it  = meshParser.mMeshes.begin(),end = meshParser.mMeshes.end(); it != end;++it) {
 		MD5::MeshDesc& meshSrc = *it;
 		if (meshSrc.mFaces.empty() || meshSrc.mVertices.empty())
 			continue;
@@ -331,10 +383,7 @@ void MD5Importer::LoadMD5MeshFile ()
 
 		// copy texture coordinates
 		aiVector3D* pv = mesh->mTextureCoords[0];
-		for (MD5::VertexList::const_iterator
-			iter =  meshSrc.mVertices.begin();
-			iter != meshSrc.mVertices.end();++iter,++pv)
-		{
+		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;
@@ -348,7 +397,9 @@ void MD5Importer::LoadMD5MeshFile ()
 			for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights;++w)
 			{
 				MD5::WeightDesc& desc = meshSrc.mWeights[w];
-				++piCount[desc.mBone]; 
+				/* FIX for some invalid exporters */
+				if (!(desc.mWeight < AI_MD5_WEIGHT_EPSILON && desc.mWeight >= -AI_MD5_WEIGHT_EPSILON ))
+					++piCount[desc.mBone]; 
 			}
 		}
 
@@ -366,50 +417,55 @@ void MD5Importer::LoadMD5MeshFile ()
 				p->mNumWeights = piCount[q];
 				p->mWeights = new aiVertexWeight[p->mNumWeights];
 				p->mName = aiString(meshParser.mJoints[q].mName);
+				p->mOffsetMatrix = meshParser.mJoints[q].mInvTransform;
 
 				// store the index for later use
-				meshParser.mJoints[q].mMap = h++;
+				MD5::BoneDesc& boneSrc = meshParser.mJoints[q];
+				boneSrc.mMap = h++;
+
+				// compute w-component of quaternion
+				MD5::ConvertQuaternion( boneSrc.mRotationQuat, boneSrc.mRotationQuatConverted );
+
+				//boneSrc.mPositionXYZ.z *= -1.f;
+				//boneSrc.mRotationQuatConverted = boneSrc.mRotationQuatConverted * aiQuaternion(-0.5f, -0.5f, -0.5f, 0.5f) ;
 			}
 	
 			//unsigned int g = 0;
 			pv = mesh->mVertices;
-			for (MD5::VertexList::const_iterator
-				iter =  meshSrc.mVertices.begin();
-				iter != meshSrc.mVertices.end();++iter,++pv)
-			{
+			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");
+				if (!fSum) {
+					DefaultLogger::get()->error("MD5MESH: The sum of all vertex bone weights is 0");
+					continue;
+				}
 
 				// 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; 
-					
+					if ( desc.mWeight < AI_MD5_WEIGHT_EPSILON && desc.mWeight >= -AI_MD5_WEIGHT_EPSILON)
+						continue;
+
+					const 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;
+					const aiVector3D v = boneSrc.mRotationQuatConverted.Rotate (desc.vOffsetPosition);
 
 					// use the original weight to compute the vertex position
 					// (some MD5s seem to depend on the invalid weight values ...)
-					pv->operator +=(v * desc.mWeight);
+					*pv += ((boneSrc.mPositionXYZ+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((float&)pv->z,(float&)pv->y);
+				//pv->z *= -1.f;
 			}
 
 			// undo our nice offset tricks ...
@@ -433,47 +489,69 @@ void MD5Importer::LoadMD5MeshFile ()
 		// generate a material for the mesh
 		MaterialHelper* mat = new MaterialHelper();
 		pScene->mMaterials[n] = mat;
-		mat->AddProperty(&meshSrc.mShader,AI_MATKEY_TEXTURE_DIFFUSE(0));
+
+		// insert the typical doom3 textures:
+		// nnn_local.tga  - normal map
+		// nnn_h.tga      - height map
+		// nnn_s.tga      - specular map
+		// nnn_d.tga      - diffuse map
+		if (meshSrc.mShader.length && !strchr(meshSrc.mShader.data,'.')) {
+		
+			aiString temp(meshSrc.mShader);
+			temp.Append("_local.tga");
+			mat->AddProperty(&temp,AI_MATKEY_TEXTURE_NORMALS(0));
+
+			temp =  aiString(meshSrc.mShader);
+			temp.Append("_s.tga");
+			mat->AddProperty(&temp,AI_MATKEY_TEXTURE_SPECULAR(0));
+
+			temp =  aiString(meshSrc.mShader);
+			temp.Append("_d.tga");
+			mat->AddProperty(&temp,AI_MATKEY_TEXTURE_DIFFUSE(0));
+
+			temp =  aiString(meshSrc.mShader);
+			temp.Append("_h.tga");
+			mat->AddProperty(&temp,AI_MATKEY_TEXTURE_HEIGHT(0));
+
+			// set this also as material name
+			mat->AddProperty(&meshSrc.mShader,AI_MATKEY_NAME);
+		}
+		else mat->AddProperty(&meshSrc.mShader,AI_MATKEY_TEXTURE_DIFFUSE(0));
 		mesh->mMaterialIndex = n++;
 	}
-
+#endif
 	// delete the file again
 	UnloadFileFromMemory();
 }
 
 // ------------------------------------------------------------------------------------------------
+// Load an MD5ANIM file
 void MD5Importer::LoadMD5AnimFile ()
 {
-	std::string pFile = mFile + "MD5ANIM";
+	std::string pFile = mFile + "md5anim";
 	boost::scoped_ptr<IOStream> 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);
+	if( file.get() == NULL)	{
+		DefaultLogger::get()->warn("Failed to read MD5ANIM file: " + pFile);
 		return;
 	}
 	bHadMD5Anim = true;
-
-	// now load the file into memory
 	LoadFileIntoMemory(file.get());
 
-	// now construct a parser and parse the file
+	// parse the basic file structure
 	MD5::MD5Parser parser(mBuffer,fileSize);
 
-	// load the animation information from it
+	// load the animation information from the parse tree
 	MD5::MD5AnimParser animParser(parser.mSections);
 
 	// generate and fill the output animation
-	if (!animParser.mAnimatedBones.empty())
-	{
-		pScene->mNumAnimations = 1;
-		pScene->mAnimations = new aiAnimation*[1];
+	if (!animParser.mAnimatedBones.empty())	{
+		pScene->mAnimations = new aiAnimation*[pScene->mNumAnimations = 1];
 		aiAnimation* anim = pScene->mAnimations[0] = new aiAnimation();
 		anim->mNumChannels = (unsigned int)animParser.mAnimatedBones.size();
 		anim->mChannels = new aiNodeAnim*[anim->mNumChannels];
-		for (unsigned int i = 0; i < anim->mNumChannels;++i)
-		{
+		for (unsigned int i = 0; i < anim->mNumChannels;++i)	{
 			aiNodeAnim* node = anim->mChannels[i] = new aiNodeAnim();
 			node->mNodeName = aiString( animParser.mAnimatedBones[i].mName );
 
@@ -486,70 +564,49 @@ void MD5Importer::LoadMD5AnimFile ()
 		// 1 tick == 1 frame
 		anim->mTicksPerSecond = animParser.fFrameRate;
 
-		for (FrameList::const_iterator iter = animParser.mFrames.begin(), 
-			iterEnd = animParser.mFrames.end();iter != iterEnd;++iter)
-		{
+		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
 				aiNodeAnim** pcAnimNode = anim->mChannels;
 				MD5::BaseFrameDesc* pcBaseFrame = &animParser.mBaseFrames[0];
-				for (AnimBoneList::const_iterator 
-					iter2		= animParser.mAnimatedBones.begin(), 
-					iterEnd2	= animParser.mAnimatedBones.end();
-					iter2		!= iterEnd2;++iter2,++pcAnimNode,++pcBaseFrame)
+				for (AnimBoneList::const_iterator iter2	= animParser.mAnimatedBones.begin(); iter2 != animParser.mAnimatedBones.end();++iter2,
+					++pcAnimNode,++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. ");
+					if((*iter2).iFirstKeyIndex >= (*iter).mValues.size()) {
+						DefaultLogger::get()->error("MD5: Keyframe index is out of range");
 						continue;
 					}
 					const float* fpCur = &(*iter).mValues[(*iter2).iFirstKeyIndex];
 
 					aiNodeAnim* pcCurAnimBone = *pcAnimNode;
 					aiVectorKey* vKey = pcCurAnimBone->mPositionKeys++;
+					aiQuatKey* qKey = pcCurAnimBone->mRotationKeys++;
+					aiVector3D vTemp;
 
-					// 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;
+					// translational component
+					for (unsigned int i = 0; i < 3; ++i) {
+						if ((*iter2).iFlags & (1u << i))
+							vKey->mValue[i] =  *fpCur++;
+						else vKey->mValue[i] = pcBaseFrame->vPositionXYZ[i];
+					}
+					
+					// orientation component
+					for (unsigned int i = 0; i < 3; ++i) {
+						if ((*iter2).iFlags & (8u << i))
+							vTemp[i] =  *fpCur++;
+						else vTemp[i] = pcBaseFrame->vRotationQuat[i];
+					}
 
-					// 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;
+					MD5::ConvertQuaternion(vTemp, qKey->mValue);
 
+					aiMatrix4x4 m;
+					aiMatrix4x4::Translation(vKey->mValue,m);
+					m = m*aiMatrix4x4( qKey->mValue.GetMatrix() );
+					m.DecomposeNoScaling(qKey->mValue,vKey->mValue);
 
-					// 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;
+					qKey->mTime = vKey->mTime = dTime;
 				}
 			}
 			// compute the duration of the animation
@@ -557,16 +614,53 @@ void MD5Importer::LoadMD5AnimFile ()
 		}
 
 		// undo our offset computations
-		for (unsigned int i = 0; i < anim->mNumChannels;++i)
-		{
+		for (unsigned int i = 0; i < anim->mNumChannels;++i)	{
 			aiNodeAnim* node = anim->mChannels[i];
 			node->mPositionKeys -= node->mNumPositionKeys;
 			node->mRotationKeys -= node->mNumPositionKeys;
 		}
+
+		// If we didn't build the hierarchy yet (== we didn't load a MD5MESH),
+		// construct it now from the data given in the MD5ANIM.
+		if (!pScene->mRootNode) {
+			pScene->mRootNode = new aiNode();
+			pScene->mRootNode->mName.Set("<MD5_Hierarchy>");
+			AttachChilds_Anim(-1,pScene->mRootNode,animParser.mAnimatedBones,(const aiNodeAnim**)anim->mChannels);
+
+			// Call SkeletonMeshBuilder to construct a mesh to represent the shape
+			if (pScene->mRootNode->mNumChildren) {
+				SkeletonMeshBuilder skeleton_maker(pScene,pScene->mRootNode->mChildren[0]);
+			}
+		}
 	}
 
 	// delete the file again
 	UnloadFileFromMemory();
 }
 
+// ------------------------------------------------------------------------------------------------
+// Load an MD5CAMERA file
+void MD5Importer::LoadMD5CameraFile ()
+{
+#if 0
+	std::string pFile = mFile + "md5camera";
+	boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
+
+	// Check whether we can read from the file
+	if( file.get() == NULL)	{
+		DefaultLogger::get()->warn("Failed to read MD5CAMERA file: " + pFile);
+		return;
+	}
+	bHadMD5Camera = true;
+	LoadFileIntoMemory(file.get());
+
+	// parse the basic file structure
+	MD5::MD5Parser parser(mBuffer,fileSize);
+
+	// load the camera animation data from the parse tree
+	MD5::MD5CameraParser cameraParser(parser.mSections);
+#endif
+	throw new ImportErrorException("MD5Camera is not yet supported");
+}
+
 #endif // !! ASSIMP_BUILD_NO_MD5_IMPORTER

+ 54 - 13
code/MD5Loader.h

@@ -57,7 +57,7 @@ class IOStream;
 using namespace Assimp::MD5;
 
 // ---------------------------------------------------------------------------
-/** Used to load MD5 files
+/** Importer class for the MD5 file format
 */
 class MD5Importer : public BaseImporter
 {
@@ -74,7 +74,8 @@ public:
 
 	// -------------------------------------------------------------------
 	/** Returns whether the class can handle the format of the given file. 
-	* See BaseImporter::CanRead() for details.	*/
+	 * See BaseImporter::CanRead() for details.
+ 	 */
 	bool CanRead( const std::string& pFile, IOSystem* pIOHandler,
 		bool checkSig) const;
 
@@ -85,11 +86,18 @@ protected:
 	 * See BaseImporter::GetExtensionList() for details
 	 */
 	void GetExtensionList(std::string& append);
+
+	// -------------------------------------------------------------------
+	/** Called prior to ReadFile().
+	 * The function is a request to the importer to update its configuration
+	 * basing on the Importer's configuration property list.
+	 */
+	void SetupProperties(const Importer* pImp);
 	
 	// -------------------------------------------------------------------
 	/** 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);
 
@@ -97,22 +105,49 @@ protected:
 
 
 	// -------------------------------------------------------------------
-	/** Load the *.MD5MESH file.
-	 * Must be called at first.
-	*/
+	/** Load a *.MD5MESH file.
+	 */
 	void LoadMD5MeshFile ();
 
 	// -------------------------------------------------------------------
-	/** Load the *.MD5ANIM file.
-	*/
+	/** Load a *.MD5ANIM file.
+	 */
 	void LoadMD5AnimFile ();
 
+	// -------------------------------------------------------------------
+	/** Load a *.MD5CAMERA file.
+	 */
+	void LoadMD5CameraFile ();
+
+	// -------------------------------------------------------------------
+	/** Construct node hierarchy from a given MD5ANIM 
+	 *  @param iParentID Current parent ID
+	 *  @param piParent Parent node to attach to
+	 *  @param bones Input bones
+	 *  @param node_anims Generated node animations
+	*/
+	void AttachChilds_Anim(int iParentID,aiNode* piParent, 
+		AnimBoneList& bones,const aiNodeAnim** node_anims);
+
+	// -------------------------------------------------------------------
+	/** Construct node hierarchy from a given MD5MESH 
+	 *  @param iParentID Current parent ID
+	 *  @param piParent Parent node to attach to
+	 *  @param bones Input bones
+	*/
+	void AttachChilds_Mesh(int iParentID,aiNode* piParent,BoneList& bones);
+
+	// -------------------------------------------------------------------
+	/** Build unique vertex buffers from a given MD5ANIM
+	 *  @param meshSrc Input data
+	 */
+	void MakeDataUnique (MD5::MeshDesc& meshSrc);
+
 	// -------------------------------------------------------------------
 	/** 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 ...
+	 *  mBuffer is modified to point to this buffer.
 	 *  @param pFile File stream to be read
 	*/
 	void LoadFileIntoMemory (IOStream* pFile);
@@ -141,11 +176,17 @@ protected:
 	/** (Custom) I/O handler implementation */
 	IOSystem* pIOHandler;
 
-	/** true if the MD5MESH file has already been parsed */
+	/** true if a MD5MESH file has already been parsed */
 	bool bHadMD5Mesh;
 
-	/** true if the MD5ANIM file has already been parsed */
+	/** true if a MD5ANIM file has already been parsed */
 	bool bHadMD5Anim;
+
+	/** true if a MD5CAMERA file has already been parsed */
+	bool bHadMD5Camera;
+
+	/** configuration option: prevent anim autoload */
+	bool configNoAutoLoad;
 };
 
 } // end of namespace Assimp

+ 102 - 181
code/MD5Parser.cpp

@@ -39,8 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ---------------------------------------------------------------------------
 */
 
-/** @file Implementation of the MD5 parser class */
-
+/** @file  MD5Parser.cpp 
+ *  @brief Implementation of the MD5 parser class
+ */
 #include "AssimpPCH.h"
 
 // internal headers
@@ -50,90 +51,88 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "ParsingUtils.h"
 #include "StringComparison.h"
 
-
 using namespace Assimp;
 using namespace Assimp::MD5;
 
-#if _MSC_VER >= 1400
-#	define sprintf sprintf_s
-#endif
-
 // ------------------------------------------------------------------------------------------------
-MD5Parser::MD5Parser(char* buffer, unsigned int fileSize)
+// Parse the segment structure fo a MD5 file
+MD5Parser::MD5Parser(char* _buffer, unsigned int _fileSize)
 {
-	ai_assert(NULL != buffer && 0 != fileSize);
+	ai_assert(NULL != _buffer && 0 != _fileSize);
 
-	this->buffer = buffer;
-	this->fileSize = fileSize;
-	this->lineNumber = 0;
+	buffer = _buffer;
+	fileSize = fileSize;
+	lineNumber = 0;
 
 	DefaultLogger::get()->debug("MD5Parser begin");
 
 	// parse the file header
-	this->ParseHeader();
+	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))
-		{
+	while (1)	{
+		mSections.push_back(Section());
+		Section& sec = mSections.back();
+		if(!ParseSection(sec))	{
 			break;
 		}
 	}
 
-	if ( !DefaultLogger::isNullLogger())
-	{
+	if ( !DefaultLogger::isNullLogger())	{
 		char szBuffer[128]; // should be sufficiently large
-		::sprintf(szBuffer,"MD5Parser end. Parsed %i sections",(int)this->mSections.size());
+		::sprintf(szBuffer,"MD5Parser end. Parsed %i sections",(int)mSections.size());
 		DefaultLogger::get()->debug(szBuffer);
 	}
 }
+
 // ------------------------------------------------------------------------------------------------
+// Report error to the log stream
 /*static*/ void MD5Parser::ReportError (const 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);
+	char szBuffer[1024];
+	::sprintf(szBuffer,"[MD5] Line %i: %s",line,error);
 	throw new ImportErrorException(szBuffer);
 }
+
 // ------------------------------------------------------------------------------------------------
+// Report warning to the log stream
 /*static*/ void MD5Parser::ReportWarning (const 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);
+	char szBuffer[1024]; 
+	::sprintf(szBuffer,"[MD5] Line %i: %s",line,warn);
 	DefaultLogger::get()->warn(szBuffer);
 }
+
 // ------------------------------------------------------------------------------------------------
+// Parse and validate the MD5 header
 void MD5Parser::ParseHeader()
 {
 	// parse and validate the file version
 	SkipSpaces();
-	if (0 != ASSIMP_strincmp(buffer,"MD5Version",10) ||
-		!IsSpace(*(buffer+=10)))
-	{
+	if (!TokenMatch(buffer,"MD5Version",10))	{
 		ReportError("Invalid MD5 file: MD5Version tag has not been found");
 	}
 	SkipSpaces();
 	unsigned int iVer = ::strtol10(buffer,(const char**)&buffer);
-	if (10 != iVer)
-	{
+	if (10 != iVer)	{
 		ReportWarning("MD5 version tag is unknown (10 is expected)");
 	}
 	SkipLine();
 
 	// print the command line options to the console
-	// fix: can break the log length limit ...
+	// FIX: can break the log length limit, so we need to be careful
 	char* sz = buffer;
 	while (!IsLineEnd( *buffer++));
 	DefaultLogger::get()->info(std::string(sz,std::min((uintptr_t)MAX_LOG_MESSAGE_LENGTH, (uintptr_t)(buffer-sz))));
 	SkipSpacesAndLineEnd();
 }
+
 // ------------------------------------------------------------------------------------------------
+// Recursive MD5 parsing function
 bool MD5Parser::ParseSection(Section& out)
 {
 	// store the current line number for use in error messages
-	out.iLineNumber = this->lineNumber;
+	out.iLineNumber = lineNumber;
 
 	// first parse the name of the section
 	char* sz = buffer;
@@ -141,20 +140,16 @@ bool MD5Parser::ParseSection(Section& out)
 	out.mName = std::string(sz,(uintptr_t)(buffer-sz));
 	SkipSpaces();
 
-	while (true)
-	{
-		if ('{' == *buffer)
-		{
+	while (1)	{
+		if ('{' == *buffer)	{
 			// it is a normal section so read all lines
 			buffer++;
 			while (true)
 			{
-				if (!SkipSpacesAndLineEnd())
-				{
+				if (!SkipSpacesAndLineEnd()) {
 					return false; // seems this was the last section
 				}
-				if ('}' == *buffer)
-				{
+				if ('}' == *buffer)	{
 					buffer++;
 					break;
 				}
@@ -167,19 +162,13 @@ bool MD5Parser::ParseSection(Section& out)
 
 				// 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;
+				do {buffer--;}while (IsSpace(*buffer));
+				buffer++;*buffer++ = '\0';
 			}
 			break;
 		}
-		else if (!IsSpaceOrNewLine(*buffer))
-		{
+		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));
@@ -189,7 +178,9 @@ bool MD5Parser::ParseSection(Section& out)
 	}
 	return SkipSpacesAndLineEnd();
 }
+
 // ------------------------------------------------------------------------------------------------
+// Some dirty macros just because they're so funny and easy to debug
 
 // skip all spaces ... handle EOL correctly
 #define	AI_MD5_SKIP_SPACES()  if(!SkipSpaces(&sz)) \
@@ -230,47 +221,30 @@ bool MD5Parser::ParseSection(Section& out)
 	out.data[out.length] = '\0';
 
 // ------------------------------------------------------------------------------------------------
+// .MD5MESH parsing function
 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)
-	{
+	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);
-				}
+			if ( (*iter).mName == "numMeshes")	{
+				mMeshes.reserve(::strtol10((*iter).mGlobalValue.c_str()));
 			}
-			else if ( !::strcmp("numJoints",(*iter).mName.c_str()))
-			{
-				unsigned int iNumJoints;
-				if((iNumJoints = ::strtol10((*iter).mGlobalValue.c_str())))
-				{
-					mJoints.reserve(iNumJoints);
-				}
+			else if ( (*iter).mName == "numJoints")	{
+				mJoints.reserve(::strtol10((*iter).mGlobalValue.c_str()));
 			}
 		}
-		else if (!::strcmp("joints",(*iter).mName.c_str()))
+		else if ((*iter).mName == "joints")
 		{
-			// 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)
-			{
+			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();
 
@@ -285,63 +259,48 @@ MD5MeshParser::MD5MeshParser(SectionList& mSections)
 				AI_MD5_READ_TRIPLE(desc.mRotationQuat); // normalized quaternion, so w is not there
 			}
 		}
-		else if (!::strcmp("mesh",(*iter).mName.c_str()))
+		else if ((*iter).mName == "mesh")
 		{
 			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)
-			{
+			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)++))
+				if (TokenMatch(sz,"shader",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)++))
+				else if (TokenMatch(sz,"numverts",8))
 				{
 					// reserve enough storage
 					AI_MD5_SKIP_SPACES();
-					unsigned int iNumVertices;
-					if((iNumVertices = ::strtol10(sz)))
-						desc.mVertices.resize(iNumVertices);
+					desc.mVertices.resize(::strtol10(sz));
 				}
 				// numtris attribute
-				else if (!ASSIMP_strincmp(sz,"numtris",7) &&
-					IsSpaceOrNewLine(*(sz+=7)++))
+				else if (TokenMatch(sz,"numtris",7))
 				{
 					// reserve enough storage
 					AI_MD5_SKIP_SPACES();
-					unsigned int iNumTris;
-					if((iNumTris = ::strtol10(sz)))
-						desc.mFaces.resize(iNumTris);
+					desc.mFaces.resize(::strtol10(sz));
 				}
 				// numweights attribute
-				else if (!ASSIMP_strincmp(sz,"numweights",10) &&
-					IsSpaceOrNewLine(*(sz+=10)++))
+				else if (TokenMatch(sz,"numweights",10))
 				{
 					// reserve enough storage
 					AI_MD5_SKIP_SPACES();
-					unsigned int iNumWeights;
-					if((iNumWeights = ::strtol10(sz)))
-						desc.mWeights.resize(iNumWeights);
+					desc.mWeights.resize(::strtol10(sz));
 				}
 				// vert attribute
 				// "vert 0 ( 0.394531 0.513672 ) 0 1"
-				else if (!ASSIMP_strincmp(sz,"vert",4) &&
-					IsSpaceOrNewLine(*(sz+=4)++))
+				else if (TokenMatch(sz,"vert",4))
 				{
 					AI_MD5_SKIP_SPACES();
-					unsigned int idx = ::strtol10(sz,&sz);
+					const unsigned int idx = ::strtol10(sz,&sz);
 					AI_MD5_SKIP_SPACES();
 					if (idx >= desc.mVertices.size())
 						desc.mVertices.resize(idx+1);
@@ -363,11 +322,9 @@ MD5MeshParser::MD5MeshParser(SectionList& mSections)
 				}
 				// tri attribute
 				// "tri 0 15 13 12"
-				else if (!ASSIMP_strincmp(sz,"tri",3) &&
-					IsSpaceOrNewLine(*(sz+=3)++))
-				{
+				else if (TokenMatch(sz,"tri",3)) {
 					AI_MD5_SKIP_SPACES();
-					unsigned int idx = ::strtol10(sz,&sz);
+					const unsigned int idx = ::strtol10(sz,&sz);
 					if (idx >= desc.mFaces.size())
 						desc.mFaces.resize(idx+1);
 
@@ -381,11 +338,10 @@ MD5MeshParser::MD5MeshParser(SectionList& mSections)
 				}
 				// weight attribute
 				// "weight 362 5 0.500000 ( -3.553583 11.893474 9.719339 )"
-				else if (!ASSIMP_strincmp(sz,"weight",6) &&
-					IsSpaceOrNewLine(*(sz+=6)++))
+				else if (TokenMatch(sz,"weight",6))
 				{
 					AI_MD5_SKIP_SPACES();
-					unsigned int idx = ::strtol10(sz,&sz);
+					const unsigned int idx = ::strtol10(sz,&sz);
 					AI_MD5_SKIP_SPACES();
 					if (idx >= desc.mWeights.size())
 						desc.mWeights.resize(idx+1);
@@ -403,6 +359,7 @@ MD5MeshParser::MD5MeshParser(SectionList& mSections)
 }
 
 // ------------------------------------------------------------------------------------------------
+// .MD5ANIM parsing function
 MD5AnimParser::MD5AnimParser(SectionList& mSections)
 {
 	DefaultLogger::get()->debug("MD5AnimParser begin");
@@ -411,18 +368,10 @@ MD5AnimParser::MD5AnimParser(SectionList& mSections)
 	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
+	for (SectionList::const_iterator iter =  mSections.begin(), iterEnd = mSections.end();iter != iterEnd;++iter) {
+		if ((*iter).mName == "hierarchy")	{
 			// "sheath"	0 63 6 
-			for (ElementList::const_iterator
-				eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end();
-				eit != eitEnd; ++eit)
-			{
+			for (ElementList::const_iterator eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end();eit != eitEnd; ++eit) {
 				mAnimatedBones.push_back ( AnimBoneDesc () );
 				AnimBoneDesc& desc = mAnimatedBones.back();
 
@@ -430,20 +379,13 @@ MD5AnimParser::MD5AnimParser(SectionList& mSections)
 				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;
+				// parent index - negative values are allowed (at least -1)
+				desc.mParentIndex = ::strtol10s(sz,&sz);
 
 				// 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);
+				if(63 < (desc.iFlags = ::strtol10(sz,&sz))){
+					MD5Parser::ReportWarning("Invalid flag combination in hierarchy section",(*eit).iLineNumber);
 				}
 				AI_MD5_SKIP_SPACES();
 
@@ -451,14 +393,9 @@ MD5AnimParser::MD5AnimParser(SectionList& mSections)
 				desc.iFirstKeyIndex = ::strtol10(sz,&sz);
 			}
 		}
-		else if(!::strcmp("baseframe",(*iter).mName.c_str()))
-		{
-			// now read all elements
+		else if((*iter).mName == "baseframe")	{
 			// ( -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)
-			{
+			for (ElementList::const_iterator eit = (*iter).mElements.begin(), eitEnd = (*iter).mElements.end(); eit != eitEnd; ++eit) {
 				const char* sz = (*eit).szStart;
 
 				mBaseFrames.push_back ( BaseFrameDesc () );
@@ -468,12 +405,10 @@ MD5AnimParser::MD5AnimParser(SectionList& mSections)
 				AI_MD5_READ_TRIPLE(desc.vRotationQuat);
 			}
 		}
-		else if(!::strcmp("frame",(*iter).mName.c_str()))
-		{
+		else if((*iter).mName ==  "frame")	{
 			if (!(*iter).mGlobalValue.length())
 			{
-				MD5Parser::ReportWarning("A frame section must have a frame index",
-					(*iter).iLineNumber);
+				MD5Parser::ReportWarning("A frame section must have a frame index",(*iter).iLineNumber);
 				continue;
 			}
 
@@ -485,57 +420,43 @@ MD5AnimParser::MD5AnimParser(SectionList& mSections)
 			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)
-			{
+			// now read all elements (continous list of floats)
+			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);
+				while (SkipSpacesAndLineEnd(&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((*iter).mName == "numFrames")	{
+			mFrames.reserve(::strtol10((*iter).mGlobalValue.c_str()));
 		}
-		else if(!::strcmp("numJoints",(*iter).mName.c_str()))
-		{
-			unsigned int iNum;
-			if((iNum = ::strtol10((*iter).mGlobalValue.c_str())))
-			{
-				mAnimatedBones.reserve(iNum);
+		else if((*iter).mName == "numJoints")	{
+			const unsigned int num = ::strtol10((*iter).mGlobalValue.c_str());
+			mAnimatedBones.reserve(num);
 
-				// try to guess the number of animated components if that element is not given
-				if (0xffffffff == mNumAnimatedComponents)
-					mNumAnimatedComponents = iNum * 6;
-			}
+			// try to guess the number of animated components if that element is not given
+			if (0xffffffff == mNumAnimatedComponents)
+				mNumAnimatedComponents = num * 6;
 		}
-		else if(!::strcmp("numAnimatedComponents",(*iter).mName.c_str()))
-		{
-			unsigned int iNum;
-			if((iNum = ::strtol10((*iter).mGlobalValue.c_str())))
-			{
-				mAnimatedBones.reserve(iNum);
-			}
+		else if((*iter).mName == "numAnimatedComponents")	{
+			mAnimatedBones.reserve( ::strtol10((*iter).mGlobalValue.c_str()));
 		}
-		else if(!::strcmp("frameRate",(*iter).mName.c_str()))
-		{
-			fast_atof_move((*iter).mGlobalValue.c_str(),this->fFrameRate);
+		else if((*iter).mName == "frameRate")	{
+			fast_atof_move((*iter).mGlobalValue.c_str(),fFrameRate);
 		}
 	}
 	DefaultLogger::get()->debug("MD5AnimParser end");
 }
 
-#undef AI_MD5_SKIP_SPACES
-#undef AI_MD5_READ_TRIPLE
-#undef AI_MD5_PARSE_STRING
+// ------------------------------------------------------------------------------------------------
+// .MD5CAMERA parsing function
+MD5CameraParser::MD5CameraParser(SectionList& mSections)
+{
+	DefaultLogger::get()->debug("MD5CameraParser begin");
+	fFrameRate = 24.0f;
+
+	DefaultLogger::get()->debug("MD5CameraParser end");
+}
+

+ 62 - 19
code/MD5Parser.h

@@ -98,21 +98,28 @@ struct Section
 typedef std::vector< Section> SectionList;
 
 // ---------------------------------------------------------------------------
-/** Represents a bone (joint) descriptor in a MD5Mesh file
+/** Basic information about a joint
 */
-struct BoneDesc
+struct BaseJointDescription
 {
 	//! Name of the bone
 	aiString mName;
 
 	//! Parent index of the bone
 	int mParentIndex;
+};
 
-	//! Relative position of the bone
+// ---------------------------------------------------------------------------
+/** Represents a bone (joint) descriptor in a MD5Mesh file
+*/
+struct BoneDesc : BaseJointDescription
+{
+	//! Absolute position of the bone
 	aiVector3D mPositionXYZ;
 
-	//! Relative rotation of the bone
+	//! Absolute rotation of the bone
 	aiVector3D mRotationQuat;
+	aiQuaternion mRotationQuatConverted;
 
 	//! Absolute transformation of the bone
 	//! (temporary)
@@ -131,14 +138,8 @@ typedef std::vector< BoneDesc > BoneList;
 // ---------------------------------------------------------------------------
 /** Represents a bone (joint) descriptor in a MD5Anim file
 */
-struct AnimBoneDesc
+struct AnimBoneDesc : BaseJointDescription
 {
-	//! Name of the bone
-	aiString mName;
-
-	//! Parent index of the bone
-	int mParentIndex;
-
 	//! Flags (AI_MD5_ANIMATION_FLAG_xxx)
 	unsigned int iFlags;
 
@@ -160,6 +161,15 @@ struct BaseFrameDesc
 
 typedef std::vector< BaseFrameDesc > BaseFrameList;
 
+// ---------------------------------------------------------------------------
+/** Represents a camera animation frame in a MDCamera file
+*/
+struct CameraAnimFrameDesc : BaseFrameDesc
+{
+	float fFOV;
+};
+
+typedef std::vector< CameraAnimFrameDesc > CameraFrameList;
 
 // ---------------------------------------------------------------------------
 /** Represents a frame descriptor in a MD5Anim file
@@ -237,6 +247,21 @@ struct MeshDesc
 
 typedef std::vector< MeshDesc > MeshList;
 
+// ---------------------------------------------------------------------------
+// Convert a quaternion to its usual representation
+inline void ConvertQuaternion (const aiVector3D& in, aiQuaternion& out) {
+
+	out.x = in.x;
+	out.y = in.y;
+	out.z = in.z;
+
+	const float t = 1.0f - (in.x*in.x) - (in.y*in.y) - (in.z*in.z);
+
+	if (t < 0.0f)
+		out.w = 0.0f;
+	else out.w = sqrt (t);
+}
+
 // ---------------------------------------------------------------------------
 /** Parses the data sections of a MD5 mesh file
 */
@@ -259,14 +284,6 @@ public:
 	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
 
@@ -302,6 +319,32 @@ public:
 	unsigned int mNumAnimatedComponents;
 };
 
+// ---------------------------------------------------------------------------
+/** Parses the data sections of a MD5 camera animation file
+*/
+class MD5CameraParser
+{
+public:
+
+	// -------------------------------------------------------------------
+	/** Constructs a new MD5CameraParser instance from an existing
+	 *  preparsed list of file sections.
+	 *
+	 *  @param mSections List of file sections (output of MD5Parser)
+	 */
+	MD5CameraParser(SectionList& mSections);
+
+	
+	//! Output frame rate
+	float fFrameRate;
+
+	//! List of cuts
+	std::vector<unsigned int> cuts;
+
+	//! Frames
+	CameraFrameList frames;
+};
+
 // ---------------------------------------------------------------------------
 /** Parses the block structure of MD5MESH and MD5ANIM files (but does no
  *  further processing)

+ 5 - 2
code/SkeletonMeshBuilder.cpp

@@ -48,14 +48,17 @@ using namespace Assimp;
 
 // ------------------------------------------------------------------------------------------------
 // The constructor processes the given scene and adds a mesh there. 
-SkeletonMeshBuilder::SkeletonMeshBuilder( aiScene* pScene)
+SkeletonMeshBuilder::SkeletonMeshBuilder( aiScene* pScene, aiNode* root)
 {
 	// nothing to do if there's mesh data already present at the scene
 	if( pScene->mNumMeshes > 0 || pScene->mRootNode == NULL)
 		return;
 
+	if (!root)
+		root = pScene->mRootNode;
+
 	// build some faces around each node 
-	CreateGeometry( pScene->mRootNode);
+	CreateGeometry( root );
 
 	// create a mesh to hold all the generated faces
 	pScene->mNumMeshes = 1;

+ 2 - 1
code/SkeletonMeshBuilder.h

@@ -63,8 +63,9 @@ public:
   /** The constructor processes the given scene and adds a mesh there. Does nothing
    * if the scene already has mesh data. 
    * @param pScene The scene for which a skeleton mesh should be constructed.
+   * @param root The node to start with. NULL is the scene root
    */
-  SkeletonMeshBuilder( aiScene* pScene);
+  SkeletonMeshBuilder( aiScene* pScene, aiNode* root = NULL);
 
 protected:
   /** Recursively builds a simple mesh representation for the given node and also creates

+ 15 - 0
include/aiConfig.h

@@ -367,6 +367,21 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #define AI_CONFIG_PP_FD_REMOVE \
 	"PP_FD_REMOVE"
 
+
+// ---------------------------------------------------------------------------
+/** @brief  Configures the MD5 loader to not load the MD5ANIM file for
+ *  a MD5MESH file automatically.
+ * 
+ * The default strategy is to look for a file with the same name but the
+ * MD5ANIM extension in the same directory. If it is found, it is loaded
+ * and combined with the MD5MESH file. This configuration option can be
+ * used to disable this behaviour.
+ * 
+ * Property type: integer (0: false; !0: true). Default value: false.
+ */
+#define AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD			\
+	"IMPORT_MD5_NO_ANIM_AUTOLOAD"
+
 #if 0
 // ---------------------------------------------------------------------------
 /** @brief Specifies the shape of the scene returned by the CSM format loader.

+ 27 - 3
include/aiQuaternion.h

@@ -84,6 +84,12 @@ struct aiQuaternion
 	/** Normalize the quaternion */
 	aiQuaternion& Normalize();
 
+	/** Compute quaternion conjugate */
+	aiQuaternion& Conjugate ();
+
+	/** Rotate a point by this quaternion */
+	aiVector3D Rotate (const aiVector3D& in);
+
 	/** Multiply two quaternions */
 	aiQuaternion operator* (const aiQuaternion& two) const;
 
@@ -204,13 +210,11 @@ inline aiQuaternion::aiQuaternion( aiVector3D normalized)
 	y = normalized.y;
 	z = normalized.z;
 
-	float t = 1.0f - (normalized.x * normalized.x) - 
-		(normalized.y * normalized.y) - (normalized.z * normalized.z);
+	const float t = 1.0f - (x*x) - (y*y) - (z*z);
 
 	if (t < 0.0f)
 		w = 0.0f;
 	else w = sqrt (t);
-
 }
 
 // ---------------------------------------------------------------------------
@@ -280,6 +284,26 @@ inline aiQuaternion aiQuaternion::operator* (const aiQuaternion& t) const
 		w*t.z + z*t.w + x*t.y - y*t.x);
 }
 
+// ---------------------------------------------------------------------------
+inline aiQuaternion& aiQuaternion::Conjugate ()
+{
+	x = -x;
+	y = -y;
+	z = -z;
+	return *this;
+}
+
+// ---------------------------------------------------------------------------
+inline aiVector3D aiQuaternion::Rotate (const aiVector3D& v)
+{
+	aiQuaternion q2(0.f,v.x,v.y,v.z), q = *this, qinv = q;
+	q.Conjugate();
+
+	q = q*q2*qinv;
+	return aiVector3D(q.x,q.y,q.z);
+
+}
+
 } // end extern "C"
 
 #endif // __cplusplus

+ 0 - 0
test/models/MD5/License.txt → test/models/MD5/BoarMan.source.txt


+ 74 - 0
test/models/MD5/SimpleCube.md5mesh

@@ -0,0 +1,74 @@
+MD5Version 10
+commandline "mesh models/code/code.ma -dest models/code/code.md5mesh -game blackrose -game sw"
+
+numJoints 3
+numMeshes 1
+
+joints {
+"origin" -1 ( 0 0 0 ) ( 0 0 0 ) // 
+"root" 0 ( 0 0 0 ) ( -0.5001951456 0.4998047054 -0.4998047054 ) // origin
+"joint1" 1 ( 0.0249728262 0.0000019062 31.9803333282 ) ( -0.7071067691 0.0000000527 0.0000000105 ) // root
+}
+
+mesh {
+// meshes: Mesh
+shader ""
+
+numverts 24
+vert 0 ( 0.1875 -0.25 ) 0 2
+vert 1 ( -0.3125 -0.25 ) 2 2
+vert 2 ( -0.3125 0.25 ) 4 2
+vert 3 ( 0.1875 0.25 ) 6 2
+vert 4 ( 0.1875 -0.25 ) 8 2
+vert 5 ( -0.3125 -0.25 ) 10 2
+vert 6 ( -0.3125 0.25 ) 12 2
+vert 7 ( 0.1875 0.25 ) 14 2
+vert 8 ( -0.25 -0.5 ) 10 2
+vert 9 ( -0.25 0 ) 4 2
+vert 10 ( 0.25 0 ) 2 2
+vert 11 ( 0.25 -0.5 ) 12 2
+vert 12 ( -0.3125 -0.5 ) 12 2
+vert 13 ( -0.3125 0 ) 2 2
+vert 14 ( 0.1875 0 ) 0 2
+vert 15 ( 0.1875 -0.5 ) 14 2
+vert 16 ( -0.25 -0.5 ) 14 2
+vert 17 ( -0.25 0 ) 0 2
+vert 18 ( 0.25 0 ) 6 2
+vert 19 ( 0.25 -0.5 ) 8 2
+vert 20 ( -0.1875 -0.5 ) 8 2
+vert 21 ( -0.1875 0 ) 6 2
+vert 22 ( 0.3125 0 ) 4 2
+vert 23 ( 0.3125 -0.5 ) 10 2
+
+numtris 12
+tri 0 2 1 0
+tri 1 2 0 3
+tri 2 6 5 4
+tri 3 6 4 7
+tri 4 10 9 8
+tri 5 10 8 11
+tri 6 14 13 12
+tri 7 14 12 15
+tri 8 18 17 16
+tri 9 18 16 19
+tri 10 22 21 20
+tri 11 22 20 23
+
+numweights 16
+weight 0 1 0.7386021614 ( -31.9750022888 -32.0249786377 -32 )
+weight 1 2 0.2613978386 ( 31.9750270844 -63.9803352356 -32 )
+weight 2 1 0.7386021614 ( -31.9750022888 -32.0249786377 32 )
+weight 3 2 0.2613978386 ( 31.9750270844 -63.9803352356 32 )
+weight 4 1 0.7387529016 ( -32.0249786377 31.9750022888 32 )
+weight 5 2 0.2612471282 ( -32.0249710083 -63.9803352356 32 )
+weight 6 1 0.7387529016 ( -32.0249786377 31.9750022888 -32 )
+weight 7 2 0.2612471282 ( -32.0249710083 -63.9803352356 -32 )
+weight 8 1 0.5 ( 31.9750022888 32.0249786377 -32 )
+weight 9 2 0.5 ( -32.0249710083 0.0196647644 -32 )
+weight 10 1 0.5 ( 31.9750022888 32.0249786377 32 )
+weight 11 2 0.5 ( -32.0249710083 0.0196647644 32 )
+weight 12 1 0.5 ( 32.0249786377 -31.9750022888 32 )
+weight 13 2 0.5 ( 31.9750270844 0.0196647644 32 )
+weight 14 1 0.5 ( 32.0249786377 -31.9750022888 -32 )
+weight 15 2 0.5 ( 31.9750270844 0.0196647644 -32 )
+}

+ 1 - 0
test/models/MD5/SimpleCube.source.txt

@@ -0,0 +1 @@
+From http://www.doom3world.org/phpbb2/viewtopic.php?f=3&t=16842 (thanks, Rayne, whoever you are)

+ 1 - 0
tools/assimp_view/assimp_view.cpp

@@ -148,6 +148,7 @@ DWORD WINAPI LoadThreadProc(LPVOID lpParameter)
 		aiProcess_GenUVCoords              | // convert spherical, cylindrical, box and planar mapping to proper UVs
 		aiProcess_TransformUVCoords        | // preprocess UV transformations (scaling, translation ...)
 		aiProcess_FindInstances            | // search for instanced meshes and remove them by references to one master
+		aiProcess_LimitBoneWeights         | // limit bone weights to 4 per vertex
 //		aiProcess_PreTransformVertices |
 		0);