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