Skip to content

Commit

Permalink
Implement blending support for Motion Matching Animation Nodes (#66)
Browse files Browse the repository at this point in the history
* Implement blending support for motion matching animation nodes

* Use damped springs for animation transitions

* Add blending toggle
  • Loading branch information
GuilhermeGSousa committed Dec 17, 2024
1 parent a1e54dc commit ee4ae54
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 31 deletions.
126 changes: 97 additions & 29 deletions src/mm_animation_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#include "mm_animation_node.h"

#include "math/spring.hpp"
#include "mm_query.h"

#ifdef TOOLS_ENABLED
Expand Down Expand Up @@ -59,8 +60,12 @@ AnimationNode::NodeTimeInfo MMAnimationNode::_process(const AnimationMixer::Play
return cur_nti;
}

_current_animation_info.playback_info = p_playback_info;
_current_animation_info.playback_info.weight = 1.0;
_current_animation_info.playback_info.time = p_playback_info.time;
_current_animation_info.playback_info.delta = p_playback_info.delta;
_current_animation_info.playback_info.start = p_playback_info.start;
_current_animation_info.playback_info.end = p_playback_info.end;
_current_animation_info.playback_info.looped_flag = p_playback_info.looped_flag;
_current_animation_info.playback_info.is_external_seeking = p_playback_info.is_external_seeking;

const bool is_about_to_end = false; // TODO: Implement this

Expand Down Expand Up @@ -104,19 +109,63 @@ AnimationNode::NodeTimeInfo MMAnimationNode::_process(const AnimationMixer::Play
}

void MMAnimationNode::_start_transition(const StringName p_animation, float p_time) {
_current_animation_info.name = p_animation;
_current_animation_info.playback_info.time = p_time;
Ref<Animation> anim = process_state->tree->get_animation(p_animation);
ERR_FAIL_COND_MSG(anim.is_null(), vformat("Animation not found: %s", p_animation));

if (!_current_animation_info.name.is_empty() && blending_enabled) {
_prev_animation_queue.push_front(_current_animation_info);
}

_current_animation_info.name = p_animation;
_current_animation_info.length = anim->get_length();
_current_animation_info.playback_info.time = p_time;
_current_animation_info.playback_info.weight = blending_enabled ? 0.f : 1.f;
}

AnimationNode::NodeTimeInfo MMAnimationNode::_update_current_animation(bool p_test_only) {
const bool will_end = Animation::is_greater_or_equal_approx(
_current_animation_info.playback_info.time + _current_animation_info.playback_info.delta,
_current_animation_info.length);

Spring::_simple_spring_damper_exact(
_current_animation_info.playback_info.weight,
_current_animation_info.blend_spring_speed,
1.f,
transition_halflife,
_current_animation_info.playback_info.delta);

int pop_count = 0;
for (AnimationInfo& prev_info : _prev_animation_queue) {
Spring::_simple_spring_damper_exact(
prev_info.playback_info.weight,
prev_info.blend_spring_speed,
0.f,
transition_halflife,
_current_animation_info.playback_info.delta);
if (prev_info.playback_info.weight <= SMALL_NUMBER) {
pop_count++;
}
}

for (int i = 0; i < pop_count; i++) {
_prev_animation_queue.pop_back();
}

// Normalized blend weights in the queue
const float inv_blend = 1.f - _current_animation_info.playback_info.weight;
float prev_blend_total = 0.f;
for (AnimationInfo& prev_info : _prev_animation_queue) {
prev_blend_total += prev_info.playback_info.weight;
}

for (AnimationInfo& prev_info : _prev_animation_queue) {
prev_info.playback_info.weight *= inv_blend / prev_blend_total;
}

if (!p_test_only) {
for (AnimationInfo& prev_info : _prev_animation_queue) {
blend_animation(prev_info.name, prev_info.playback_info);
}
blend_animation(_current_animation_info.name, _current_animation_info.playback_info);
}

Expand Down Expand Up @@ -165,44 +214,63 @@ String MMAnimationNode::get_caption() const {
}

void MMAnimationNode::_validate_property(PropertyInfo& p_property) const {
#ifdef TOOLS_ENABLED
if (p_property.name != "library") {
return;
if (p_property.name == "transition_halflife") {
if (!blending_enabled) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}

if (!AnimationTreeEditor::get_singleton()) {
return;
}
AnimationTree* tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
if (!tree) {
return;
}
String animations;
List<StringName> library_names;
tree->get_animation_library_list(&library_names);
for (const StringName& lib_name : library_names) {
Ref<MMAnimationLibrary> lib = tree->get_animation_library(lib_name);
if (lib.is_null()) {
continue;
#ifdef TOOLS_ENABLED
if (p_property.name == "library") {
if (!AnimationTreeEditor::get_singleton()) {
return;
}
if (!animations.is_empty()) {
animations += ",";
AnimationTree* tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
if (!tree) {
return;
}
animations += lib_name;
}
if (animations.is_empty()) {
return;
String animations;
List<StringName> library_names;
tree->get_animation_library_list(&library_names);
for (const StringName& lib_name : library_names) {
Ref<MMAnimationLibrary> lib = tree->get_animation_library(lib_name);
if (lib.is_null()) {
continue;
}
if (!animations.is_empty()) {
animations += ",";
}
animations += lib_name;
}
if (animations.is_empty()) {
return;
}
p_property.hint = PROPERTY_HINT_ENUM;
p_property.hint_string = animations;
}
p_property.hint = PROPERTY_HINT_ENUM;
p_property.hint_string = animations;
#endif

AnimationRootNode::_validate_property(p_property);
}

void MMAnimationNode::_bind_methods() {
BINDER_PROPERTY_PARAMS(MMAnimationNode, Variant::STRING_NAME, library);
BINDER_PROPERTY_PARAMS(MMAnimationNode, Variant::FLOAT, query_frequency);
ClassDB::bind_method(D_METHOD("get_blending_enabled"), &MMAnimationNode::get_blending_enabled);
ClassDB::bind_method(D_METHOD("set_blending_enabled", "value"), &MMAnimationNode::set_blending_enabled);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "blending_enabled"), "set_blending_enabled", "get_blending_enabled");
BINDER_PROPERTY_PARAMS(MMAnimationNode, Variant::FLOAT, transition_halflife);
}

bool MMAnimationNode::has_filter() const {
return true;
}

bool MMAnimationNode::get_blending_enabled() const {
return blending_enabled;
}

void MMAnimationNode::set_blending_enabled(bool value) {
blending_enabled = value;
notify_property_list_changed();
}
14 changes: 12 additions & 2 deletions src/mm_animation_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@
#ifndef MM_ANIMATION_NODE_H
#define MM_ANIMATION_NODE_H

#include "common.h"
#include "mm_animation_library.h"

#include "scene/animation/animation_blend_tree.h"
#include "scene/animation/animation_tree.h"

#include "common.h"
#include "mm_animation_library.h"
#include <queue>

class MMAnimationNode : public AnimationRootNode {
GDCLASS(MMAnimationNode, AnimationRootNode);
Expand All @@ -46,6 +48,12 @@ class MMAnimationNode : public AnimationRootNode {

GETSET(StringName, library);
GETSET(float, query_frequency, 2.0f)
GETSET(float, transition_halflife, 0.1f)

bool blending_enabled{true};
bool get_blending_enabled() const;

void set_blending_enabled(bool value);

virtual AnimationNode::NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual void get_parameter_list(List<PropertyInfo>* r_list) const override;
Expand All @@ -66,8 +74,10 @@ class MMAnimationNode : public AnimationRootNode {
StringName name;
double length;
AnimationMixer::PlaybackInfo playback_info;
float blend_spring_speed;
};

std::deque<AnimationInfo> _prev_animation_queue;
AnimationInfo _current_animation_info;

void _start_transition(const StringName p_animation, float p_time);
Expand Down

0 comments on commit ee4ae54

Please sign in to comment.