nuclear@0: /* nuclear@0: --------------------------------------------------------------------------- nuclear@0: Open Asset Import Library (assimp) nuclear@0: --------------------------------------------------------------------------- nuclear@0: nuclear@0: Copyright (c) 2006-2012, assimp team nuclear@0: nuclear@0: All rights reserved. nuclear@0: nuclear@0: Redistribution and use of this software in source and binary forms, nuclear@0: with or without modification, are permitted provided that the following nuclear@0: conditions are met: nuclear@0: nuclear@0: * Redistributions of source code must retain the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer. nuclear@0: nuclear@0: * Redistributions in binary form must reproduce the above nuclear@0: copyright notice, this list of conditions and the nuclear@0: following disclaimer in the documentation and/or other nuclear@0: materials provided with the distribution. nuclear@0: nuclear@0: * Neither the name of the assimp team, nor the names of its nuclear@0: contributors may be used to endorse or promote products nuclear@0: derived from this software without specific prior nuclear@0: written permission of the assimp team. nuclear@0: nuclear@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS nuclear@0: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT nuclear@0: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR nuclear@0: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT nuclear@0: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, nuclear@0: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT nuclear@0: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, nuclear@0: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY nuclear@0: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT nuclear@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE nuclear@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. nuclear@0: --------------------------------------------------------------------------- nuclear@0: */ nuclear@0: nuclear@0: /** @file MD5Loader.cpp nuclear@0: * @brief Implementation of the MD5 importer class nuclear@0: */ nuclear@0: nuclear@0: #include "AssimpPCH.h" nuclear@0: #ifndef ASSIMP_BUILD_NO_MD5_IMPORTER nuclear@0: nuclear@0: // internal headers nuclear@0: #include "RemoveComments.h" nuclear@0: #include "MD5Loader.h" nuclear@0: #include "StringComparison.h" nuclear@0: #include "fast_atof.h" nuclear@0: #include "SkeletonMeshBuilder.h" nuclear@0: nuclear@0: using namespace Assimp; nuclear@0: nuclear@0: // Minimum weight value. Weights inside [-n ... n] are ignored nuclear@0: #define AI_MD5_WEIGHT_EPSILON 1e-5f nuclear@0: nuclear@0: nuclear@0: static const aiImporterDesc desc = { nuclear@0: "Doom 3 / MD5 Mesh Importer", nuclear@0: "", nuclear@0: "", nuclear@0: "", nuclear@0: aiImporterFlags_SupportBinaryFlavour, nuclear@0: 0, nuclear@0: 0, nuclear@0: 0, nuclear@0: 0, nuclear@0: "md5mesh md5camera md5anim" nuclear@0: }; nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Constructor to be privately used by Importer nuclear@0: MD5Importer::MD5Importer() nuclear@0: : mBuffer() nuclear@0: , configNoAutoLoad (false) nuclear@0: {} nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Destructor, private as well nuclear@0: MD5Importer::~MD5Importer() nuclear@0: {} nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Returns whether the class can handle the format of the given file. nuclear@0: bool MD5Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const nuclear@0: { nuclear@0: const std::string extension = GetExtension(pFile); nuclear@0: nuclear@0: if (extension == "md5anim" || extension == "md5mesh" || extension == "md5camera") nuclear@0: return true; nuclear@0: else if (!extension.length() || checkSig) { nuclear@0: if (!pIOHandler) { nuclear@0: return true; nuclear@0: } nuclear@0: const char* tokens[] = {"MD5Version"}; nuclear@0: return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1); nuclear@0: } nuclear@0: return false; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Get list of all supported extensions nuclear@0: const aiImporterDesc* MD5Importer::GetInfo () const nuclear@0: { nuclear@0: return &desc; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Setup import properties nuclear@0: void MD5Importer::SetupProperties(const Importer* pImp) nuclear@0: { nuclear@0: // AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD nuclear@0: configNoAutoLoad = (0 != pImp->GetPropertyInteger(AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD,0)); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Imports the given file into the given scene structure. nuclear@0: void MD5Importer::InternReadFile( const std::string& pFile, nuclear@0: aiScene* _pScene, IOSystem* _pIOHandler) nuclear@0: { nuclear@0: pIOHandler = _pIOHandler; nuclear@0: pScene = _pScene; nuclear@0: bHadMD5Mesh = bHadMD5Anim = bHadMD5Camera = false; nuclear@0: nuclear@0: // remove the file extension nuclear@0: const std::string::size_type pos = pFile.find_last_of('.'); nuclear@0: mFile = (std::string::npos == pos ? pFile : pFile.substr(0,pos+1)); nuclear@0: nuclear@0: const std::string extension = GetExtension(pFile); nuclear@0: try { nuclear@0: if (extension == "md5camera") { nuclear@0: LoadMD5CameraFile(); nuclear@0: } nuclear@0: else if (configNoAutoLoad || extension == "md5anim") { nuclear@0: // determine file extension and process just *one* file nuclear@0: if (extension.length() == 0) { nuclear@0: throw DeadlyImportError("Failure, need file extension to determine MD5 part type"); nuclear@0: } nuclear@0: if (extension == "md5anim") { nuclear@0: LoadMD5AnimFile(); nuclear@0: } nuclear@0: else if (extension == "md5mesh") { nuclear@0: LoadMD5MeshFile(); nuclear@0: } nuclear@0: } nuclear@0: else { nuclear@0: LoadMD5MeshFile(); nuclear@0: LoadMD5AnimFile(); nuclear@0: } nuclear@0: } nuclear@0: catch ( ... ) { // std::exception, Assimp::DeadlyImportError nuclear@0: UnloadFileFromMemory(); nuclear@0: throw; nuclear@0: } nuclear@0: nuclear@0: // make sure we have at least one file nuclear@0: if (!bHadMD5Mesh && !bHadMD5Anim && !bHadMD5Camera) { nuclear@0: throw DeadlyImportError("Failed to read valid contents out of this MD5* file"); nuclear@0: } nuclear@0: nuclear@0: // Now rotate the whole scene 90 degrees around the x axis to match our internal coordinate system nuclear@0: pScene->mRootNode->mTransformation = aiMatrix4x4(1.f,0.f,0.f,0.f, nuclear@0: 0.f,0.f,1.f,0.f,0.f,-1.f,0.f,0.f,0.f,0.f,0.f,1.f); nuclear@0: nuclear@0: // the output scene wouldn't pass the validation without this flag nuclear@0: if (!bHadMD5Mesh) { nuclear@0: pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; nuclear@0: } nuclear@0: nuclear@0: // clean the instance -- the BaseImporter instance may be reused later. nuclear@0: UnloadFileFromMemory(); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Load a file into a memory buffer nuclear@0: void MD5Importer::LoadFileIntoMemory (IOStream* file) nuclear@0: { nuclear@0: // unload the previous buffer, if any nuclear@0: UnloadFileFromMemory(); nuclear@0: nuclear@0: ai_assert(NULL != file); nuclear@0: fileSize = (unsigned int)file->FileSize(); nuclear@0: ai_assert(fileSize); nuclear@0: nuclear@0: // allocate storage and copy the contents of the file to a memory buffer nuclear@0: mBuffer = new char[fileSize+1]; nuclear@0: file->Read( (void*)mBuffer, 1, fileSize); nuclear@0: iLineNumber = 1; nuclear@0: nuclear@0: // append a terminal 0 nuclear@0: mBuffer[fileSize] = '\0'; nuclear@0: nuclear@0: // now remove all line comments from the file nuclear@0: CommentRemover::RemoveLineComments("//",mBuffer,' '); nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Unload the current memory buffer nuclear@0: void MD5Importer::UnloadFileFromMemory () nuclear@0: { nuclear@0: // delete the file buffer nuclear@0: delete[] mBuffer; nuclear@0: mBuffer = NULL; nuclear@0: fileSize = 0; nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Build unique vertices nuclear@0: void MD5Importer::MakeDataUnique (MD5::MeshDesc& meshSrc) nuclear@0: { nuclear@0: std::vector abHad(meshSrc.mVertices.size(),false); nuclear@0: nuclear@0: // allocate enough storage to keep the output structures nuclear@0: const unsigned int iNewNum = meshSrc.mFaces.size()*3; nuclear@0: unsigned int iNewIndex = meshSrc.mVertices.size(); nuclear@0: meshSrc.mVertices.resize(iNewNum); nuclear@0: nuclear@0: // try to guess how much storage we'll need for new weights nuclear@0: const float fWeightsPerVert = meshSrc.mWeights.size() / (float)iNewIndex; nuclear@0: const unsigned int guess = (unsigned int)(fWeightsPerVert*iNewNum); nuclear@0: meshSrc.mWeights.reserve(guess + (guess >> 3)); // + 12.5% as buffer nuclear@0: nuclear@0: for (FaceList::const_iterator iter = meshSrc.mFaces.begin(),iterEnd = meshSrc.mFaces.end();iter != iterEnd;++iter){ nuclear@0: const aiFace& face = *iter; nuclear@0: for (unsigned int i = 0; i < 3;++i) { nuclear@0: if (face.mIndices[0] >= meshSrc.mVertices.size()) { nuclear@0: throw DeadlyImportError("MD5MESH: Invalid vertex index"); nuclear@0: } nuclear@0: nuclear@0: if (abHad[face.mIndices[i]]) { nuclear@0: // generate a new vertex nuclear@0: meshSrc.mVertices[iNewIndex] = meshSrc.mVertices[face.mIndices[i]]; nuclear@0: face.mIndices[i] = iNewIndex++; nuclear@0: } nuclear@0: else abHad[face.mIndices[i]] = true; nuclear@0: } nuclear@0: // swap face order nuclear@0: std::swap(face.mIndices[0],face.mIndices[2]); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Recursive node graph construction from a MD5MESH nuclear@0: void MD5Importer::AttachChilds_Mesh(int iParentID,aiNode* piParent, BoneList& bones) nuclear@0: { nuclear@0: ai_assert(NULL != piParent && !piParent->mNumChildren); nuclear@0: nuclear@0: // First find out how many children we'll have nuclear@0: for (int i = 0; i < (int)bones.size();++i) { nuclear@0: if (iParentID != i && bones[i].mParentIndex == iParentID) { nuclear@0: ++piParent->mNumChildren; nuclear@0: } nuclear@0: } nuclear@0: if (piParent->mNumChildren) { nuclear@0: piParent->mChildren = new aiNode*[piParent->mNumChildren]; nuclear@0: for (int i = 0; i < (int)bones.size();++i) { nuclear@0: // (avoid infinite recursion) nuclear@0: if (iParentID != i && bones[i].mParentIndex == iParentID) { nuclear@0: aiNode* pc; nuclear@0: // setup a new node nuclear@0: *piParent->mChildren++ = pc = new aiNode(); nuclear@0: pc->mName = aiString(bones[i].mName); nuclear@0: pc->mParent = piParent; nuclear@0: nuclear@0: // get the transformation matrix from rotation and translational components nuclear@0: aiQuaternion quat; nuclear@0: MD5::ConvertQuaternion ( bones[i].mRotationQuat, quat ); nuclear@0: nuclear@0: // FIX to get to Assimp's quaternion conventions nuclear@0: quat.w *= -1.f; nuclear@0: nuclear@0: bones[i].mTransform = aiMatrix4x4 ( quat.GetMatrix()); nuclear@0: bones[i].mTransform.a4 = bones[i].mPositionXYZ.x; nuclear@0: bones[i].mTransform.b4 = bones[i].mPositionXYZ.y; nuclear@0: bones[i].mTransform.c4 = bones[i].mPositionXYZ.z; nuclear@0: nuclear@0: // store it for later use nuclear@0: pc->mTransformation = bones[i].mInvTransform = bones[i].mTransform; nuclear@0: bones[i].mInvTransform.Inverse(); nuclear@0: nuclear@0: // the transformations for each bone are absolute, so we need to multiply them nuclear@0: // with the inverse of the absolute matrix of the parent joint nuclear@0: if (-1 != iParentID) { nuclear@0: pc->mTransformation = bones[iParentID].mInvTransform * pc->mTransformation; nuclear@0: } nuclear@0: nuclear@0: // add children to this node, too nuclear@0: AttachChilds_Mesh( i, pc, bones); nuclear@0: } nuclear@0: } nuclear@0: // undo offset computations nuclear@0: piParent->mChildren -= piParent->mNumChildren; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Recursive node graph construction from a MD5ANIM nuclear@0: void MD5Importer::AttachChilds_Anim(int iParentID,aiNode* piParent, AnimBoneList& bones,const aiNodeAnim** node_anims) nuclear@0: { nuclear@0: ai_assert(NULL != piParent && !piParent->mNumChildren); nuclear@0: nuclear@0: // First find out how many children we'll have nuclear@0: for (int i = 0; i < (int)bones.size();++i) { nuclear@0: if (iParentID != i && bones[i].mParentIndex == iParentID) { nuclear@0: ++piParent->mNumChildren; nuclear@0: } nuclear@0: } nuclear@0: if (piParent->mNumChildren) { nuclear@0: piParent->mChildren = new aiNode*[piParent->mNumChildren]; nuclear@0: for (int i = 0; i < (int)bones.size();++i) { nuclear@0: // (avoid infinite recursion) nuclear@0: if (iParentID != i && bones[i].mParentIndex == iParentID) nuclear@0: { nuclear@0: aiNode* pc; nuclear@0: // setup a new node nuclear@0: *piParent->mChildren++ = pc = new aiNode(); nuclear@0: pc->mName = aiString(bones[i].mName); nuclear@0: pc->mParent = piParent; nuclear@0: nuclear@0: // get the corresponding animation channel and its first frame nuclear@0: const aiNodeAnim** cur = node_anims; nuclear@0: while ((**cur).mNodeName != pc->mName)++cur; nuclear@0: nuclear@0: aiMatrix4x4::Translation((**cur).mPositionKeys[0].mValue,pc->mTransformation); nuclear@0: pc->mTransformation = pc->mTransformation * aiMatrix4x4((**cur).mRotationKeys[0].mValue.GetMatrix()) ; nuclear@0: nuclear@0: // add children to this node, too nuclear@0: AttachChilds_Anim( i, pc, bones,node_anims); nuclear@0: } nuclear@0: } nuclear@0: // undo offset computations nuclear@0: piParent->mChildren -= piParent->mNumChildren; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Load a MD5MESH file nuclear@0: void MD5Importer::LoadMD5MeshFile () nuclear@0: { nuclear@0: std::string pFile = mFile + "md5mesh"; nuclear@0: boost::scoped_ptr file( pIOHandler->Open( pFile, "rb")); nuclear@0: nuclear@0: // Check whether we can read from the file nuclear@0: if( file.get() == NULL || !file->FileSize()) { nuclear@0: DefaultLogger::get()->warn("Failed to access MD5MESH file: " + pFile); nuclear@0: return; nuclear@0: } nuclear@0: bHadMD5Mesh = true; nuclear@0: LoadFileIntoMemory(file.get()); nuclear@0: nuclear@0: // now construct a parser and parse the file nuclear@0: MD5::MD5Parser parser(mBuffer,fileSize); nuclear@0: nuclear@0: // load the mesh information from it nuclear@0: MD5::MD5MeshParser meshParser(parser.mSections); nuclear@0: nuclear@0: // create the bone hierarchy - first the root node and dummy nodes for all meshes nuclear@0: pScene->mRootNode = new aiNode(""); nuclear@0: pScene->mRootNode->mNumChildren = 2; nuclear@0: pScene->mRootNode->mChildren = new aiNode*[2]; nuclear@0: nuclear@0: // build the hierarchy from the MD5MESH file nuclear@0: aiNode* pcNode = pScene->mRootNode->mChildren[1] = new aiNode(); nuclear@0: pcNode->mName.Set(""); nuclear@0: pcNode->mParent = pScene->mRootNode; nuclear@0: AttachChilds_Mesh(-1,pcNode,meshParser.mJoints); nuclear@0: nuclear@0: pcNode = pScene->mRootNode->mChildren[0] = new aiNode(); nuclear@0: pcNode->mName.Set(""); nuclear@0: pcNode->mParent = pScene->mRootNode; nuclear@0: nuclear@0: #if 0 nuclear@0: if (pScene->mRootNode->mChildren[1]->mNumChildren) /* start at the right hierarchy level */ nuclear@0: SkeletonMeshBuilder skeleton_maker(pScene,pScene->mRootNode->mChildren[1]->mChildren[0]); nuclear@0: #else nuclear@0: nuclear@0: // FIX: MD5 files exported from Blender can have empty meshes nuclear@0: for (std::vector::const_iterator it = meshParser.mMeshes.begin(),end = meshParser.mMeshes.end(); it != end;++it) { nuclear@0: if (!(*it).mFaces.empty() && !(*it).mVertices.empty()) nuclear@0: ++pScene->mNumMaterials; nuclear@0: } nuclear@0: nuclear@0: // generate all meshes nuclear@0: pScene->mNumMeshes = pScene->mNumMaterials; nuclear@0: pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; nuclear@0: pScene->mMaterials = new aiMaterial*[pScene->mNumMeshes]; nuclear@0: nuclear@0: // storage for node mesh indices nuclear@0: pcNode->mNumMeshes = pScene->mNumMeshes; nuclear@0: pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes]; nuclear@0: for (unsigned int m = 0; m < pcNode->mNumMeshes;++m) nuclear@0: pcNode->mMeshes[m] = m; nuclear@0: nuclear@0: unsigned int n = 0; nuclear@0: for (std::vector::iterator it = meshParser.mMeshes.begin(),end = meshParser.mMeshes.end(); it != end;++it) { nuclear@0: MD5::MeshDesc& meshSrc = *it; nuclear@0: if (meshSrc.mFaces.empty() || meshSrc.mVertices.empty()) nuclear@0: continue; nuclear@0: nuclear@0: aiMesh* mesh = pScene->mMeshes[n] = new aiMesh(); nuclear@0: mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; nuclear@0: nuclear@0: // generate unique vertices in our internal verbose format nuclear@0: MakeDataUnique(meshSrc); nuclear@0: nuclear@0: mesh->mNumVertices = (unsigned int) meshSrc.mVertices.size(); nuclear@0: mesh->mVertices = new aiVector3D[mesh->mNumVertices]; nuclear@0: mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; nuclear@0: mesh->mNumUVComponents[0] = 2; nuclear@0: nuclear@0: // copy texture coordinates nuclear@0: aiVector3D* pv = mesh->mTextureCoords[0]; nuclear@0: for (MD5::VertexList::const_iterator iter = meshSrc.mVertices.begin();iter != meshSrc.mVertices.end();++iter,++pv) { nuclear@0: pv->x = (*iter).mUV.x; nuclear@0: pv->y = 1.0f-(*iter).mUV.y; // D3D to OpenGL nuclear@0: pv->z = 0.0f; nuclear@0: } nuclear@0: nuclear@0: // sort all bone weights - per bone nuclear@0: unsigned int* piCount = new unsigned int[meshParser.mJoints.size()]; nuclear@0: ::memset(piCount,0,sizeof(unsigned int)*meshParser.mJoints.size()); nuclear@0: nuclear@0: for (MD5::VertexList::const_iterator iter = meshSrc.mVertices.begin();iter != meshSrc.mVertices.end();++iter,++pv) { nuclear@0: for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights;++w) nuclear@0: { nuclear@0: MD5::WeightDesc& desc = meshSrc.mWeights[w]; nuclear@0: /* FIX for some invalid exporters */ nuclear@0: if (!(desc.mWeight < AI_MD5_WEIGHT_EPSILON && desc.mWeight >= -AI_MD5_WEIGHT_EPSILON )) nuclear@0: ++piCount[desc.mBone]; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // check how many we will need nuclear@0: for (unsigned int p = 0; p < meshParser.mJoints.size();++p) nuclear@0: if (piCount[p])mesh->mNumBones++; nuclear@0: nuclear@0: if (mesh->mNumBones) // just for safety nuclear@0: { nuclear@0: mesh->mBones = new aiBone*[mesh->mNumBones]; nuclear@0: for (unsigned int q = 0,h = 0; q < meshParser.mJoints.size();++q) nuclear@0: { nuclear@0: if (!piCount[q])continue; nuclear@0: aiBone* p = mesh->mBones[h] = new aiBone(); nuclear@0: p->mNumWeights = piCount[q]; nuclear@0: p->mWeights = new aiVertexWeight[p->mNumWeights]; nuclear@0: p->mName = aiString(meshParser.mJoints[q].mName); nuclear@0: p->mOffsetMatrix = meshParser.mJoints[q].mInvTransform; nuclear@0: nuclear@0: // store the index for later use nuclear@0: MD5::BoneDesc& boneSrc = meshParser.mJoints[q]; nuclear@0: boneSrc.mMap = h++; nuclear@0: nuclear@0: // compute w-component of quaternion nuclear@0: MD5::ConvertQuaternion( boneSrc.mRotationQuat, boneSrc.mRotationQuatConverted ); nuclear@0: } nuclear@0: nuclear@0: //unsigned int g = 0; nuclear@0: pv = mesh->mVertices; nuclear@0: for (MD5::VertexList::const_iterator iter = meshSrc.mVertices.begin();iter != meshSrc.mVertices.end();++iter,++pv) { nuclear@0: // compute the final vertex position from all single weights nuclear@0: *pv = aiVector3D(); nuclear@0: nuclear@0: // there are models which have weights which don't sum to 1 ... nuclear@0: float fSum = 0.0f; nuclear@0: for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights;++w) nuclear@0: fSum += meshSrc.mWeights[w].mWeight; nuclear@0: if (!fSum) { nuclear@0: DefaultLogger::get()->error("MD5MESH: The sum of all vertex bone weights is 0"); nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: // process bone weights nuclear@0: for (unsigned int jub = (*iter).mFirstWeight, w = jub; w < jub + (*iter).mNumWeights;++w) { nuclear@0: if (w >= meshSrc.mWeights.size()) nuclear@0: throw DeadlyImportError("MD5MESH: Invalid weight index"); nuclear@0: nuclear@0: MD5::WeightDesc& desc = meshSrc.mWeights[w]; nuclear@0: if ( desc.mWeight < AI_MD5_WEIGHT_EPSILON && desc.mWeight >= -AI_MD5_WEIGHT_EPSILON) { nuclear@0: continue; nuclear@0: } nuclear@0: nuclear@0: const float fNewWeight = desc.mWeight / fSum; nuclear@0: nuclear@0: // transform the local position into worldspace nuclear@0: MD5::BoneDesc& boneSrc = meshParser.mJoints[desc.mBone]; nuclear@0: const aiVector3D v = boneSrc.mRotationQuatConverted.Rotate (desc.vOffsetPosition); nuclear@0: nuclear@0: // use the original weight to compute the vertex position nuclear@0: // (some MD5s seem to depend on the invalid weight values ...) nuclear@0: *pv += ((boneSrc.mPositionXYZ+v)* desc.mWeight); nuclear@0: nuclear@0: aiBone* bone = mesh->mBones[boneSrc.mMap]; nuclear@0: *bone->mWeights++ = aiVertexWeight((unsigned int)(pv-mesh->mVertices),fNewWeight); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // undo our nice offset tricks ... nuclear@0: for (unsigned int p = 0; p < mesh->mNumBones;++p) { nuclear@0: mesh->mBones[p]->mWeights -= mesh->mBones[p]->mNumWeights; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: delete[] piCount; nuclear@0: nuclear@0: // now setup all faces - we can directly copy the list nuclear@0: // (however, take care that the aiFace destructor doesn't delete the mIndices array) nuclear@0: mesh->mNumFaces = (unsigned int)meshSrc.mFaces.size(); nuclear@0: mesh->mFaces = new aiFace[mesh->mNumFaces]; nuclear@0: for (unsigned int c = 0; c < mesh->mNumFaces;++c) { nuclear@0: mesh->mFaces[c].mNumIndices = 3; nuclear@0: mesh->mFaces[c].mIndices = meshSrc.mFaces[c].mIndices; nuclear@0: meshSrc.mFaces[c].mIndices = NULL; nuclear@0: } nuclear@0: nuclear@0: // generate a material for the mesh nuclear@0: aiMaterial* mat = new aiMaterial(); nuclear@0: pScene->mMaterials[n] = mat; nuclear@0: nuclear@0: // insert the typical doom3 textures: nuclear@0: // nnn_local.tga - normal map nuclear@0: // nnn_h.tga - height map nuclear@0: // nnn_s.tga - specular map nuclear@0: // nnn_d.tga - diffuse map nuclear@0: if (meshSrc.mShader.length && !strchr(meshSrc.mShader.data,'.')) { nuclear@0: nuclear@0: aiString temp(meshSrc.mShader); nuclear@0: temp.Append("_local.tga"); nuclear@0: mat->AddProperty(&temp,AI_MATKEY_TEXTURE_NORMALS(0)); nuclear@0: nuclear@0: temp = aiString(meshSrc.mShader); nuclear@0: temp.Append("_s.tga"); nuclear@0: mat->AddProperty(&temp,AI_MATKEY_TEXTURE_SPECULAR(0)); nuclear@0: nuclear@0: temp = aiString(meshSrc.mShader); nuclear@0: temp.Append("_d.tga"); nuclear@0: mat->AddProperty(&temp,AI_MATKEY_TEXTURE_DIFFUSE(0)); nuclear@0: nuclear@0: temp = aiString(meshSrc.mShader); nuclear@0: temp.Append("_h.tga"); nuclear@0: mat->AddProperty(&temp,AI_MATKEY_TEXTURE_HEIGHT(0)); nuclear@0: nuclear@0: // set this also as material name nuclear@0: mat->AddProperty(&meshSrc.mShader,AI_MATKEY_NAME); nuclear@0: } nuclear@0: else mat->AddProperty(&meshSrc.mShader,AI_MATKEY_TEXTURE_DIFFUSE(0)); nuclear@0: mesh->mMaterialIndex = n++; nuclear@0: } nuclear@0: #endif nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Load an MD5ANIM file nuclear@0: void MD5Importer::LoadMD5AnimFile () nuclear@0: { nuclear@0: std::string pFile = mFile + "md5anim"; nuclear@0: boost::scoped_ptr file( pIOHandler->Open( pFile, "rb")); nuclear@0: nuclear@0: // Check whether we can read from the file nuclear@0: if( !file.get() || !file->FileSize()) { nuclear@0: DefaultLogger::get()->warn("Failed to read MD5ANIM file: " + pFile); nuclear@0: return; nuclear@0: } nuclear@0: LoadFileIntoMemory(file.get()); nuclear@0: nuclear@0: // parse the basic file structure nuclear@0: MD5::MD5Parser parser(mBuffer,fileSize); nuclear@0: nuclear@0: // load the animation information from the parse tree nuclear@0: MD5::MD5AnimParser animParser(parser.mSections); nuclear@0: nuclear@0: // generate and fill the output animation nuclear@0: if (animParser.mAnimatedBones.empty() || animParser.mFrames.empty() || nuclear@0: animParser.mBaseFrames.size() != animParser.mAnimatedBones.size()) { nuclear@0: nuclear@0: DefaultLogger::get()->error("MD5ANIM: No frames or animated bones loaded"); nuclear@0: } nuclear@0: else { nuclear@0: bHadMD5Anim = true; nuclear@0: nuclear@0: pScene->mAnimations = new aiAnimation*[pScene->mNumAnimations = 1]; nuclear@0: aiAnimation* anim = pScene->mAnimations[0] = new aiAnimation(); nuclear@0: anim->mNumChannels = (unsigned int)animParser.mAnimatedBones.size(); nuclear@0: anim->mChannels = new aiNodeAnim*[anim->mNumChannels]; nuclear@0: for (unsigned int i = 0; i < anim->mNumChannels;++i) { nuclear@0: aiNodeAnim* node = anim->mChannels[i] = new aiNodeAnim(); nuclear@0: node->mNodeName = aiString( animParser.mAnimatedBones[i].mName ); nuclear@0: nuclear@0: // allocate storage for the keyframes nuclear@0: node->mPositionKeys = new aiVectorKey[animParser.mFrames.size()]; nuclear@0: node->mRotationKeys = new aiQuatKey[animParser.mFrames.size()]; nuclear@0: } nuclear@0: nuclear@0: // 1 tick == 1 frame nuclear@0: anim->mTicksPerSecond = animParser.fFrameRate; nuclear@0: nuclear@0: for (FrameList::const_iterator iter = animParser.mFrames.begin(), iterEnd = animParser.mFrames.end();iter != iterEnd;++iter){ nuclear@0: double dTime = (double)(*iter).iIndex; nuclear@0: aiNodeAnim** pcAnimNode = anim->mChannels; nuclear@0: if (!(*iter).mValues.empty() || iter == animParser.mFrames.begin()) /* be sure we have at least one frame */ nuclear@0: { nuclear@0: // now process all values in there ... read all joints nuclear@0: MD5::BaseFrameDesc* pcBaseFrame = &animParser.mBaseFrames[0]; nuclear@0: for (AnimBoneList::const_iterator iter2 = animParser.mAnimatedBones.begin(); iter2 != animParser.mAnimatedBones.end();++iter2, nuclear@0: ++pcAnimNode,++pcBaseFrame) nuclear@0: { nuclear@0: if((*iter2).iFirstKeyIndex >= (*iter).mValues.size()) { nuclear@0: nuclear@0: // Allow for empty frames nuclear@0: if ((*iter2).iFlags != 0) { nuclear@0: throw DeadlyImportError("MD5: Keyframe index is out of range"); nuclear@0: nuclear@0: } nuclear@0: continue; nuclear@0: } nuclear@0: const float* fpCur = &(*iter).mValues[(*iter2).iFirstKeyIndex]; nuclear@0: aiNodeAnim* pcCurAnimBone = *pcAnimNode; nuclear@0: nuclear@0: aiVectorKey* vKey = &pcCurAnimBone->mPositionKeys[pcCurAnimBone->mNumPositionKeys++]; nuclear@0: aiQuatKey* qKey = &pcCurAnimBone->mRotationKeys [pcCurAnimBone->mNumRotationKeys++]; nuclear@0: aiVector3D vTemp; nuclear@0: nuclear@0: // translational component nuclear@0: for (unsigned int i = 0; i < 3; ++i) { nuclear@0: if ((*iter2).iFlags & (1u << i)) { nuclear@0: vKey->mValue[i] = *fpCur++; nuclear@0: } nuclear@0: else vKey->mValue[i] = pcBaseFrame->vPositionXYZ[i]; nuclear@0: } nuclear@0: nuclear@0: // orientation component nuclear@0: for (unsigned int i = 0; i < 3; ++i) { nuclear@0: if ((*iter2).iFlags & (8u << i)) { nuclear@0: vTemp[i] = *fpCur++; nuclear@0: } nuclear@0: else vTemp[i] = pcBaseFrame->vRotationQuat[i]; nuclear@0: } nuclear@0: nuclear@0: MD5::ConvertQuaternion(vTemp, qKey->mValue); nuclear@0: qKey->mTime = vKey->mTime = dTime; nuclear@0: nuclear@0: // we need this to get to Assimp quaternion conventions nuclear@0: qKey->mValue.w *= -1.f; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // compute the duration of the animation nuclear@0: anim->mDuration = std::max(dTime,anim->mDuration); nuclear@0: } nuclear@0: nuclear@0: // If we didn't build the hierarchy yet (== we didn't load a MD5MESH), nuclear@0: // construct it now from the data given in the MD5ANIM. nuclear@0: if (!pScene->mRootNode) { nuclear@0: pScene->mRootNode = new aiNode(); nuclear@0: pScene->mRootNode->mName.Set(""); nuclear@0: nuclear@0: AttachChilds_Anim(-1,pScene->mRootNode,animParser.mAnimatedBones,(const aiNodeAnim**)anim->mChannels); nuclear@0: nuclear@0: // Call SkeletonMeshBuilder to construct a mesh to represent the shape nuclear@0: if (pScene->mRootNode->mNumChildren) { nuclear@0: SkeletonMeshBuilder skeleton_maker(pScene,pScene->mRootNode->mChildren[0]); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: // ------------------------------------------------------------------------------------------------ nuclear@0: // Load an MD5CAMERA file nuclear@0: void MD5Importer::LoadMD5CameraFile () nuclear@0: { nuclear@0: std::string pFile = mFile + "md5camera"; nuclear@0: boost::scoped_ptr file( pIOHandler->Open( pFile, "rb")); nuclear@0: nuclear@0: // Check whether we can read from the file nuclear@0: if( !file.get() || !file->FileSize()) { nuclear@0: throw DeadlyImportError("Failed to read MD5CAMERA file: " + pFile); nuclear@0: } nuclear@0: bHadMD5Camera = true; nuclear@0: LoadFileIntoMemory(file.get()); nuclear@0: nuclear@0: // parse the basic file structure nuclear@0: MD5::MD5Parser parser(mBuffer,fileSize); nuclear@0: nuclear@0: // load the camera animation data from the parse tree nuclear@0: MD5::MD5CameraParser cameraParser(parser.mSections); nuclear@0: nuclear@0: if (cameraParser.frames.empty()) { nuclear@0: throw DeadlyImportError("MD5CAMERA: No frames parsed"); nuclear@0: } nuclear@0: nuclear@0: std::vector& cuts = cameraParser.cuts; nuclear@0: std::vector& frames = cameraParser.frames; nuclear@0: nuclear@0: // Construct output graph - a simple root with a dummy child. nuclear@0: // The root node performs the coordinate system conversion nuclear@0: aiNode* root = pScene->mRootNode = new aiNode(""); nuclear@0: root->mChildren = new aiNode*[root->mNumChildren = 1]; nuclear@0: root->mChildren[0] = new aiNode(""); nuclear@0: root->mChildren[0]->mParent = root; nuclear@0: nuclear@0: // ... but with one camera assigned to it nuclear@0: pScene->mCameras = new aiCamera*[pScene->mNumCameras = 1]; nuclear@0: aiCamera* cam = pScene->mCameras[0] = new aiCamera(); nuclear@0: cam->mName = ""; nuclear@0: nuclear@0: // FIXME: Fov is currently set to the first frame's value nuclear@0: cam->mHorizontalFOV = AI_DEG_TO_RAD( frames.front().fFOV ); nuclear@0: nuclear@0: // every cut is written to a separate aiAnimation nuclear@0: if (!cuts.size()) { nuclear@0: cuts.push_back(0); nuclear@0: cuts.push_back(frames.size()-1); nuclear@0: } nuclear@0: else { nuclear@0: cuts.insert(cuts.begin(),0); nuclear@0: nuclear@0: if (cuts.back() < frames.size()-1) nuclear@0: cuts.push_back(frames.size()-1); nuclear@0: } nuclear@0: nuclear@0: pScene->mNumAnimations = cuts.size()-1; nuclear@0: aiAnimation** tmp = pScene->mAnimations = new aiAnimation*[pScene->mNumAnimations]; nuclear@0: for (std::vector::const_iterator it = cuts.begin(); it != cuts.end()-1; ++it) { nuclear@0: nuclear@0: aiAnimation* anim = *tmp++ = new aiAnimation(); nuclear@0: anim->mName.length = ::sprintf(anim->mName.data,"anim%u_from_%u_to_%u",(unsigned int)(it-cuts.begin()),(*it),*(it+1)); nuclear@0: nuclear@0: anim->mTicksPerSecond = cameraParser.fFrameRate; nuclear@0: anim->mChannels = new aiNodeAnim*[anim->mNumChannels = 1]; nuclear@0: aiNodeAnim* nd = anim->mChannels[0] = new aiNodeAnim(); nuclear@0: nd->mNodeName.Set(""); nuclear@0: nuclear@0: nd->mNumPositionKeys = nd->mNumRotationKeys = *(it+1) - (*it); nuclear@0: nd->mPositionKeys = new aiVectorKey[nd->mNumPositionKeys]; nuclear@0: nd->mRotationKeys = new aiQuatKey [nd->mNumRotationKeys]; nuclear@0: for (unsigned int i = 0; i < nd->mNumPositionKeys; ++i) { nuclear@0: nuclear@0: nd->mPositionKeys[i].mValue = frames[*it+i].vPositionXYZ; nuclear@0: MD5::ConvertQuaternion(frames[*it+i].vRotationQuat,nd->mRotationKeys[i].mValue); nuclear@0: nd->mRotationKeys[i].mTime = nd->mPositionKeys[i].mTime = *it+i; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: #endif // !! ASSIMP_BUILD_NO_MD5_IMPORTER