From f926e9feb95cd587679124e3b4c0f66155948c6f Mon Sep 17 00:00:00 2001 From: Johan Lind Date: Tue, 5 Sep 2023 19:49:28 +0200 Subject: [PATCH] Animation blending with root motion, threaded skinning process --- engine/cAnimator.cpp | 191 ++++++++++++++++++++++++++++--------------- engine/cAnimator.h | 50 ++++++++--- 2 files changed, 167 insertions(+), 74 deletions(-) diff --git a/engine/cAnimator.cpp b/engine/cAnimator.cpp index 433e1fdf..a10b49ef 100644 --- a/engine/cAnimator.cpp +++ b/engine/cAnimator.cpp @@ -9,21 +9,34 @@ #include "editor/jleEditor.h" +#include #include +JLE_EXTERN_TEMPLATE_CEREAL_CPP(cAnimatorAnimation) JLE_EXTERN_TEMPLATE_CEREAL_CPP(cAnimator) +template +void +cAnimatorAnimation::serialize(Archive &ar) +{ + try { + ar(CEREAL_NVP(currentAnimation), CEREAL_NVP(animationSpeed)); + } catch (std::exception &e) { + LOGE << "Failed loading cAnimator: " << e.what(); + } +} + template void cAnimator::serialize(Archive &ar) { try { - ar(CEREAL_NVP(_currentAnimation), - CEREAL_NVP(_animationSpeed), + ar(CEREAL_NVP(_animations), CEREAL_NVP(_enableRootMotion), - CEREAL_NVP(_rootMotionBone)); + CEREAL_NVP(_rootMotionBone), + CEREAL_NVP(_blendFactor)); } catch (std::exception &e) { - LOGE << "Failed loading cAnimator:" << e.what(); + LOGE << "Failed loading cAnimator: " << e.what(); } } @@ -41,18 +54,24 @@ void cAnimator::update(float dt) { JLE_SCOPE_PROFILE_CPU(cAnimator_Skinning) - dt *= _animationSpeed; - _deltaTime = dt; - if (_currentAnimation) { - _currentTime += _currentAnimation->getTicksPerSec() * dt; - if (_currentTime / _currentAnimation->getDuration() > 1.f) { - _animationLoopedThisFrame = true; - } else { - _animationLoopedThisFrame = false; + std::for_each(std::execution::par, _animations.begin(), _animations.end(), [&](cAnimatorAnimation &animation) { + if (animation.currentAnimation) { + animation.deltaTime = dt * animation.animationSpeed; + animation.currentTime += animation.currentAnimation->getTicksPerSec() * animation.deltaTime; + if (animation.currentTime / animation.currentAnimation->getDuration() > 1.f) { + animation.animationLoopedThisFrame = true; + } else { + animation.animationLoopedThisFrame = false; + } + animation.currentTime = fmod(animation.currentTime, animation.currentAnimation->getDuration()); + + calculateBoneTransform(animation.currentAnimation->getRootNode(), glm::identity(), animation); } - _currentTime = fmod(_currentTime, _currentAnimation->getDuration()); - calculateBoneTransform(_currentAnimation->getRootNode(), glm::identity()); - } + }); + + blendAnimations(); + + applyRootMotion(); } void @@ -71,53 +90,30 @@ cAnimator::registerLua(sol::state &lua, sol::table &table) } void -cAnimator::calculateBoneTransform(const jleAnimationNode &node, const glm::mat4 &parentTransform) +cAnimator::calculateBoneTransform(const jleAnimationNode &node, + const glm::mat4 &parentTransform, + cAnimatorAnimation &animation) { const std::string &nodeName = node.name; glm::mat4 nodeTransform = node.transformation; - jleAnimationBone *bone = _currentAnimation->findBone(nodeName); + jleAnimationBone *bone = animation.currentAnimation->findBone(nodeName); if (bone) { - bone->update(_currentTime); + bone->update(animation.currentTime); nodeTransform = bone->getLocalTransform(); if (!gEngine->isGameKilled()) { - if (bone->getName() == _rootMotionBone && _enableRootMotion) { auto newPos = glm::vec3(bone->getLocalTransform()[3]); - auto rootMotionDiff = _lastFrameRootPosition - newPos; - if (!_animationLoopedThisFrame) { - glm::mat4 matrix = glm::mat4(glm::vec4(getTransform().getRight(), 0.0f), - glm::vec4(getTransform().getUp(), 0.0f), - glm::vec4(getTransform().getForward(), 0.0f), - glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); - auto q = glm::quat(matrix); - - glm::vec3 rotatedVec = -(q * rootMotionDiff); - - // gEngine->renderGraph().sendLine(getTransform().getWorldPosition(), - // getTransform().getWorldPosition() + - // glm::normalize(rotatedVec) * 5.f); - - glm::vec3 scale; - auto modelMatrix = getTransform().getWorldMatrix(); - scale.x = glm::length(glm::vec3(modelMatrix[0])); - scale.y = glm::length(glm::vec3(modelMatrix[1])); - scale.z = glm::length(glm::vec3(modelMatrix[2])); - - getTransform().addLocalTranslation(rotatedVec * scale); - for (auto &child : object()->childObjects()) { - child->getTransform().addLocalTranslation(rootMotionDiff); - } - _lastFrameRootPosition = newPos; + auto rootMotionDiff = animation.lastFrameRootPosition - newPos; + animation.lastFrameRootPosition = newPos; + if (!animation.animationLoopedThisFrame) { + animation.thisFrameRootMotionTranslation = rootMotionDiff; } else { - _lastFrameRootPosition = glm::vec3{0.f, _lastFrameRootPosition.y, 0.f}; - for (auto &child : object()->childObjects()) { - child->getTransform().setLocalPosition(glm::vec3{0.f}); - } + animation.thisFrameRootMotionTranslation = newPos; } } } @@ -125,25 +121,20 @@ cAnimator::calculateBoneTransform(const jleAnimationNode &node, const glm::mat4 glm::mat4 globalTransformation = parentTransform * nodeTransform; - auto boneMapping = _currentAnimation->getBoneMapping(); + auto boneMapping = animation.currentAnimation->getBoneMapping(); auto meshBone = boneMapping.find(nodeName); if (meshBone != boneMapping.end()) { int index = meshBone->second.index; const glm::mat4 &offset = meshBone->second.offset; - _animationMatrices->matrices[index] = globalTransformation * offset; + + animation.animationMatrices.matrices[index] = globalTransformation * offset; } for (int i = 0; i < node.childNodes.size(); ++i) { - calculateBoneTransform(node.childNodes[i], globalTransformation); + calculateBoneTransform(node.childNodes[i], globalTransformation, animation); } } -void -cAnimator::setAnimation(const jleResourceRef &animation) -{ - _currentAnimation = animation; -} - const std::shared_ptr & cAnimator::animationMatrices() { @@ -154,16 +145,88 @@ void cAnimator::editorInspectorImGuiRender() { #ifdef BUILD_EDITOR - if (_currentAnimation) { - if (gEditor->isGameKilled()) { - ImGui::Checkbox("Preview Animation", &_editorPreviewAnimation); - if (ImGui::IsItemEdited()) { - if (!_editorPreviewAnimation) { - _animationMatrices = std::make_shared(); + int p = 0; + ImGui::Separator(); + for (auto &animation : _animations) { + ImGui::PushID(p++); + ImGui::Text("%s", animation.currentAnimation.path.getVirtualPath().c_str()); + if (animation.currentAnimation) { + if (gEditor->isGameKilled()) { + if (ImGui::IsItemEdited()) { + if (!_editorPreviewAnimation) { + _animationMatrices = std::make_shared(); + } } } + ImGui::SliderFloat("Current Time", &animation.currentTime, 0.f, animation.currentAnimation->getDuration()); } - ImGui::SliderFloat("Current Time", &_currentTime, 0.f, _currentAnimation->getDuration()); + ImGui::PopID(); } + + ImGui::Checkbox("Preview Animation", &_editorPreviewAnimation); + #endif } + +void +cAnimator::blendAnimations() +{ + if (_animations.size() == 1) { + if (_animations[0].currentAnimation) { + *_animationMatrices = _animations[0].animationMatrices; + _animations[0].blendFactorStrength = 1.f; + } + } else if (_animations.size() == 2) { + for (int i = 0; i < 100; ++i) { + glm::mat4 &matrix1 = _animations[0].animationMatrices.matrices[i]; + glm::mat4 &matrix2 = _animations[1].animationMatrices.matrices[i]; + _animationMatrices->matrices[i] = matrix1 * (1.0f - _blendFactor) + matrix2 * _blendFactor; + } + //_animations[0].thisFrameRootMotionTranslation *= 1.0f - _blendFactor; + //_animations[1].thisFrameRootMotionTranslation *= _blendFactor; + _animations[0].blendFactorStrength = 1.0f - _blendFactor; + _animations[1].blendFactorStrength = _blendFactor; + } +} + +void +cAnimator::applyRootMotion() +{ + + glm::mat4 matrix = glm::mat4(glm::vec4(getTransform().getRight(), 0.0f), + glm::vec4(getTransform().getUp(), 0.0f), + glm::vec4(getTransform().getForward(), 0.0f), + glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); + + auto quat = glm::quat(matrix); + + glm::vec3 scale; + auto modelMatrix = getTransform().getWorldMatrix(); + scale.x = glm::length(glm::vec3(modelMatrix[0])); + scale.y = glm::length(glm::vec3(modelMatrix[1])); + scale.z = glm::length(glm::vec3(modelMatrix[2])); + + for (auto &animation : _animations) { + if (!animation.animationLoopedThisFrame) { + + glm::vec3 rotatedVec = -(quat * animation.thisFrameRootMotionTranslation * animation.blendFactorStrength); + animation.totalRootMotionTranslation += rotatedVec; + + gEngine->renderGraph().sendLine(getTransform().getWorldPosition(), + getTransform().getWorldPosition() + rotatedVec); + + getTransform().addLocalTranslation(rotatedVec * scale); + } else { + animation.totalRootMotionTranslation = glm::vec3{0.f}; + } + } + + glm::vec3 addedRootMotionForChildren{0.f}; + for (const auto &animation : _animations) { + addedRootMotionForChildren += animation.totalRootMotionTranslation; + } + + for (auto &child : object()->childObjects()) { + child->getTransform().setLocalPosition(addedRootMotionForChildren); + } +} diff --git a/engine/cAnimator.h b/engine/cAnimator.h index 29b64dbd..2212d3f1 100644 --- a/engine/cAnimator.h +++ b/engine/cAnimator.h @@ -10,7 +10,35 @@ #include -class cAnimator : public jleComponent +struct cAnimatorAnimation +{ + jleResourceRef currentAnimation{}; + jleAnimationFinalMatrices animationMatrices{}; + + float currentTime{}; + float deltaTime{}; + float animationSpeed{1.f}; + bool animationLoopedThisFrame{false}; + + float blendFactorStrength = 1.f; + + glm::vec3 lastFrameRootPosition; + + // Local bone root motion translation + glm::vec3 thisFrameRootMotionTranslation; + + // Local model root motion translation, additive + glm::vec3 totalRootMotionTranslation; + + template + void serialize(Archive &ar); + +}; + +JLE_EXTERN_TEMPLATE_CEREAL_H(cAnimatorAnimation) + + +class cAnimator : public jleComponent, public std::enable_shared_from_this { JLE_REGISTER_COMPONENT_TYPE(cAnimator) public: @@ -27,25 +55,27 @@ class cAnimator : public jleComponent void registerLua(sol::state& lua, sol::table &table) override; - void setAnimation(const jleResourceRef& animation); - - void calculateBoneTransform(const jleAnimationNode& node, const glm::mat4& parentTransform); + void calculateBoneTransform(const jleAnimationNode& node, const glm::mat4& parentTransform, cAnimatorAnimation& animation); const std::shared_ptr& animationMatrices(); void editorInspectorImGuiRender() override; private: - jleResourceRef _currentAnimation; + + void blendAnimations(); + + void applyRootMotion(); + + std::vector _animations; + std::shared_ptr _animationMatrices{}; - float _currentTime{}; - float _deltaTime{}; - float _animationSpeed{1.f}; + bool _enableRootMotion{false}; std::string _rootMotionBone{"RootBone"}; - bool _animationLoopedThisFrame{false}; - glm::vec3 _lastFrameRootPosition{}; + float _blendFactor{1.f}; + #ifdef BUILD_EDITOR bool _editorPreviewAnimation{false};