From e0fee3d87b78dc90c42c1099e7793aef337fa5cc Mon Sep 17 00:00:00 2001 From: Victor Cebollada Date: Wed, 25 Sep 2019 09:57:53 +0100 Subject: [PATCH] gltf2.0 importer - Support for mesh morph animations added. Signed-off-by: Victor Cebollada --- code/Common/SceneCombiner.cpp | 21 +++ code/PostProcessing/ValidateDataStructure.cpp | 61 ++++++++- code/PostProcessing/ValidateDataStructure.h | 8 ++ code/glTF2/glTF2Importer.cpp | 129 +++++++++++++++++- include/assimp/SceneCombiner.h | 2 + 5 files changed, 212 insertions(+), 9 deletions(-) diff --git a/code/Common/SceneCombiner.cpp b/code/Common/SceneCombiner.cpp index 4e6bc5b47..f7b13cc95 100644 --- a/code/Common/SceneCombiner.cpp +++ b/code/Common/SceneCombiner.cpp @@ -1196,6 +1196,7 @@ void SceneCombiner::Copy( aiAnimation** _dest, const aiAnimation* src ) { // and reallocate all arrays CopyPtrArray( dest->mChannels, src->mChannels, dest->mNumChannels ); + CopyPtrArray( dest->mMorphMeshChannels, src->mMorphMeshChannels, dest->mNumMorphMeshChannels ); } // ------------------------------------------------------------------------------------------------ @@ -1215,6 +1216,26 @@ void SceneCombiner::Copy(aiNodeAnim** _dest, const aiNodeAnim* src) { GetArrayCopy( dest->mRotationKeys, dest->mNumRotationKeys ); } +void SceneCombiner::Copy(aiMeshMorphAnim** _dest, const aiMeshMorphAnim* src) { + if ( nullptr == _dest || nullptr == src ) { + return; + } + + aiMeshMorphAnim* dest = *_dest = new aiMeshMorphAnim(); + + // get a flat copy + ::memcpy(dest,src,sizeof(aiMeshMorphAnim)); + + // and reallocate all arrays + GetArrayCopy( dest->mKeys, dest->mNumKeys ); + for (ai_uint i = 0; i < dest->mNumKeys;++i) { + dest->mKeys[i].mValues = new unsigned int[dest->mKeys[i].mNumValuesAndWeights]; + dest->mKeys[i].mWeights = new double[dest->mKeys[i].mNumValuesAndWeights]; + ::memcpy(dest->mKeys[i].mValues, src->mKeys[i].mValues, dest->mKeys[i].mNumValuesAndWeights * sizeof(unsigned int)); + ::memcpy(dest->mKeys[i].mWeights, src->mKeys[i].mWeights, dest->mKeys[i].mNumValuesAndWeights * sizeof(double)); + } +} + // ------------------------------------------------------------------------------------------------ void SceneCombiner::Copy( aiCamera** _dest,const aiCamera* src) { if ( nullptr == _dest || nullptr == src ) { diff --git a/code/PostProcessing/ValidateDataStructure.cpp b/code/PostProcessing/ValidateDataStructure.cpp index 501f7a9b2..75d1b6ef7 100644 --- a/code/PostProcessing/ValidateDataStructure.cpp +++ b/code/PostProcessing/ValidateDataStructure.cpp @@ -538,13 +538,17 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation) { Validate(&pAnimation->mName); - // validate all materials - if (pAnimation->mNumChannels) + // validate all animations + if (pAnimation->mNumChannels || pAnimation->mNumMorphMeshChannels) { - if (!pAnimation->mChannels) { + if (!pAnimation->mChannels && pAnimation->mNumChannels) { ReportError("aiAnimation::mChannels is NULL (aiAnimation::mNumChannels is %i)", pAnimation->mNumChannels); } + if (!pAnimation->mMorphMeshChannels && pAnimation->mNumMorphMeshChannels) { + ReportError("aiAnimation::mMorphMeshChannels is NULL (aiAnimation::mNumMorphMeshChannels is %i)", + pAnimation->mNumMorphMeshChannels); + } for (unsigned int i = 0; i < pAnimation->mNumChannels;++i) { if (!pAnimation->mChannels[i]) @@ -554,6 +558,15 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation) } Validate(pAnimation, pAnimation->mChannels[i]); } + for (unsigned int i = 0; i < pAnimation->mNumMorphMeshChannels;++i) + { + if (!pAnimation->mMorphMeshChannels[i]) + { + ReportError("aiAnimation::mMorphMeshChannels[%i] is NULL (aiAnimation::mNumMorphMeshChannels is %i)", + i, pAnimation->mNumMorphMeshChannels); + } + Validate(pAnimation, pAnimation->mMorphMeshChannels[i]); + } } else { ReportError("aiAnimation::mNumChannels is 0. At least one node animation channel must be there."); @@ -903,6 +916,48 @@ void ValidateDSProcess::Validate( const aiAnimation* pAnimation, } } +void ValidateDSProcess::Validate( const aiAnimation* pAnimation, + const aiMeshMorphAnim* pMeshMorphAnim) +{ + Validate(&pMeshMorphAnim->mName); + + if (!pMeshMorphAnim->mNumKeys) { + ReportError("Empty mesh morph animation channel"); + } + + // otherwise check whether one of the keys exceeds the total duration of the animation + if (pMeshMorphAnim->mNumKeys) + { + if (!pMeshMorphAnim->mKeys) + { + ReportError("aiMeshMorphAnim::mKeys is NULL (aiMeshMorphAnim::mNumKeys is %i)", + pMeshMorphAnim->mNumKeys); + } + double dLast = -10e10; + for (unsigned int i = 0; i < pMeshMorphAnim->mNumKeys;++i) + { + // ScenePreprocessor will compute the duration if still the default value + // (Aramis) Add small epsilon, comparison tended to fail if max_time == duration, + // seems to be due the compilers register usage/width. + if (pAnimation->mDuration > 0. && pMeshMorphAnim->mKeys[i].mTime > pAnimation->mDuration+0.001) + { + ReportError("aiMeshMorphAnim::mKeys[%i].mTime (%.5f) is larger " + "than aiAnimation::mDuration (which is %.5f)",i, + (float)pMeshMorphAnim->mKeys[i].mTime, + (float)pAnimation->mDuration); + } + if (i && pMeshMorphAnim->mKeys[i].mTime <= dLast) + { + ReportWarning("aiMeshMorphAnim::mKeys[%i].mTime (%.5f) is smaller " + "than aiMeshMorphAnim::mKeys[%i] (which is %.5f)",i, + (float)pMeshMorphAnim->mKeys[i].mTime, + i-1, (float)dLast); + } + dLast = pMeshMorphAnim->mKeys[i].mTime; + } + } +} + // ------------------------------------------------------------------------------------------------ void ValidateDSProcess::Validate( const aiNode* pNode) { diff --git a/code/PostProcessing/ValidateDataStructure.h b/code/PostProcessing/ValidateDataStructure.h index 0b891ef41..7b309c925 100644 --- a/code/PostProcessing/ValidateDataStructure.h +++ b/code/PostProcessing/ValidateDataStructure.h @@ -55,6 +55,7 @@ struct aiBone; struct aiMesh; struct aiAnimation; struct aiNodeAnim; +struct aiMeshMorphAnim; struct aiTexture; struct aiMaterial; struct aiNode; @@ -150,6 +151,13 @@ protected: void Validate( const aiAnimation* pAnimation, const aiNodeAnim* pBoneAnim); + /** Validates a mesh morph animation channel. + * @param pAnimation Input animation. + * @param pMeshMorphAnim Mesh morph animation channel. + * */ + void Validate( const aiAnimation* pAnimation, + const aiMeshMorphAnim* pMeshMorphAnim); + // ------------------------------------------------------------------- /** Validates a node and all of its subnodes * @param Node Input node*/ diff --git a/code/glTF2/glTF2Importer.cpp b/code/glTF2/glTF2Importer.cpp index 82c6cbfa8..fbffd900a 100644 --- a/code/glTF2/glTF2Importer.cpp +++ b/code/glTF2/glTF2Importer.cpp @@ -1005,13 +1005,15 @@ struct AnimationSamplers { AnimationSamplers() : translation(nullptr) , rotation(nullptr) - , scale(nullptr) { + , scale(nullptr) + , weight(nullptr) { // empty } Animation::Sampler* translation; Animation::Sampler* rotation; Animation::Sampler* scale; + Animation::Sampler* weight; }; aiNodeAnim* CreateNodeAnim(glTF2::Asset& r, Node& node, AnimationSamplers& samplers) @@ -1094,6 +1096,43 @@ aiNodeAnim* CreateNodeAnim(glTF2::Asset& r, Node& node, AnimationSamplers& sampl return anim; } +aiMeshMorphAnim* CreateMeshMorphAnim(glTF2::Asset& r, Node& node, AnimationSamplers& samplers) +{ + aiMeshMorphAnim* anim = new aiMeshMorphAnim(); + anim->mName = GetNodeName(node); + + static const float kMillisecondsFromSeconds = 1000.f; + + if (nullptr != samplers.weight) { + float* times = nullptr; + samplers.weight->input->ExtractData(times); + float* values = nullptr; + samplers.weight->output->ExtractData(values); + anim->mNumKeys = static_cast(samplers.weight->input->count); + + const unsigned int numMorphs = samplers.weight->output->count / anim->mNumKeys; + + anim->mKeys = new aiMeshMorphKey[anim->mNumKeys]; + unsigned int k = 0u; + for (unsigned int i = 0u; i < anim->mNumKeys; ++i) { + anim->mKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mKeys[i].mNumValuesAndWeights = numMorphs; + anim->mKeys[i].mValues = new unsigned int[numMorphs]; + anim->mKeys[i].mWeights = new double[numMorphs]; + + for (unsigned int j = 0u; j < numMorphs; ++j, ++k) { + anim->mKeys[i].mValues[j] = j; + anim->mKeys[i].mWeights[j] = ( 0.f > values[k] ) ? 0.f : values[k]; + } + } + + delete[] times; + delete[] values; + } + + return anim; +} + std::unordered_map GatherSamplers(Animation& anim) { std::unordered_map samplers; @@ -1112,6 +1151,8 @@ std::unordered_map GatherSamplers(Animation& an sampler.rotation = &anim.samplers[channel.sampler]; } else if (channel.target.path == AnimationPath_SCALE) { sampler.scale = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_WEIGHTS) { + sampler.weight = &anim.samplers[channel.sampler]; } } @@ -1138,13 +1179,39 @@ void glTF2Importer::ImportAnimations(glTF2::Asset& r) std::unordered_map samplers = GatherSamplers(anim); - ai_anim->mNumChannels = static_cast(samplers.size()); + uint32_t numChannels = 0u; + uint32_t numMorphMeshChannels = 0u; + + for (auto& iter : samplers) { + if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) { + ++numChannels; + } + if (nullptr != iter.second.weight) { + ++numMorphMeshChannels; + } + } + + ai_anim->mNumChannels = numChannels; if (ai_anim->mNumChannels > 0) { ai_anim->mChannels = new aiNodeAnim*[ai_anim->mNumChannels]; int j = 0; for (auto& iter : samplers) { - ai_anim->mChannels[j] = CreateNodeAnim(r, r.nodes[iter.first], iter.second); - ++j; + if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) { + ai_anim->mChannels[j] = CreateNodeAnim(r, r.nodes[iter.first], iter.second); + ++j; + } + } + } + + ai_anim->mNumMorphMeshChannels = numMorphMeshChannels; + if (ai_anim->mNumMorphMeshChannels > 0) { + ai_anim->mMorphMeshChannels = new aiMeshMorphAnim*[ai_anim->mNumMorphMeshChannels]; + int j = 0; + for (auto& iter : samplers) { + if (nullptr != iter.second.weight) { + ai_anim->mMorphMeshChannels[j] = CreateMeshMorphAnim(r, r.nodes[iter.first], iter.second); + ++j; + } } } @@ -1175,8 +1242,58 @@ void glTF2Importer::ImportAnimations(glTF2::Asset& r) maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumScalingKeys); } } - ai_anim->mDuration = maxDuration; - ai_anim->mTicksPerSecond = (maxNumberOfKeys > 0 && maxDuration > 0) ? (maxNumberOfKeys / (maxDuration/1000)) : 30; + + for (unsigned int j = 0; j < ai_anim->mNumMorphMeshChannels; ++j) { + const auto* const chan = ai_anim->mMorphMeshChannels[j]; + + if (0u != chan->mNumKeys) { + const auto& lastKey = chan->mKeys[chan->mNumKeys - 1u]; + if (lastKey.mTime > maxDuration) { + maxDuration = lastKey.mTime; + } + maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumKeys); + } + } + + ai_anim->mDuration = static_cast(maxNumberOfKeys - 1u); /// According the documentation in anim.h the mDuration units are ticks. + ai_anim->mTicksPerSecond = (maxNumberOfKeys > 0 && maxDuration > 0) ? ((maxNumberOfKeys-1u) / (maxDuration / 1000.0)) : 30.0; + + // Set all the times of the keys in ticks. + + const float kMsToTicks = ai_anim->mTicksPerSecond / 1000.f; + + for (unsigned int j = 0; j < ai_anim->mNumChannels; ++j) { + auto chan = ai_anim->mChannels[j]; + if (0u != chan->mNumPositionKeys) { + for (unsigned int k = 0u; k < chan->mNumPositionKeys; ++k) + { + chan->mPositionKeys[k].mTime *= kMsToTicks; + } + } + if (0u != chan->mNumRotationKeys) { + for (unsigned int k = 0u; k < chan->mNumRotationKeys; ++k) + { + chan->mRotationKeys[k].mTime *= kMsToTicks; + } + } + if (0u != chan->mNumScalingKeys) { + for (unsigned int k = 0u; k < chan->mNumScalingKeys; ++k) + { + chan->mScalingKeys[k].mTime *= kMsToTicks; + } + } + } + + for (unsigned int j = 0; j < ai_anim->mNumMorphMeshChannels; ++j) { + const auto* const chan = ai_anim->mMorphMeshChannels[j]; + + if (0u != chan->mNumKeys) { + for (unsigned int k = 0u; k < chan->mNumKeys; ++k) + { + chan->mKeys[k].mTime = static_cast(k); + } + } + } mScene->mAnimations[i] = ai_anim; } diff --git a/include/assimp/SceneCombiner.h b/include/assimp/SceneCombiner.h index f69a25f43..e594f649f 100644 --- a/include/assimp/SceneCombiner.h +++ b/include/assimp/SceneCombiner.h @@ -68,6 +68,7 @@ struct aiMesh; struct aiAnimMesh; struct aiAnimation; struct aiNodeAnim; +struct aiMeshMorphAnim; namespace Assimp { @@ -372,6 +373,7 @@ public: static void Copy (aiBone** dest, const aiBone* src); static void Copy (aiLight** dest, const aiLight* src); static void Copy (aiNodeAnim** dest, const aiNodeAnim* src); + static void Copy (aiMeshMorphAnim** dest, const aiMeshMorphAnim* src); static void Copy (aiMetadata** dest, const aiMetadata* src); // recursive, of course