Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] Additive Animation Fix (and Sub2 node feature) #76310

Open
wants to merge 21 commits into
base: 3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/classes/AnimationNode.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<argument index="3" name="blend" type="float" />
<argument index="4" name="filter" type="int" enum="AnimationNode.FilterAction" default="0" />
<argument index="5" name="optimize" type="bool" default="true" />
<argument index="6" name="use_blend" type="bool" default="true" />
<description>
Blend an input. This is only useful for nodes created for an [AnimationNodeBlendTree]. The [code]time[/code] parameter is a relative delta, unless [code]seek[/code] is [code]true[/code], in which case it is absolute. A filter mode may be optionally passed (see [enum FilterAction] for options).
</description>
Expand All @@ -50,6 +51,7 @@
<argument index="4" name="blend" type="float" />
<argument index="5" name="filter" type="int" enum="AnimationNode.FilterAction" default="0" />
<argument index="6" name="optimize" type="bool" default="true" />
<argument index="7" name="use_blend" type="bool" default="true" />
<description>
Blend another animation node (in case this node contains children animation nodes). This function is only useful if you inherit from [AnimationRootNode] instead, else editors will not display your node for addition.
</description>
Expand Down
3 changes: 3 additions & 0 deletions doc/classes/AnimationNodeAdd2.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
<methods>
</methods>
<members>
<member name="add_directly" type="bool" setter="set_add_directly" getter="get_add_directly" default="false">
If [code]true[/code], transform tracks will have their location and rotation applied directly. Normal behavior, when [code]false[/code], adds transforms using blending (linear interpolation).
</member>
<member name="sync" type="bool" setter="set_use_sync" getter="is_using_sync" default="false">
If [code]true[/code], sets the [code]optimization[/code] to [code]false[/code] when calling [method AnimationNode.blend_input], forcing the blended animations to update every frame.
</member>
Expand Down
21 changes: 21 additions & 0 deletions doc/classes/AnimationNodeSub2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="AnimationNodeSub2" inherits="AnimationNode" version="3.6" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Blends two animations subtractively inside of an [AnimationNodeBlendTree].
</brief_description>
<description>
A resource to add to an [AnimationNodeBlendTree]. Blends two animations subtractively based on an amount value in the [code][0.0, 1.0][/code] range. The direct inverse of the Add2 node.
</description>
<tutorials>
<link>$DOCS_URL/tutorials/animation/animation_tree.html</link>
</tutorials>
<methods>
</methods>
<members>
<member name="sync" type="bool" setter="set_use_sync" getter="is_using_sync" default="false">
If [code]true[/code], sets the [code]optimization[/code] to [code]false[/code] when calling [method AnimationNode.blend_input], forcing the blended animations to update every frame.
</member>
</members>
<constants>
</constants>
</class>
1 change: 1 addition & 0 deletions editor/plugins/animation_blend_tree_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,7 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() {

add_options.push_back(AddOption("Animation", "AnimationNodeAnimation"));
add_options.push_back(AddOption("OneShot", "AnimationNodeOneShot", 2));
add_options.push_back(AddOption("Sub2", "AnimationNodeSub2", 2));
add_options.push_back(AddOption("Add2", "AnimationNodeAdd2", 2));
add_options.push_back(AddOption("Add3", "AnimationNodeAdd3", 3));
add_options.push_back(AddOption("Blend2", "AnimationNodeBlend2", 2));
Expand Down
65 changes: 62 additions & 3 deletions scene/animation/animation_blend_tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,53 @@ AnimationNodeOneShot::AnimationNodeOneShot() {

////////////////////////////////////////////////

void AnimationNodeSub2::get_parameter_list(List<PropertyInfo> *r_list) const {
r_list->push_back(PropertyInfo(Variant::REAL, sub_amount, PROPERTY_HINT_RANGE, "0,1,0.01"));
}

Variant AnimationNodeSub2::get_parameter_default_value(const StringName &p_parameter) const {
return 0;
}

String AnimationNodeSub2::get_caption() const {
return "Sub2";
}

void AnimationNodeSub2::set_use_sync(bool p_sync) {
Riordan-DC marked this conversation as resolved.
Show resolved Hide resolved
sync = p_sync;
}

bool AnimationNodeSub2::is_using_sync() const {
return sync;
}

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

float AnimationNodeSub2::process(float p_time, bool p_seek) {
// Out = Sub.Transform3D^(-1) * In.Transform3D
float amount = get_parameter(sub_amount);
float rem0 = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, sync, true); // in
blend_input(1, p_time, p_seek, -amount, FILTER_PASS, sync, false); // sub
return rem0;
}

void AnimationNodeSub2::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeSub2::set_use_sync);
ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeSub2::is_using_sync);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
}

AnimationNodeSub2::AnimationNodeSub2() {
sub_amount = PNAME("sub_amount");
add_input("in");
add_input("sub");
sync = false;
}

////////////////////////////////////////////////

void AnimationNodeAdd2::get_parameter_list(List<PropertyInfo> *r_list) const {
r_list->push_back(PropertyInfo(Variant::REAL, add_amount, PROPERTY_HINT_RANGE, "0,1,0.01"));
}
Expand All @@ -379,30 +426,42 @@ bool AnimationNodeAdd2::is_using_sync() const {
return sync;
}

void AnimationNodeAdd2::set_add_directly(bool p_add_directly) {
add_directly = p_add_directly;
}

bool AnimationNodeAdd2::get_add_directly() const {
return add_directly;
}

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

float AnimationNodeAdd2::process(float p_time, bool p_seek) {
float amount = get_parameter(add_amount);
float rem0 = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync);
blend_input(1, p_time, p_seek, amount, FILTER_PASS, !sync);

float rem0 = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, sync, true);
blend_input(1, p_time, p_seek, amount, FILTER_PASS, sync, add_directly ? false : true);
return rem0;
}

void AnimationNodeAdd2::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeAdd2::set_use_sync);
ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeAdd2::is_using_sync);

ClassDB::bind_method(D_METHOD("set_add_directly", "enable"), &AnimationNodeAdd2::set_add_directly);
ClassDB::bind_method(D_METHOD("get_add_directly"), &AnimationNodeAdd2::get_add_directly);

ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "add_directly"), "set_add_directly", "get_add_directly");
}

AnimationNodeAdd2::AnimationNodeAdd2() {
add_amount = PNAME("add_amount");
add_input("in");
add_input("add");
sync = false;
add_directly = false;
}

////////////////////////////////////////////////
Expand Down
29 changes: 29 additions & 0 deletions scene/animation/animation_blend_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,37 @@ class AnimationNodeOneShot : public AnimationNode {

VARIANT_ENUM_CAST(AnimationNodeOneShot::MixMode)

class AnimationNodeSub2 : public AnimationNode {
GDCLASS(AnimationNodeSub2, AnimationNode);

bool sync;

StringName sub_amount;

protected:
static void _bind_methods();

public:
void get_parameter_list(List<PropertyInfo> *r_list) const;
virtual Variant get_parameter_default_value(const StringName &p_parameter) const;

virtual String get_caption() const;

void set_use_sync(bool p_sync);
bool is_using_sync() const;

virtual bool has_filter() const;
virtual float process(float p_time, bool p_seek);

AnimationNodeSub2();
};

class AnimationNodeAdd2 : public AnimationNode {
Riordan-DC marked this conversation as resolved.
Show resolved Hide resolved
GDCLASS(AnimationNodeAdd2, AnimationNode);

StringName add_amount;
bool sync;
bool add_directly;

protected:
static void _bind_methods();
Expand All @@ -147,6 +173,9 @@ class AnimationNodeAdd2 : public AnimationNode {
void set_use_sync(bool p_sync);
bool is_using_sync() const;

void set_add_directly(bool p_add_directly);
bool get_add_directly() const;

virtual bool has_filter() const;
virtual float process(float p_time, bool p_seek);

Expand Down
61 changes: 39 additions & 22 deletions scene/animation/animation_tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ void AnimationNode::blend_animation(const StringName &p_animation, float p_time,
anim_state.time = p_time;
anim_state.animation = animation;
anim_state.seeked = p_seeked;
anim_state.use_blend = use_blend;

state->animation_states.push_back(anim_state);
}
Expand Down Expand Up @@ -141,7 +142,7 @@ void AnimationNode::make_invalid(const String &p_reason) {
state->invalid_reasons += String::utf8("• ") + p_reason;
}

float AnimationNode::blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize) {
float AnimationNode::blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize, bool p_use_blend) {
ERR_FAIL_INDEX_V(p_input, inputs.size(), 0);
ERR_FAIL_COND_V(!state, 0);

Expand All @@ -160,7 +161,7 @@ float AnimationNode::blend_input(int p_input, float p_time, bool p_seek, float p

//inputs.write[p_input].last_pass = state->last_pass;
float activity = 0;
float ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), nullptr, node, p_time, p_seek, p_blend, p_filter, p_optimize, &activity);
float ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), nullptr, node, p_time, p_seek, p_blend, p_filter, p_optimize, &activity, p_use_blend);

Vector<AnimationTree::Activity> *activity_ptr = state->tree->input_activity_map.getptr(base_path);

Expand All @@ -171,11 +172,11 @@ float AnimationNode::blend_input(int p_input, float p_time, bool p_seek, float p
return ret;
}

float AnimationNode::blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize) {
return _blend_node(p_sub_path, Vector<StringName>(), this, p_node, p_time, p_seek, p_blend, p_filter, p_optimize);
float AnimationNode::blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize, bool p_use_blend) {
return _blend_node(p_sub_path, Vector<StringName>(), this, p_node, p_time, p_seek, p_blend, p_filter, p_optimize, nullptr, p_use_blend);
}

float AnimationNode::_blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize, float *r_max) {
float AnimationNode::_blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize, float *r_max, bool p_use_blend) {
ERR_FAIL_COND_V(!p_node.is_valid(), 0);
ERR_FAIL_COND_V(!state, 0);

Expand All @@ -185,6 +186,12 @@ float AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Strin
p_node->blends.resize(blend_count);
}

// If an animation node sets use_blend to false it must remain false
// this is a way to make it pass down the tree. Without this
// the next node, which will often set use_blend to true, will
// override our request for use_blend false.
p_node->use_blend = p_use_blend && use_blend;

float *blendw = p_node->blends.ptrw();
const float *blendr = blends.ptr();

Expand Down Expand Up @@ -215,7 +222,7 @@ float AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Strin
}

blendw[i] = blendr[i] * p_blend;
if (blendw[i] > CMP_EPSILON) {
if (Math::absf(blendw[i]) > CMP_EPSILON) {
any_valid = true;
}
}
Expand All @@ -230,7 +237,7 @@ float AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Strin
}

blendw[i] = blendr[i] * p_blend;
if (blendw[i] > CMP_EPSILON) {
if (Math::absf(blendw[i]) > CMP_EPSILON) {
any_valid = true;
}
}
Expand All @@ -246,7 +253,7 @@ float AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Strin
blendw[i] = blendr[i]; //not filtered, do not blend
}

if (blendw[i] > CMP_EPSILON) {
if (Math::absf(blendw[i]) > CMP_EPSILON) {
any_valid = true;
}
}
Expand All @@ -257,7 +264,7 @@ float AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Strin
for (int i = 0; i < blend_count; i++) {
//regular blend
blendw[i] = blendr[i] * p_blend;
if (blendw[i] > CMP_EPSILON) {
if (Math::absf(blendw[i]) > CMP_EPSILON) {
any_valid = true;
}
}
Expand All @@ -266,7 +273,7 @@ float AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Strin
if (r_max) {
*r_max = 0;
for (int i = 0; i < blend_count; i++) {
*r_max = MAX(*r_max, blendw[i]);
*r_max = MAX(*r_max, Math::absf(blendw[i])); // To account for negative blend weights as used by Sub2 nodes.
}
}

Expand Down Expand Up @@ -411,8 +418,8 @@ void AnimationNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("_get_filters"), &AnimationNode::_get_filters);

ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "blend"), &AnimationNode::blend_animation);
ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true));
ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true));
ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "blend", "filter", "optimize", "use_blend"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(true));
ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "blend", "filter", "optimize", "use_blend"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(true));

ClassDB::bind_method(D_METHOD("set_parameter", "name", "value"), &AnimationNode::set_parameter);
ClassDB::bind_method(D_METHOD("get_parameter", "name"), &AnimationNode::get_parameter);
Expand Down Expand Up @@ -446,6 +453,7 @@ AnimationNode::AnimationNode() {
state = nullptr;
parent = nullptr;
filter_enabled = false;
use_blend = true;
}

////////////////////
Expand Down Expand Up @@ -843,7 +851,7 @@ void AnimationTree::_process_graph(float p_delta) {

float blend = (*as.track_blends)[blend_idx] * weight;

if (blend < CMP_EPSILON) {
if (Math::absf(blend) < CMP_EPSILON) {
continue; //nothing to blend
}

Expand Down Expand Up @@ -919,20 +927,29 @@ void AnimationTree::_process_graph(float p_delta) {
t->scale = scale;
}

if (err != OK) {
if (err != OK) { // Error happens when I animated an object transform manually?? fix.
continue;
}

t->loc = t->loc.linear_interpolate(loc, blend);
if (t->rot_blend_accum == 0) {
t->rot = rot;
t->rot_blend_accum = blend;
if (as.use_blend) {
blend = Math::absf(blend); // If negative rot_total could be 0, causing divide by 0 errors, inf numbers.
t->loc = t->loc.linear_interpolate(loc, blend);
if (t->rot_blend_accum == 0) {
t->rot = rot;
t->rot_blend_accum = blend;
} else {
float rot_total = t->rot_blend_accum + blend;
t->rot = rot.slerp(t->rot, t->rot_blend_accum / rot_total).normalized();
t->rot_blend_accum = rot_total;
}
} else {
float rot_total = t->rot_blend_accum + blend;
t->rot = rot.slerp(t->rot, t->rot_blend_accum / rot_total).normalized();
t->rot_blend_accum = rot_total;
// Direct addition / subtraction.
t->loc += loc * blend;
Quat q = Quat();
q = q.slerp(rot.normalized(), blend).normalized();
t->rot = (t->rot * q).normalized();
}
t->scale = t->scale.linear_interpolate(scale, blend);
t->scale = t->scale.linear_interpolate(scale, Math::absf(blend));
}

} break;
Expand Down
8 changes: 5 additions & 3 deletions scene/animation/animation_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class AnimationNode : public Resource {
const Vector<float> *track_blends;
float blend;
bool seeked;
bool use_blend;
};

struct State {
Expand All @@ -91,16 +92,17 @@ class AnimationNode : public Resource {

HashMap<NodePath, bool> filter;
bool filter_enabled;
bool use_blend;

Array _get_filters() const;
void _set_filters(const Array &p_filters);
friend class AnimationNodeBlendTree;
float _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, float *r_max = nullptr);
float _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, float *r_max = nullptr, bool p_use_blend = true);

protected:
void blend_animation(const StringName &p_animation, float p_time, float p_delta, bool p_seeked, float p_blend);
float blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true);
float blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true);
float blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, bool p_use_blend = true);
float blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, bool p_use_blend = true);
void make_invalid(const String &p_reason);

static void _bind_methods();
Expand Down
Loading