diff --git a/code/BVHLoader.cpp b/code/BVHLoader.cpp index c3c28c43b..1ce2be908 100644 --- a/code/BVHLoader.cpp +++ b/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; + 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; + 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 ); - } - } + // 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); diff --git a/code/LimitBoneWeightsProcess.cpp b/code/LimitBoneWeightsProcess.cpp index bbc492969..6bee3943c 100644 --- a/code/LimitBoneWeightsProcess.cpp +++ b/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::const_iterator - iter = abNoNeed.begin(); - iter != abNoNeed.end() ;++iter) - { - if (*iter) - { + for (std::vector::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); + } } } diff --git a/code/MD5Loader.cpp b/code/MD5Loader.cpp index 1b59f01a2..b7a7f2e46 100644 --- a/code/MD5Loader.cpp +++ b/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; + mFile = (std::string::npos == pos ? pFile : pFile.substr(0,pos+1)); - bHadMD5Mesh = bHadMD5Anim = false; + 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; + } - // load the animation keyframes - LoadMD5AnimFile(); - - // load the mesh vertices and bones - LoadMD5MeshFile(); - - // 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 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 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(""); 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(""); 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(""); 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::const_iterator end = meshParser.mMeshes.end(); // FIX: MD5 files exported from Blender can have empty meshes - for (std::vector::const_iterator - it = meshParser.mMeshes.begin(), - end = meshParser.mMeshes.end(); it != end;++it) - { + for (std::vector::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::iterator - it = meshParser.mMeshes.begin(), - end = meshParser.mMeshes.end(); it != end;++it) - { + for (std::vector::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 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++; - - // translation on the x axis - if ((*iter2).iFlags & AI_MD5_ANIMATION_FLAG_TRANSLATE_X) - vKey->mValue.x = *fpCur++; - else vKey->mValue.x = pcBaseFrame->vPositionXYZ.x; - - // translation on the y axis - if ((*iter2).iFlags & AI_MD5_ANIMATION_FLAG_TRANSLATE_Y) - vKey->mValue.y = *fpCur++; - else vKey->mValue.y = pcBaseFrame->vPositionXYZ.y; - - // translation on the z axis - if ((*iter2).iFlags & AI_MD5_ANIMATION_FLAG_TRANSLATE_Z) - vKey->mValue.z = *fpCur++; - else vKey->mValue.z = pcBaseFrame->vPositionXYZ.z; - - - // rotation quaternion, x component aiQuatKey* qKey = pcCurAnimBone->mRotationKeys++; aiVector3D vTemp; - if ((*iter2).iFlags & AI_MD5_ANIMATION_FLAG_ROTQUAT_X) - vTemp.x = *fpCur++; - else vTemp.x = pcBaseFrame->vRotationQuat.x; - // rotation quaternion, y component - if ((*iter2).iFlags & AI_MD5_ANIMATION_FLAG_ROTQUAT_Y) - vTemp.y = *fpCur++; - else vTemp.y = pcBaseFrame->vRotationQuat.y; + // 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]; + } - // rotation quaternion, z component - if ((*iter2).iFlags & AI_MD5_ANIMATION_FLAG_ROTQUAT_Z) - vTemp.z = *fpCur++; - else vTemp.z = pcBaseFrame->vRotationQuat.z; + MD5::ConvertQuaternion(vTemp, qKey->mValue); - // compute the w component of the quaternion - invert it (DX to OGL) - qKey->mValue = aiQuaternion(vTemp); - //qKey->mValue.w *= -1.0f; + aiMatrix4x4 m; + aiMatrix4x4::Translation(vKey->mValue,m); + m = m*aiMatrix4x4( qKey->mValue.GetMatrix() ); + m.DecomposeNoScaling(qKey->mValue,vKey->mValue); - 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(""); + 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 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 diff --git a/code/MD5Loader.h b/code/MD5Loader.h index c2c7d8981..84ae840f7 100644 --- a/code/MD5Loader.h +++ b/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 diff --git a/code/MD5Parser.cpp b/code/MD5Parser.cpp index bd25b6dbe..59cfae742 100644 --- a/code/MD5Parser.cpp +++ b/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"); +} + diff --git a/code/MD5Parser.h b/code/MD5Parser.h index 5ad5a92fc..b25ce3828 100644 --- a/code/MD5Parser.h +++ b/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 cuts; + + //! Frames + CameraFrameList frames; +}; + // --------------------------------------------------------------------------- /** Parses the block structure of MD5MESH and MD5ANIM files (but does no * further processing) diff --git a/code/SkeletonMeshBuilder.cpp b/code/SkeletonMeshBuilder.cpp index 0fa155e76..5605e2dd2 100644 --- a/code/SkeletonMeshBuilder.cpp +++ b/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; diff --git a/code/SkeletonMeshBuilder.h b/code/SkeletonMeshBuilder.h index b11bd41e8..9b1dc086c 100644 --- a/code/SkeletonMeshBuilder.h +++ b/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 diff --git a/include/aiConfig.h b/include/aiConfig.h index c49f1c9bd..0182cbf99 100644 --- a/include/aiConfig.h +++ b/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. diff --git a/include/aiQuaternion.h b/include/aiQuaternion.h index 8cab09d14..a3dfab78c 100644 --- a/include/aiQuaternion.h +++ b/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 diff --git a/test/models/MD5/License.txt b/test/models/MD5/BoarMan.source.txt similarity index 100% rename from test/models/MD5/License.txt rename to test/models/MD5/BoarMan.source.txt diff --git a/test/models/MD5/SimpleCube.md5mesh b/test/models/MD5/SimpleCube.md5mesh new file mode 100644 index 000000000..6aa798ea6 --- /dev/null +++ b/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 ) +} diff --git a/test/models/MD5/SimpleCube.source.txt b/test/models/MD5/SimpleCube.source.txt new file mode 100644 index 000000000..6f51bc1cf --- /dev/null +++ b/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) \ No newline at end of file diff --git a/tools/assimp_view/assimp_view.cpp b/tools/assimp_view/assimp_view.cpp index cd3be8595..242164fcf 100644 --- a/tools/assimp_view/assimp_view.cpp +++ b/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);