Skip to content

Commit

Permalink
Animation blending with root motion, threaded skinning process
Browse files Browse the repository at this point in the history
  • Loading branch information
Mormert committed Sep 5, 2023
1 parent 2ed76f9 commit f926e9f
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 74 deletions.
191 changes: 127 additions & 64 deletions engine/cAnimator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,34 @@

#include "editor/jleEditor.h"

#include <execution>
#include <glm/gtc/quaternion.hpp>

JLE_EXTERN_TEMPLATE_CEREAL_CPP(cAnimatorAnimation)
JLE_EXTERN_TEMPLATE_CEREAL_CPP(cAnimator)

template <class Archive>
void
cAnimatorAnimation::serialize(Archive &ar)
{
try {
ar(CEREAL_NVP(currentAnimation), CEREAL_NVP(animationSpeed));
} catch (std::exception &e) {
LOGE << "Failed loading cAnimator: " << e.what();
}
}

template <class Archive>
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();
}
}

Expand All @@ -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<glm::mat4>(), animation);
}
_currentTime = fmod(_currentTime, _currentAnimation->getDuration());
calculateBoneTransform(_currentAnimation->getRootNode(), glm::identity<glm::mat4>());
}
});

blendAnimations();

applyRootMotion();
}

void
Expand All @@ -71,79 +90,51 @@ 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;
}
}
}
}

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<jleAnimation> &animation)
{
_currentAnimation = animation;
}

const std::shared_ptr<jleAnimationFinalMatrices> &
cAnimator::animationMatrices()
{
Expand All @@ -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<jleAnimationFinalMatrices>();
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<jleAnimationFinalMatrices>();
}
}
}
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);
}
}
50 changes: 40 additions & 10 deletions engine/cAnimator.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,35 @@

#include <glm/glm.hpp>

class cAnimator : public jleComponent
struct cAnimatorAnimation
{
jleResourceRef<jleAnimation> 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 <class Archive>
void serialize(Archive &ar);

};

JLE_EXTERN_TEMPLATE_CEREAL_H(cAnimatorAnimation)


class cAnimator : public jleComponent, public std::enable_shared_from_this<cAnimator>
{
JLE_REGISTER_COMPONENT_TYPE(cAnimator)
public:
Expand All @@ -27,25 +55,27 @@ class cAnimator : public jleComponent

void registerLua(sol::state& lua, sol::table &table) override;

void setAnimation(const jleResourceRef<jleAnimation>& 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<jleAnimationFinalMatrices>& animationMatrices();

void editorInspectorImGuiRender() override;

private:
jleResourceRef<jleAnimation> _currentAnimation;

void blendAnimations();

void applyRootMotion();

std::vector<cAnimatorAnimation> _animations;

std::shared_ptr<jleAnimationFinalMatrices> _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};
Expand Down

0 comments on commit f926e9f

Please sign in to comment.