Rework AnimationNode process for retrieving the semantic time info

This commit is contained in:
Silc Lizard (Tokage) Renew 2024-01-08 06:08:10 +09:00
parent fe01776f05
commit 6dd410854c
18 changed files with 704 additions and 319 deletions

View file

@ -6,6 +6,13 @@
<description>
Base resource for [AnimationTree] nodes. In general, it's not used directly, but you can create custom ones with custom blending formulas.
Inherit this when creating animation nodes mainly for use in [AnimationNodeBlendTree], otherwise [AnimationRootNode] should be used instead.
You can access the time information as read-only parameter which is processed and stored in the previous frame for all nodes except [AnimationNodeOutput].
[b]Note:[/b] If more than two inputs exist in the [AnimationNode], which time information takes precedence depends on the type of [AnimationNode].
[codeblock]
var current_length = $AnimationTree[parameters/AnimationNodeName/current_length]
var current_position = $AnimationTree[parameters/AnimationNodeName/current_position]
var current_delta = $AnimationTree[parameters/AnimationNodeName/current_delta]
[/codeblock]
</description>
<tutorials>
<link title="Using AnimationTree">$DOCS_URL/tutorials/animation/animation_tree.html</link>
@ -56,7 +63,7 @@
When inheriting from [AnimationRootNode], implement this virtual method to return whether the [param parameter] is read-only. Parameters are custom local memory used for your animation nodes, given a resource can be reused in multiple trees.
</description>
</method>
<method name="_process" qualifiers="virtual const">
<method name="_process" qualifiers="virtual const" deprecated="Currently this is mostly useless as there is a lack of many APIs to extend AnimationNode by GDScript. It is planned that a more flexible API using structures will be provided in the future.">
<return type="float" />
<param index="0" name="time" type="float" />
<param index="1" name="seek" type="bool" />
@ -65,7 +72,7 @@
<description>
When inheriting from [AnimationRootNode], implement this virtual method to run some code when this animation node is processed. The [param time] parameter is a relative delta, unless [param seek] is [code]true[/code], in which case it is absolute.
Here, call the [method blend_input], [method blend_node] or [method blend_animation] functions. You can also use [method get_parameter] and [method set_parameter] to modify local memory.
This function should return the time left for the current animation to finish (if unsure, pass the value from the main blend being called).
This function should return the delta.
</description>
</method>
<method name="add_input">

View file

@ -15,9 +15,27 @@
<member name="animation" type="StringName" setter="set_animation" getter="get_animation" default="&amp;&quot;&quot;">
Animation to use as an output. It is one of the animations provided by [member AnimationTree.anim_player].
</member>
<member name="loop_mode" type="int" setter="set_loop_mode" getter="get_loop_mode" enum="Animation.LoopMode">
If [member use_custom_timeline] is [code]true[/code], override the loop settings of the original [Animation] resource with the value.
</member>
<member name="play_mode" type="int" setter="set_play_mode" getter="get_play_mode" enum="AnimationNodeAnimation.PlayMode" default="0">
Determines the playback direction of the animation.
</member>
<member name="start_offset" type="float" setter="set_start_offset" getter="get_start_offset">
If [member use_custom_timeline] is [code]true[/code], offset the start position of the animation.
This is useful for adjusting which foot steps first in 3D walking animations.
</member>
<member name="stretch_time_scale" type="bool" setter="set_stretch_time_scale" getter="is_stretching_time_scale">
If [code]true[/code], scales the time so that the length specified in [member timeline_length] is one cycle.
This is useful for matching the periods of walking and running animations.
If [code]false[/code], the original animation length is respected. If you set the loop to [member loop_mode], the animation will loop in [member timeline_length].
</member>
<member name="timeline_length" type="float" setter="set_timeline_length" getter="get_timeline_length">
If [member use_custom_timeline] is [code]true[/code], offset the start position of the animation.
</member>
<member name="use_custom_timeline" type="bool" setter="set_use_custom_timeline" getter="is_using_custom_timeline" default="false">
If [code]true[/code], [AnimationNode] provides an animation based on the [Animation] resource with some parameters adjusted.
</member>
</members>
<constants>
<constant name="PLAY_MODE_FORWARD" value="0" enum="PlayMode">

View file

@ -66,17 +66,22 @@
<member name="autorestart_random_delay" type="float" setter="set_autorestart_random_delay" getter="get_autorestart_random_delay" default="0.0">
If [member autorestart] is [code]true[/code], a random additional delay (in seconds) between 0 and this value will be added to [member autorestart_delay].
</member>
<member name="break_loop_at_end" type="bool" setter="set_break_loop_at_end" getter="is_loop_broken_at_end" default="false">
If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping.
</member>
<member name="fadein_curve" type="Curve" setter="set_fadein_curve" getter="get_fadein_curve">
Determines how cross-fading between animations is eased. If empty, the transition will be linear.
</member>
<member name="fadein_time" type="float" setter="set_fadein_time" getter="get_fadein_time" default="0.0">
The fade-in duration. For example, setting this to [code]1.0[/code] for a 5 second length animation will produce a cross-fade that starts at 0 second and ends at 1 second during the animation.
[b]Note:[/b] [AnimationNodeOneShot] transitions the current state after the end of the fading. When [AnimationNodeOutput] is considered as the most upstream, so the [member fadein_time] is scaled depending on the downstream delta. For example, if this value is set to [code]1.0[/code] and a [AnimationNodeTimeScale] with a value of [code]2.0[/code] is chained downstream, the actual processing time will be 0.5 second.
</member>
<member name="fadeout_curve" type="Curve" setter="set_fadeout_curve" getter="get_fadeout_curve">
Determines how cross-fading between animations is eased. If empty, the transition will be linear.
</member>
<member name="fadeout_time" type="float" setter="set_fadeout_time" getter="get_fadeout_time" default="0.0">
The fade-out duration. For example, setting this to [code]1.0[/code] for a 5 second length animation will produce a cross-fade that starts at 4 second and ends at 5 second during the animation.
[b]Note:[/b] [AnimationNodeOneShot] transitions the current state after the end of the fading. When [AnimationNodeOutput] is considered as the most upstream, so the [member fadeout_time] is scaled depending on the downstream delta. For example, if this value is set to [code]1.0[/code] and an [AnimationNodeTimeScale] with a value of [code]2.0[/code] is chained downstream, the actual processing time will be 0.5 second.
</member>
<member name="mix_mode" type="int" setter="set_mix_mode" getter="get_mix_mode" enum="AnimationNodeOneShot.MixMode" default="0">
The blend type.

View file

@ -28,6 +28,9 @@
<member name="advance_mode" type="int" setter="set_advance_mode" getter="get_advance_mode" enum="AnimationNodeStateMachineTransition.AdvanceMode" default="1">
Determines whether the transition should disabled, enabled when using [method AnimationNodeStateMachinePlayback.travel], or traversed automatically if the [member advance_condition] and [member advance_expression] checks are true (if assigned).
</member>
<member name="break_loop_at_end" type="bool" setter="set_break_loop_at_end" getter="is_loop_broken_at_end" default="false">
If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping.
</member>
<member name="priority" type="int" setter="set_priority" getter="get_priority" default="1">
Lower priority transitions are preferred when travelling through the tree via [method AnimationNodeStateMachinePlayback.travel] or [member advance_mode] is set to [constant ADVANCE_MODE_AUTO].
</member>
@ -42,6 +45,7 @@
</member>
<member name="xfade_time" type="float" setter="set_xfade_time" getter="get_xfade_time" default="0.0">
The time to cross-fade between this state and the next.
[b]Note:[/b] [AnimationNodeStateMachine] transitions the current state immediately after the start of the fading. The precise remaining time can only be inferred from the main animation. When [AnimationNodeOutput] is considered as the most upstream, so the [member xfade_time] is not scaled depending on the downstream delta. See also [member AnimationNodeOneShot.fadeout_time].
</member>
</members>
<signals>

View file

@ -42,6 +42,13 @@
<link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link>
</tutorials>
<methods>
<method name="is_input_loop_broken_at_end" qualifiers="const">
<return type="bool" />
<param index="0" name="input" type="int" />
<description>
Returns whether the animation breaks the loop at the end of the loop cycle for transition.
</description>
</method>
<method name="is_input_reset" qualifiers="const">
<return type="bool" />
<param index="0" name="input" type="int" />
@ -64,6 +71,14 @@
Enables or disables auto-advance for the given [param input] index. If enabled, state changes to the next input after playing the animation once. If enabled for the last input state, it loops to the first.
</description>
</method>
<method name="set_input_break_loop_at_end">
<return type="void" />
<param index="0" name="input" type="int" />
<param index="1" name="enable" type="bool" />
<description>
If [code]true[/code], breaks the loop at the end of the loop cycle for transition, even if the animation is looping.
</description>
</method>
<method name="set_input_reset">
<return type="void" />
<param index="0" name="input" type="int" />
@ -85,6 +100,7 @@
</member>
<member name="xfade_time" type="float" setter="set_xfade_time" getter="get_xfade_time" default="0.0">
Cross-fading time (in seconds) between each animation connected to the inputs.
[b]Note:[/b] [AnimationNodeTransition] transitions the current state immediately after the start of the fading. The precise remaining time can only be inferred from the main animation. When [AnimationNodeOutput] is considered as the most upstream, so the [member xfade_time] is not scaled depending on the downstream delta. See also [member AnimationNodeOneShot.fadeout_time].
</member>
</members>
</class>

View file

@ -256,10 +256,6 @@ void AnimationNodeBlendTreeEditor::update_graph() {
options.push_back(F);
}
if (tree->has_animation(anim->get_animation())) {
pb->set_max(tree->get_animation(anim->get_animation())->get_length());
}
pb->set_show_percentage(false);
pb->set_custom_minimum_size(Vector2(0, 14) * EDSCALE);
animations[E] = pb;
@ -994,9 +990,10 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) {
if (tree->has_animation(an->get_animation())) {
Ref<Animation> anim = tree->get_animation(an->get_animation());
if (anim.is_valid()) {
E.value->set_max(anim->get_length());
//StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node;
StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/time";
StringName length_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/current_length";
StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/current_position";
E.value->set_max(tree->get(length_path));
E.value->set_value(tree->get(time_path));
}
}

View file

@ -1247,14 +1247,14 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw_all() {
{
float len = MAX(0.0001, current_length);
float pos = CLAMP(current_play_pos, 0, len);
float c = current_length == HUGE_LENGTH ? 1 : (pos / len);
float c = pos / len;
_state_machine_pos_draw_individual(playback->get_current_node(), c);
}
{
float len = MAX(0.0001, fade_from_length);
float pos = CLAMP(fade_from_current_play_pos, 0, len);
float c = fade_from_length == HUGE_LENGTH ? 1 : (pos / len);
float c = pos / len;
_state_machine_pos_draw_individual(playback->get_fading_from_node(), c);
}
}

View file

@ -33,12 +33,17 @@
#include "animation_blend_tree.h"
void AnimationNodeBlendSpace1D::get_parameter_list(List<PropertyInfo> *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, blend_position));
r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
r_list->push_back(PropertyInfo(Variant::FLOAT, length_internal, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
}
Variant AnimationNodeBlendSpace1D::get_parameter_default_value(const StringName &p_parameter) const {
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
if (ret != Variant()) {
return ret;
}
if (p_parameter == closest) {
return -1;
} else {
@ -272,9 +277,9 @@ void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<Animatio
}
}
double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
if (blend_points_used == 0) {
return 0.0;
return NodeTimeInfo();
}
AnimationMixer::PlaybackInfo pi = p_playback_info;
@ -287,8 +292,7 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
double blend_pos = get_parameter(blend_position);
int cur_closest = get_parameter(closest);
double cur_length_internal = get_parameter(length_internal);
double max_time_remaining = 0.0;
NodeTimeInfo mind;
if (blend_mode == BLEND_MODE_INTERPOLATED) {
int point_lower = -1;
@ -341,12 +345,17 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
}
// actually blend the animations now
bool first = true;
double max_weight = 0.0;
for (int i = 0; i < blend_points_used; i++) {
if (i == point_lower || i == point_higher) {
pi.weight = weights[i];
double remaining = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
max_time_remaining = MAX(max_time_remaining, remaining);
NodeTimeInfo t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
if (first || pi.weight > max_weight) {
max_weight = pi.weight;
mind = t;
first = false;
}
} else if (sync) {
pi.weight = 0;
blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
@ -365,7 +374,7 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
}
if (new_closest != cur_closest && new_closest != -1) {
double from = 0.0;
NodeTimeInfo from;
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
//for ping-pong loop
Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
@ -376,18 +385,17 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
//see how much animation remains
pi.seeked = false;
pi.weight = 0;
from = cur_length_internal - blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
}
pi.time = from;
pi.time = from.position;
pi.seeked = true;
pi.weight = 1.0;
max_time_remaining = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);
cur_length_internal = from + max_time_remaining;
mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);
cur_closest = new_closest;
} else {
pi.weight = 1.0;
max_time_remaining = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
mind = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
}
if (sync) {
@ -402,8 +410,7 @@ double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_
}
set_parameter(closest, cur_closest);
set_parameter(length_internal, cur_length_internal);
return max_time_remaining;
return mind;
}
String AnimationNodeBlendSpace1D::get_caption() const {

View file

@ -68,7 +68,6 @@ protected:
StringName blend_position = "blend_position";
StringName closest = "closest";
StringName length_internal = "length_internal";
BlendMode blend_mode = BLEND_MODE_INTERPOLATED;
@ -114,7 +113,7 @@ public:
void set_use_sync(bool p_sync);
bool is_using_sync() const;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
String get_caption() const override;
Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;

View file

@ -34,16 +34,19 @@
#include "core/math/geometry_2d.h"
void AnimationNodeBlendSpace2D::get_parameter_list(List<PropertyInfo> *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::VECTOR2, blend_position));
r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
r_list->push_back(PropertyInfo(Variant::FLOAT, length_internal, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
}
Variant AnimationNodeBlendSpace2D::get_parameter_default_value(const StringName &p_parameter) const {
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
if (ret != Variant()) {
return ret;
}
if (p_parameter == closest) {
return -1;
} else if (p_parameter == length_internal) {
return 0;
} else {
return Vector2();
}
@ -442,19 +445,18 @@ void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vect
r_weights[2] = w;
}
double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
_update_triangles();
Vector2 blend_pos = get_parameter(blend_position);
int cur_closest = get_parameter(closest);
double cur_length_internal = get_parameter(length_internal);
double mind = 0.0; //time of min distance point
NodeTimeInfo mind; //time of min distance point
AnimationMixer::PlaybackInfo pi = p_playback_info;
if (blend_mode == BLEND_MODE_INTERPOLATED) {
if (triangles.size() == 0) {
return 0;
return NodeTimeInfo();
}
Vector2 best_point;
@ -500,7 +502,7 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
}
}
ERR_FAIL_COND_V(blend_triangle == -1, 0); //should never reach here
ERR_FAIL_COND_V(blend_triangle == -1, NodeTimeInfo()); //should never reach here
int triangle_points[3];
for (int j = 0; j < 3; j++) {
@ -509,15 +511,17 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
first = true;
bool found = false;
double max_weight = 0.0;
for (int i = 0; i < blend_points_used; i++) {
bool found = false;
for (int j = 0; j < 3; j++) {
if (i == triangle_points[j]) {
//blend with the given weight
pi.weight = blend_weights[j];
double t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
if (first || t < mind) {
NodeTimeInfo t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
if (first || pi.weight > max_weight) {
mind = t;
max_weight = pi.weight;
first = false;
}
found = true;
@ -543,7 +547,7 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
}
if (new_closest != cur_closest && new_closest != -1) {
double from = 0.0;
NodeTimeInfo from;
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
//for ping-pong loop
Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
@ -554,14 +558,13 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
//see how much animation remains
pi.seeked = false;
pi.weight = 0;
from = cur_length_internal - blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
}
pi.time = from;
pi.time = from.position;
pi.seeked = true;
pi.weight = 1.0;
mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);
cur_length_internal = from + mind;
cur_closest = new_closest;
} else {
pi.weight = 1.0;
@ -580,7 +583,6 @@ double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_
}
set_parameter(closest, cur_closest);
set_parameter(length_internal, cur_length_internal);
return mind;
}

View file

@ -65,7 +65,6 @@ protected:
StringName blend_position = "blend_position";
StringName closest = "closest";
StringName length_internal = "length_internal";
Vector2 max_space = Vector2(1, 1);
Vector2 min_space = Vector2(-1, -1);
Vector2 snap = Vector2(0.1, 0.1);
@ -129,7 +128,7 @@ public:
void set_y_label(const String &p_label);
String get_y_label() const;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual String get_caption() const override;
Vector2 get_closest_point(const Vector2 &p_point);

View file

@ -44,7 +44,26 @@ StringName AnimationNodeAnimation::get_animation() const {
Vector<String> (*AnimationNodeAnimation::get_editable_animation_list)() = nullptr;
void AnimationNodeAnimation::get_parameter_list(List<PropertyInfo> *r_list) const {
r_list->push_back(PropertyInfo(Variant::FLOAT, time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
AnimationNode::get_parameter_list(r_list);
}
AnimationNode::NodeTimeInfo AnimationNodeAnimation::get_node_time_info() const {
NodeTimeInfo nti;
if (!process_state->tree->has_animation(animation)) {
return nti;
}
if (use_custom_timeline) {
nti.length = timeline_length;
nti.loop_mode = loop_mode;
} else {
Ref<Animation> anim = process_state->tree->get_animation(animation);
nti.length = (double)anim->get_length();
nti.loop_mode = anim->get_loop_mode();
}
nti.position = get_parameter(current_position);
return nti;
}
void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const {
@ -62,11 +81,34 @@ void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const
p_property.hint_string = anims;
}
}
if (!use_custom_timeline) {
if (p_property.name == "timeline_length" || p_property.name == "start_offset" || p_property.name == "loop_mode" || p_property.name == "stretch_time_scale") {
p_property.usage = PROPERTY_USAGE_NONE;
}
}
}
double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double cur_time = get_parameter(time);
AnimationNode::NodeTimeInfo AnimationNodeAnimation::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
process_state->is_testing = p_test_only;
AnimationMixer::PlaybackInfo pi = p_playback_info;
if (p_playback_info.seeked) {
pi.delta = get_node_time_info().position - p_playback_info.time;
} else {
pi.time = get_node_time_info().position + (backward ? -p_playback_info.delta : p_playback_info.delta);
}
NodeTimeInfo nti = _process(pi, p_test_only);
if (!p_test_only) {
set_node_time_info(nti);
}
return nti;
}
AnimationNode::NodeTimeInfo AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
if (!process_state->tree->has_animation(animation)) {
AnimationNodeBlendTree *tree = Object::cast_to<AnimationNodeBlendTree>(node_state.parent);
if (tree) {
@ -77,87 +119,129 @@ double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_pla
make_invalid(vformat(RTR("Animation not found: '%s'"), animation));
}
return 0;
return NodeTimeInfo();
}
Ref<Animation> anim = process_state->tree->get_animation(animation);
double anim_size = (double)anim->get_length();
double step = 0.0;
double prev_time = cur_time;
NodeTimeInfo cur_nti = get_node_time_info();
double cur_len = cur_nti.length;
double cur_time = p_playback_info.time;
double cur_delta = p_playback_info.delta;
Animation::LoopMode cur_loop_mode = cur_nti.loop_mode;
double prev_time = cur_nti.position;
Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
bool node_backward = play_mode == PLAY_MODE_BACKWARD;
double p_time = p_playback_info.time;
bool p_seek = p_playback_info.seeked;
bool p_is_external_seeking = p_playback_info.is_external_seeking;
if (p_playback_info.seeked) {
step = p_time - cur_time;
cur_time = p_time;
} else {
p_time *= backward ? -1.0 : 1.0;
cur_time = cur_time + p_time;
step = p_time;
}
bool is_just_looped = false;
bool is_looping = false;
if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) {
if (!Math::is_zero_approx(anim_size)) {
if (prev_time >= 0 && cur_time < 0) {
backward = !backward;
looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START;
// 1. Progress for AnimationNode.
if (cur_loop_mode != Animation::LOOP_NONE) {
if (cur_loop_mode == Animation::LOOP_LINEAR) {
if (!Math::is_zero_approx(cur_len)) {
if (prev_time <= cur_len && cur_time > cur_len) {
is_just_looped = true; // Don't break with negative timescale since remain will not be 0.
}
cur_time = Math::fposmod(cur_time, cur_len);
}
if (prev_time <= anim_size && cur_time > anim_size) {
backward = !backward;
looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END;
backward = false;
} else {
if (!Math::is_zero_approx(cur_len)) {
if (prev_time >= 0 && cur_time < 0) {
backward = !backward;
} else if (prev_time <= cur_len && cur_time > cur_len) {
backward = !backward;
is_just_looped = true; // Don't break with negative timescale since remain will not be 0.
}
cur_time = Math::pingpong(cur_time, cur_len);
}
cur_time = Math::pingpong(cur_time, anim_size);
}
is_looping = true;
} else if (anim->get_loop_mode() == Animation::LOOP_LINEAR) {
if (!Math::is_zero_approx(anim_size)) {
if (prev_time >= 0 && cur_time < 0) {
looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START;
}
if (prev_time <= anim_size && cur_time > anim_size) {
looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END;
}
cur_time = Math::fposmod(cur_time, anim_size);
}
backward = false;
is_looping = true;
} else {
if (cur_time < 0) {
step += cur_time;
cur_delta += cur_time;
cur_time = 0;
} else if (cur_time > anim_size) {
step += anim_size - cur_time;
cur_time = anim_size;
} else if (cur_time > cur_len) {
cur_delta += cur_time - cur_len;
cur_time = cur_len;
}
backward = false;
// If ended, don't progress animation. So set delta to 0.
if (p_time > 0) {
// If ended, don't progress AnimationNode. So set delta to 0.
if (!Math::is_zero_approx(cur_delta)) {
if (play_mode == PLAY_MODE_FORWARD) {
if (prev_time >= anim_size) {
step = 0;
if (prev_time >= cur_len) {
cur_delta = 0;
}
} else {
if (prev_time <= 0) {
step = 0;
cur_delta = 0;
}
}
}
}
// 2. For return, store "AnimationNode" time info here, not "Animation" time info as below.
NodeTimeInfo nti;
nti.length = cur_len;
nti.position = cur_time;
nti.delta = cur_delta;
nti.loop_mode = cur_loop_mode;
nti.is_just_looped = is_just_looped;
// 3. Progress for Animation.
double prev_playback_time = prev_time - start_offset;
double cur_playback_time = cur_time - start_offset;
if (stretch_time_scale) {
double mlt = anim_size / cur_len;
cur_playback_time *= mlt;
cur_delta *= mlt;
}
if (cur_loop_mode == Animation::LOOP_LINEAR) {
if (!Math::is_zero_approx(anim_size)) {
prev_playback_time = Math::fposmod(prev_playback_time, anim_size);
cur_playback_time = Math::fposmod(cur_playback_time, anim_size);
if (prev_playback_time >= 0 && cur_playback_time < 0) {
looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START;
}
if (prev_playback_time <= anim_size && cur_playback_time > anim_size) {
looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END;
}
}
} else if (cur_loop_mode == Animation::LOOP_PINGPONG) {
if (!Math::is_zero_approx(anim_size)) {
if (Math::fposmod(cur_playback_time, anim_size * 2.0) >= anim_size) {
cur_delta = -cur_delta; // Needed for retrieveing discrete keys correctly.
}
prev_playback_time = Math::pingpong(prev_playback_time, anim_size);
cur_playback_time = Math::pingpong(cur_playback_time, anim_size);
if (prev_playback_time >= 0 && cur_playback_time < 0) {
looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START;
}
if (prev_playback_time <= anim_size && cur_playback_time > anim_size) {
looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END;
}
}
} else {
if (cur_playback_time < 0) {
cur_playback_time = 0;
} else if (cur_playback_time > anim_size) {
cur_playback_time = anim_size;
}
// Emit start & finish signal. Internally, the detections are the same for backward.
// We should use call_deferred since the track keys are still being processed.
if (process_state->tree && !p_test_only) {
// AnimationTree uses seek to 0 "internally" to process the first key of the animation, which is used as the start detection.
if (p_seek && !p_is_external_seeking && cur_time == 0) {
if (p_seek && !p_is_external_seeking && cur_playback_time == 0) {
process_state->tree->call_deferred(SNAME("emit_signal"), "animation_started", animation);
}
// Finished.
if (prev_time < anim_size && cur_time >= anim_size) {
if (prev_time - start_offset < anim_size && cur_playback_time >= anim_size) {
process_state->tree->call_deferred(SNAME("emit_signal"), "animation_finished", animation);
}
}
@ -166,19 +250,18 @@ double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_pla
if (!p_test_only) {
AnimationMixer::PlaybackInfo pi = p_playback_info;
if (play_mode == PLAY_MODE_FORWARD) {
pi.time = cur_time;
pi.delta = step;
pi.time = cur_playback_time;
pi.delta = cur_delta;
} else {
pi.time = anim_size - cur_time;
pi.delta = -step;
pi.time = anim_size - cur_playback_time;
pi.delta = -cur_delta;
}
pi.weight = 1.0;
pi.looped_flag = looped_flag;
blend_animation(animation, pi);
}
set_parameter(time, cur_time);
return is_looping ? HUGE_LENGTH : anim_size - cur_time;
return nti;
}
String AnimationNodeAnimation::get_caption() const {
@ -201,6 +284,48 @@ bool AnimationNodeAnimation::is_backward() const {
return backward;
}
void AnimationNodeAnimation::set_use_custom_timeline(bool p_use_custom_timeline) {
use_custom_timeline = p_use_custom_timeline;
notify_property_list_changed();
}
bool AnimationNodeAnimation::is_using_custom_timeline() const {
return use_custom_timeline;
}
void AnimationNodeAnimation::set_timeline_length(double p_length) {
timeline_length = p_length;
}
double AnimationNodeAnimation::get_timeline_length() const {
return timeline_length;
}
void AnimationNodeAnimation::set_stretch_time_scale(bool p_strech_time_scale) {
stretch_time_scale = p_strech_time_scale;
notify_property_list_changed();
}
bool AnimationNodeAnimation::is_stretching_time_scale() const {
return stretch_time_scale;
}
void AnimationNodeAnimation::set_start_offset(double p_offset) {
start_offset = p_offset;
}
double AnimationNodeAnimation::get_start_offset() const {
return start_offset;
}
void AnimationNodeAnimation::set_loop_mode(Animation::LoopMode p_loop_mode) {
loop_mode = p_loop_mode;
}
Animation::LoopMode AnimationNodeAnimation::get_loop_mode() const {
return loop_mode;
}
void AnimationNodeAnimation::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimationNodeAnimation::set_animation);
ClassDB::bind_method(D_METHOD("get_animation"), &AnimationNodeAnimation::get_animation);
@ -208,8 +333,28 @@ void AnimationNodeAnimation::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_play_mode", "mode"), &AnimationNodeAnimation::set_play_mode);
ClassDB::bind_method(D_METHOD("get_play_mode"), &AnimationNodeAnimation::get_play_mode);
ClassDB::bind_method(D_METHOD("set_use_custom_timeline", "use_custom_timeline"), &AnimationNodeAnimation::set_use_custom_timeline);
ClassDB::bind_method(D_METHOD("is_using_custom_timeline"), &AnimationNodeAnimation::is_using_custom_timeline);
ClassDB::bind_method(D_METHOD("set_timeline_length", "timeline_length"), &AnimationNodeAnimation::set_timeline_length);
ClassDB::bind_method(D_METHOD("get_timeline_length"), &AnimationNodeAnimation::get_timeline_length);
ClassDB::bind_method(D_METHOD("set_stretch_time_scale", "stretch_time_scale"), &AnimationNodeAnimation::set_stretch_time_scale);
ClassDB::bind_method(D_METHOD("is_stretching_time_scale"), &AnimationNodeAnimation::is_stretching_time_scale);
ClassDB::bind_method(D_METHOD("set_start_offset", "start_offset"), &AnimationNodeAnimation::set_start_offset);
ClassDB::bind_method(D_METHOD("get_start_offset"), &AnimationNodeAnimation::get_start_offset);
ClassDB::bind_method(D_METHOD("set_loop_mode", "loop_mode"), &AnimationNodeAnimation::set_loop_mode);
ClassDB::bind_method(D_METHOD("get_loop_mode"), &AnimationNodeAnimation::get_loop_mode);
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation");
ADD_PROPERTY(PropertyInfo(Variant::INT, "play_mode", PROPERTY_HINT_ENUM, "Forward,Backward"), "set_play_mode", "get_play_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_custom_timeline"), "set_use_custom_timeline", "is_using_custom_timeline");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "timeline_length", PROPERTY_HINT_RANGE, "0.001,60,0.001,or_greater,or_less,hide_slider,suffix:s"), "set_timeline_length", "get_timeline_length");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch_time_scale"), "set_stretch_time_scale", "is_stretching_time_scale");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "start_offset", PROPERTY_HINT_RANGE, "-60,60,0.001,or_greater,or_less,hide_slider,suffix:s"), "set_start_offset", "get_start_offset");
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "None,Linear,Ping-Pong"), "set_loop_mode", "get_loop_mode");
BIND_ENUM_CONSTANT(PLAY_MODE_FORWARD);
BIND_ENUM_CONSTANT(PLAY_MODE_BACKWARD);
@ -240,16 +385,21 @@ AnimationNodeSync::AnimationNodeSync() {
////////////////////////////////////////////////////////
void AnimationNodeOneShot::get_parameter_list(List<PropertyInfo> *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::BOOL, active, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY));
r_list->push_back(PropertyInfo(Variant::BOOL, internal_active, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY));
r_list->push_back(PropertyInfo(Variant::INT, request, PROPERTY_HINT_ENUM, ",Fire,Abort,Fade Out"));
r_list->push_back(PropertyInfo(Variant::FLOAT, time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
r_list->push_back(PropertyInfo(Variant::FLOAT, remaining, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
r_list->push_back(PropertyInfo(Variant::FLOAT, fade_in_remaining, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
r_list->push_back(PropertyInfo(Variant::FLOAT, fade_out_remaining, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
r_list->push_back(PropertyInfo(Variant::FLOAT, time_to_restart, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
}
Variant AnimationNodeOneShot::get_parameter_default_value(const StringName &p_parameter) const {
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
if (ret != Variant()) {
return ret;
}
if (p_parameter == request) {
return ONE_SHOT_REQUEST_NONE;
} else if (p_parameter == active || p_parameter == internal_active) {
@ -262,6 +412,10 @@ Variant AnimationNodeOneShot::get_parameter_default_value(const StringName &p_pa
}
bool AnimationNodeOneShot::is_parameter_read_only(const StringName &p_parameter) const {
if (AnimationNode::is_parameter_read_only(p_parameter)) {
return true;
}
if (p_parameter == active || p_parameter == internal_active) {
return true;
}
@ -332,6 +486,14 @@ AnimationNodeOneShot::MixMode AnimationNodeOneShot::get_mix_mode() const {
return mix;
}
void AnimationNodeOneShot::set_break_loop_at_end(bool p_enable) {
break_loop_at_end = p_enable;
}
bool AnimationNodeOneShot::is_loop_broken_at_end() const {
return break_loop_at_end;
}
String AnimationNodeOneShot::get_caption() const {
return "OneShot";
}
@ -340,14 +502,14 @@ bool AnimationNodeOneShot::has_filter() const {
return true;
}
double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
OneShotRequest cur_request = static_cast<OneShotRequest>((int)get_parameter(request));
bool cur_active = get_parameter(active);
bool cur_internal_active = get_parameter(internal_active);
double cur_time = get_parameter(time);
double cur_remaining = get_parameter(remaining);
double cur_fade_out_remaining = get_parameter(fade_out_remaining);
NodeTimeInfo cur_nti = get_node_time_info();
double cur_time_to_restart = get_parameter(time_to_restart);
double cur_fade_in_remaining = get_parameter(fade_in_remaining);
double cur_fade_out_remaining = get_parameter(fade_out_remaining);
set_parameter(request, ONE_SHOT_REQUEST_NONE);
@ -356,6 +518,8 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
bool is_fading_out = cur_active == true && cur_internal_active == false;
double p_time = p_playback_info.time;
double p_delta = p_playback_info.delta;
double abs_delta = Math::abs(p_delta);
bool p_seek = p_playback_info.seeked;
bool p_is_external_seeking = p_playback_info.is_external_seeking;
@ -374,6 +538,7 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
// Request fading.
is_fading_out = true;
cur_fade_out_remaining = fade_out;
cur_fade_in_remaining = 0;
} else {
// Shot is ended, do nothing.
is_shooting = false;
@ -382,7 +547,7 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
set_parameter(time_to_restart, -1);
} else if (!do_start && !cur_active) {
if (cur_time_to_restart >= 0.0 && !p_seek) {
cur_time_to_restart -= p_time;
cur_time_to_restart -= abs_delta;
if (cur_time_to_restart < 0) {
do_start = true; // Restart.
}
@ -413,8 +578,11 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
}
if (do_start) {
cur_time = 0;
os_seek = true;
if (!cur_internal_active) {
cur_fade_in_remaining = fade_in; // If already active, don't fade-in again.
}
cur_internal_active = true;
set_parameter(request, ONE_SHOT_REQUEST_NONE);
set_parameter(internal_active, true);
set_parameter(active, true);
@ -422,20 +590,17 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
real_t blend = 1.0;
bool use_blend = sync;
if (cur_time < fade_in) {
if (cur_fade_in_remaining > 0) {
if (fade_in > 0) {
use_blend = true;
blend = cur_time / fade_in;
blend = (fade_in - cur_fade_in_remaining) / fade_in;
if (fade_in_curve.is_valid()) {
blend = fade_in_curve->sample(blend);
}
} else {
blend = 0; // Should not happen.
}
} else if (!do_start && !is_fading_out && cur_remaining <= fade_out) {
is_fading_out = true;
cur_fade_out_remaining = cur_remaining;
set_parameter(internal_active, false);
}
if (is_fading_out) {
@ -451,34 +616,36 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
}
AnimationMixer::PlaybackInfo pi = p_playback_info;
double main_rem = 0.0;
NodeTimeInfo main_nti;
if (mix == MIX_MODE_ADD) {
pi.weight = 1.0;
main_rem = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
main_nti = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
} else {
pi.seeked &= use_blend;
pi.weight = 1.0 - blend;
main_rem = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case.
main_nti = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case.
}
pi = p_playback_info;
if (os_seek) {
pi.time = cur_time;
if (do_start) {
pi.time = 0;
} else if (os_seek) {
pi.time = cur_nti.position;
}
pi.seeked = os_seek;
pi.weight = Math::is_zero_approx(blend) ? CMP_EPSILON : blend;
double os_rem = blend_input(1, pi, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
if (do_start) {
cur_remaining = os_rem;
NodeTimeInfo os_nti = blend_input(1, pi, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
if (cur_fade_in_remaining <= 0 && !do_start && !is_fading_out && os_nti.get_remain(break_loop_at_end) <= fade_out) {
is_fading_out = true;
cur_fade_out_remaining = os_nti.get_remain(break_loop_at_end);
cur_fade_in_remaining = 0;
set_parameter(internal_active, false);
}
if (p_seek) {
cur_time = p_time;
} else {
cur_time += p_time;
cur_remaining = os_rem;
cur_fade_out_remaining -= p_time;
if (cur_remaining <= 0 || (is_fading_out && cur_fade_out_remaining <= 0)) {
if (!p_seek) {
if (os_nti.get_remain(break_loop_at_end) <= 0 || (is_fading_out && cur_fade_out_remaining <= 0)) {
set_parameter(internal_active, false);
set_parameter(active, false);
if (auto_restart) {
@ -486,13 +653,17 @@ double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playb
set_parameter(time_to_restart, restart_sec);
}
}
double d = Math::abs(os_nti.delta);
if (!do_start) {
cur_fade_in_remaining = MAX(0, cur_fade_in_remaining - d); // Don't consider seeked delta by restart.
}
cur_fade_out_remaining = MAX(0, cur_fade_out_remaining - d);
}
set_parameter(time, cur_time);
set_parameter(remaining, cur_remaining);
set_parameter(fade_in_remaining, cur_fade_in_remaining);
set_parameter(fade_out_remaining, cur_fade_out_remaining);
return MAX(main_rem, cur_remaining);
return cur_internal_active ? os_nti : main_nti;
}
void AnimationNodeOneShot::_bind_methods() {
@ -508,6 +679,9 @@ void AnimationNodeOneShot::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_fadeout_curve", "curve"), &AnimationNodeOneShot::set_fade_out_curve);
ClassDB::bind_method(D_METHOD("get_fadeout_curve"), &AnimationNodeOneShot::get_fade_out_curve);
ClassDB::bind_method(D_METHOD("set_break_loop_at_end", "enable"), &AnimationNodeOneShot::set_break_loop_at_end);
ClassDB::bind_method(D_METHOD("is_loop_broken_at_end"), &AnimationNodeOneShot::is_loop_broken_at_end);
ClassDB::bind_method(D_METHOD("set_autorestart", "active"), &AnimationNodeOneShot::set_auto_restart_enabled);
ClassDB::bind_method(D_METHOD("has_autorestart"), &AnimationNodeOneShot::is_auto_restart_enabled);
@ -526,6 +700,7 @@ void AnimationNodeOneShot::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "fadein_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_fadein_curve", "get_fadein_curve");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fadeout_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater,suffix:s"), "set_fadeout_time", "get_fadeout_time");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "fadeout_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_fadeout_curve", "get_fadeout_curve");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "break_loop_at_end"), "set_break_loop_at_end", "is_loop_broken_at_end");
ADD_GROUP("Auto Restart", "autorestart_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autorestart"), "set_autorestart", "has_autorestart");
@ -550,10 +725,16 @@ AnimationNodeOneShot::AnimationNodeOneShot() {
////////////////////////////////////////////////
void AnimationNodeAdd2::get_parameter_list(List<PropertyInfo> *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, add_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
}
Variant AnimationNodeAdd2::get_parameter_default_value(const StringName &p_parameter) const {
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
if (ret != Variant()) {
return ret;
}
return 0;
}
@ -565,16 +746,16 @@ bool AnimationNodeAdd2::has_filter() const {
return true;
}
double AnimationNodeAdd2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeAdd2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(add_amount);
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0;
double rem0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
NodeTimeInfo nti = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
pi.weight = amount;
blend_input(1, pi, FILTER_PASS, sync, p_test_only);
return rem0;
return nti;
}
void AnimationNodeAdd2::_bind_methods() {
@ -588,10 +769,16 @@ AnimationNodeAdd2::AnimationNodeAdd2() {
////////////////////////////////////////////////
void AnimationNodeAdd3::get_parameter_list(List<PropertyInfo> *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, add_amount, PROPERTY_HINT_RANGE, "-1,1,0.01,or_less,or_greater"));
}
Variant AnimationNodeAdd3::get_parameter_default_value(const StringName &p_parameter) const {
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
if (ret != Variant()) {
return ret;
}
return 0;
}
@ -603,18 +790,18 @@ bool AnimationNodeAdd3::has_filter() const {
return true;
}
double AnimationNodeAdd3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeAdd3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(add_amount);
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = MAX(0, -amount);
blend_input(0, pi, FILTER_PASS, sync, p_test_only);
pi.weight = 1.0;
double rem0 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only);
NodeTimeInfo nti = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only);
pi.weight = MAX(0, amount);
blend_input(2, pi, FILTER_PASS, sync, p_test_only);
return rem0;
return nti;
}
void AnimationNodeAdd3::_bind_methods() {
@ -629,10 +816,16 @@ AnimationNodeAdd3::AnimationNodeAdd3() {
/////////////////////////////////////////////
void AnimationNodeBlend2::get_parameter_list(List<PropertyInfo> *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
}
Variant AnimationNodeBlend2::get_parameter_default_value(const StringName &p_parameter) const {
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
if (ret != Variant()) {
return ret;
}
return 0; // For blend amount.
}
@ -640,16 +833,16 @@ String AnimationNodeBlend2::get_caption() const {
return "Blend2";
}
double AnimationNodeBlend2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeBlend2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(blend_amount);
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0 - amount;
double rem0 = blend_input(0, pi, FILTER_BLEND, sync, p_test_only);
NodeTimeInfo nti0 = blend_input(0, pi, FILTER_BLEND, sync, p_test_only);
pi.weight = amount;
double rem1 = blend_input(1, pi, FILTER_PASS, sync, p_test_only);
NodeTimeInfo nti1 = blend_input(1, pi, FILTER_PASS, sync, p_test_only);
return amount > 0.5 ? rem1 : rem0; // Hacky but good enough.
return amount > 0.5 ? nti1 : nti0; // Hacky but good enough.
}
bool AnimationNodeBlend2::has_filter() const {
@ -667,10 +860,16 @@ AnimationNodeBlend2::AnimationNodeBlend2() {
//////////////////////////////////////
void AnimationNodeBlend3::get_parameter_list(List<PropertyInfo> *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "-1,1,0.01,or_less,or_greater"));
}
Variant AnimationNodeBlend3::get_parameter_default_value(const StringName &p_parameter) const {
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
if (ret != Variant()) {
return ret;
}
return 0; // For blend amount.
}
@ -678,18 +877,18 @@ String AnimationNodeBlend3::get_caption() const {
return "Blend3";
}
double AnimationNodeBlend3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeBlend3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(blend_amount);
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = MAX(0, -amount);
double rem0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
NodeTimeInfo nti0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
pi.weight = 1.0 - ABS(amount);
double rem1 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only);
NodeTimeInfo nti1 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only);
pi.weight = MAX(0, amount);
double rem2 = blend_input(2, pi, FILTER_IGNORE, sync, p_test_only);
NodeTimeInfo nti2 = blend_input(2, pi, FILTER_IGNORE, sync, p_test_only);
return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); // Hacky but good enough.
return amount > 0.5 ? nti2 : (amount < -0.5 ? nti0 : nti1); // Hacky but good enough.
}
void AnimationNodeBlend3::_bind_methods() {
@ -704,10 +903,16 @@ AnimationNodeBlend3::AnimationNodeBlend3() {
////////////////////////////////////////////////
void AnimationNodeSub2::get_parameter_list(List<PropertyInfo> *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, sub_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
}
Variant AnimationNodeSub2::get_parameter_default_value(const StringName &p_parameter) const {
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
if (ret != Variant()) {
return ret;
}
return 0;
}
@ -719,7 +924,7 @@ bool AnimationNodeSub2::has_filter() const {
return true;
}
double AnimationNodeSub2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeSub2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(sub_amount);
AnimationMixer::PlaybackInfo pi = p_playback_info;
@ -742,10 +947,16 @@ AnimationNodeSub2::AnimationNodeSub2() {
/////////////////////////////////
void AnimationNodeTimeScale::get_parameter_list(List<PropertyInfo> *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, scale, PROPERTY_HINT_RANGE, "-32,32,0.01,or_less,or_greater"));
}
Variant AnimationNodeTimeScale::get_parameter_default_value(const StringName &p_parameter) const {
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
if (ret != Variant()) {
return ret;
}
return 1.0; // Initial timescale.
}
@ -753,13 +964,13 @@ String AnimationNodeTimeScale::get_caption() const {
return "TimeScale";
}
double AnimationNodeTimeScale::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeTimeScale::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double cur_scale = get_parameter(scale);
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0;
if (!pi.seeked) {
pi.time *= cur_scale;
pi.delta *= cur_scale;
}
return blend_input(0, pi, FILTER_IGNORE, true, p_test_only);
@ -775,10 +986,16 @@ AnimationNodeTimeScale::AnimationNodeTimeScale() {
////////////////////////////////////
void AnimationNodeTimeSeek::get_parameter_list(List<PropertyInfo> *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::FLOAT, seek_pos_request, PROPERTY_HINT_RANGE, "-1,3600,0.01,or_greater")); // It will be reset to -1 after seeking the position immediately.
}
Variant AnimationNodeTimeSeek::get_parameter_default_value(const StringName &p_parameter) const {
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
if (ret != Variant()) {
return ret;
}
return -1.0; // Initial seek request.
}
@ -786,7 +1003,7 @@ String AnimationNodeTimeSeek::get_caption() const {
return "TimeSeek";
}
double AnimationNodeTimeSeek::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeTimeSeek::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double cur_seek_pos = get_parameter(seek_pos_request);
AnimationMixer::PlaybackInfo pi = p_playback_info;
@ -833,6 +1050,8 @@ bool AnimationNodeTransition::_set(const StringName &p_path, const Variant &p_va
set_input_name(which, p_value);
} else if (what == "auto_advance") {
set_input_as_auto_advance(which, p_value);
} else if (what == "break_loop_at_end") {
set_input_break_loop_at_end(which, p_value);
} else if (what == "reset") {
set_input_reset(which, p_value);
} else {
@ -858,6 +1077,8 @@ bool AnimationNodeTransition::_get(const StringName &p_path, Variant &r_ret) con
r_ret = get_input_name(which);
} else if (what == "auto_advance") {
r_ret = is_input_set_as_auto_advance(which);
} else if (what == "break_loop_at_end") {
r_ret = is_input_loop_broken_at_end(which);
} else if (what == "reset") {
r_ret = is_input_reset(which);
} else {
@ -868,6 +1089,7 @@ bool AnimationNodeTransition::_get(const StringName &p_path, Variant &r_ret) con
}
void AnimationNodeTransition::get_parameter_list(List<PropertyInfo> *r_list) const {
AnimationNode::get_parameter_list(r_list);
String anims;
for (int i = 0; i < get_input_count(); i++) {
if (i > 0) {
@ -880,12 +1102,16 @@ void AnimationNodeTransition::get_parameter_list(List<PropertyInfo> *r_list) con
r_list->push_back(PropertyInfo(Variant::STRING, transition_request, PROPERTY_HINT_ENUM, anims)); // For transition request. It will be cleared after setting the value immediately.
r_list->push_back(PropertyInfo(Variant::INT, current_index, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY)); // To avoid finding the index every frame, use this internally.
r_list->push_back(PropertyInfo(Variant::INT, prev_index, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
r_list->push_back(PropertyInfo(Variant::FLOAT, time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
r_list->push_back(PropertyInfo(Variant::FLOAT, prev_xfading, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
}
Variant AnimationNodeTransition::get_parameter_default_value(const StringName &p_parameter) const {
if (p_parameter == time || p_parameter == prev_xfading) {
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
if (ret != Variant()) {
return ret;
}
if (p_parameter == prev_xfading) {
return 0.0;
} else if (p_parameter == prev_index || p_parameter == current_index) {
return -1;
@ -895,6 +1121,10 @@ Variant AnimationNodeTransition::get_parameter_default_value(const StringName &p
}
bool AnimationNodeTransition::is_parameter_read_only(const StringName &p_parameter) const {
if (AnimationNode::is_parameter_read_only(p_parameter)) {
return true;
}
if (p_parameter == current_state || p_parameter == current_index) {
return true;
}
@ -947,6 +1177,16 @@ bool AnimationNodeTransition::is_input_set_as_auto_advance(int p_input) const {
return input_data[p_input].auto_advance;
}
void AnimationNodeTransition::set_input_break_loop_at_end(int p_input, bool p_enable) {
ERR_FAIL_INDEX(p_input, get_input_count());
input_data.write[p_input].break_loop_at_end = p_enable;
}
bool AnimationNodeTransition::is_input_loop_broken_at_end(int p_input) const {
ERR_FAIL_INDEX_V(p_input, get_input_count(), false);
return input_data[p_input].break_loop_at_end;
}
void AnimationNodeTransition::set_input_reset(int p_input, bool p_enable) {
ERR_FAIL_INDEX(p_input, get_input_count());
input_data.write[p_input].reset = p_enable;
@ -981,12 +1221,12 @@ bool AnimationNodeTransition::is_allow_transition_to_self() const {
return allow_transition_to_self;
}
double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
String cur_transition_request = get_parameter(transition_request);
int cur_current_index = get_parameter(current_index);
int cur_prev_index = get_parameter(prev_index);
double cur_time = get_parameter(time);
NodeTimeInfo cur_nti = get_node_time_info();
double cur_prev_xfading = get_parameter(prev_xfading);
bool switched = false;
@ -1052,7 +1292,6 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl
// Special case for restart.
if (restart) {
set_parameter(time, 0);
pi.time = 0;
pi.seeked = true;
pi.weight = 1.0;
@ -1061,16 +1300,12 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl
if (switched) {
cur_prev_xfading = xfade_time;
cur_time = 0;
}
if (cur_current_index < 0 || cur_current_index >= get_input_count() || cur_prev_index >= get_input_count()) {
return 0;
return NodeTimeInfo();
}
double rem = 0.0;
double abs_time = Math::abs(p_time);
if (sync) {
pi.weight = 0;
for (int i = 0; i < get_input_count(); i++) {
@ -1081,20 +1316,11 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl
}
if (cur_prev_index < 0) { // Process current animation, check for transition.
pi.weight = 1.0;
rem = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only);
if (p_seek) {
cur_time = abs_time;
} else {
cur_time += abs_time;
}
if (input_data[cur_current_index].auto_advance && rem <= xfade_time) {
cur_nti = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only);
if (input_data[cur_current_index].auto_advance && cur_nti.get_remain(input_data[cur_current_index].break_loop_at_end) <= xfade_time) {
set_parameter(transition_request, get_input_name((cur_current_index + 1) % get_input_count()));
}
} else { // Cross-fading from prev to current.
real_t blend = 0.0;
@ -1117,33 +1343,30 @@ double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_pl
pi.time = 0;
pi.seeked = true;
}
rem = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only);
cur_nti = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only);
pi = p_playback_info;
pi.seeked &= use_blend;
pi.weight = blend;
blend_input(cur_prev_index, pi, FILTER_IGNORE, true, p_test_only);
if (p_seek) {
cur_time = abs_time;
} else {
cur_time += abs_time;
cur_prev_xfading -= abs_time;
if (cur_prev_xfading < 0) {
if (!p_seek) {
if (cur_prev_xfading <= 0) {
set_parameter(prev_index, -1);
}
cur_prev_xfading -= Math::abs(p_playback_info.delta);
}
}
set_parameter(time, cur_time);
set_parameter(prev_xfading, cur_prev_xfading);
return rem;
return cur_nti;
}
void AnimationNodeTransition::_get_property_list(List<PropertyInfo> *p_list) const {
for (int i = 0; i < get_input_count(); i++) {
p_list->push_back(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/auto_advance", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/break_loop_at_end", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/reset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL));
}
}
@ -1154,6 +1377,9 @@ void AnimationNodeTransition::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_input_as_auto_advance", "input", "enable"), &AnimationNodeTransition::set_input_as_auto_advance);
ClassDB::bind_method(D_METHOD("is_input_set_as_auto_advance", "input"), &AnimationNodeTransition::is_input_set_as_auto_advance);
ClassDB::bind_method(D_METHOD("set_input_break_loop_at_end", "input", "enable"), &AnimationNodeTransition::set_input_break_loop_at_end);
ClassDB::bind_method(D_METHOD("is_input_loop_broken_at_end", "input"), &AnimationNodeTransition::is_input_loop_broken_at_end);
ClassDB::bind_method(D_METHOD("set_input_reset", "input", "enable"), &AnimationNodeTransition::set_input_reset);
ClassDB::bind_method(D_METHOD("is_input_reset", "input"), &AnimationNodeTransition::is_input_reset);
@ -1181,7 +1407,7 @@ String AnimationNodeOutput::get_caption() const {
return "Output";
}
double AnimationNodeOutput::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeOutput::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0;
return blend_input(0, pi, FILTER_IGNORE, true, p_test_only);
@ -1400,10 +1626,10 @@ String AnimationNodeBlendTree::get_caption() const {
return "BlendTree";
}
double AnimationNodeBlendTree::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeBlendTree::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
Ref<AnimationNodeOutput> output = nodes[SceneStringNames::get_singleton()->output].node;
node_state.connections = nodes[SceneStringNames::get_singleton()->output].connections;
ERR_FAIL_COND_V(output.is_null(), 0);
ERR_FAIL_COND_V(output.is_null(), NodeTimeInfo());
AnimationMixer::PlaybackInfo pi = p_playback_info;
pi.weight = 1.0;

View file

@ -37,7 +37,12 @@ class AnimationNodeAnimation : public AnimationRootNode {
GDCLASS(AnimationNodeAnimation, AnimationRootNode);
StringName animation;
StringName time = "time";
bool use_custom_timeline = false;
double timeline_length = 1.0;
Animation::LoopMode loop_mode = Animation::LOOP_NONE;
bool stretch_time_scale = true;
double start_offset = 0.0;
uint64_t last_version = 0;
bool skip = false;
@ -50,10 +55,13 @@ public:
void get_parameter_list(List<PropertyInfo> *r_list) const override;
virtual NodeTimeInfo get_node_time_info() const override; // Wrapper of get_parameter().
static Vector<String> (*get_editable_animation_list)();
virtual String get_caption() const override;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
void set_animation(const StringName &p_name);
StringName get_animation() const;
@ -64,6 +72,21 @@ public:
void set_backward(bool p_backward);
bool is_backward() const;
void set_use_custom_timeline(bool p_use_custom_timeline);
bool is_using_custom_timeline() const;
void set_timeline_length(double p_length);
double get_timeline_length() const;
void set_stretch_time_scale(bool p_strech_time_scale);
bool is_stretching_time_scale() const;
void set_start_offset(double p_offset);
double get_start_offset() const;
void set_loop_mode(Animation::LoopMode p_loop_mode);
Animation::LoopMode get_loop_mode() const;
AnimationNodeAnimation();
protected:
@ -118,12 +141,12 @@ private:
double auto_restart_delay = 1.0;
double auto_restart_random_delay = 0.0;
MixMode mix = MIX_MODE_BLEND;
bool break_loop_at_end = false;
StringName request = PNAME("request");
StringName active = PNAME("active");
StringName internal_active = PNAME("internal_active");
StringName time = "time";
StringName remaining = "remaining";
StringName fade_in_remaining = "fade_in_remaining";
StringName fade_out_remaining = "fade_out_remaining";
StringName time_to_restart = "time_to_restart";
@ -160,8 +183,11 @@ public:
void set_mix_mode(MixMode p_mix);
MixMode get_mix_mode() const;
void set_break_loop_at_end(bool p_enable);
bool is_loop_broken_at_end() const;
virtual bool has_filter() const override;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeOneShot();
};
@ -184,7 +210,7 @@ public:
virtual String get_caption() const override;
virtual bool has_filter() const override;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeAdd2();
};
@ -204,7 +230,7 @@ public:
virtual String get_caption() const override;
virtual bool has_filter() const override;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeAdd3();
};
@ -222,7 +248,7 @@ public:
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
virtual String get_caption() const override;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual bool has_filter() const override;
AnimationNodeBlend2();
@ -242,7 +268,7 @@ public:
virtual String get_caption() const override;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeBlend3();
};
@ -261,7 +287,7 @@ public:
virtual String get_caption() const override;
virtual bool has_filter() const override;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeSub2();
};
@ -280,7 +306,7 @@ public:
virtual String get_caption() const override;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeTimeScale();
};
@ -299,7 +325,7 @@ public:
virtual String get_caption() const override;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeTimeSeek();
};
@ -309,11 +335,11 @@ class AnimationNodeTransition : public AnimationNodeSync {
struct InputData {
bool auto_advance = false;
bool break_loop_at_end = false;
bool reset = true;
};
Vector<InputData> input_data;
StringName time = "time";
StringName prev_xfading = "prev_xfading";
StringName prev_index = "prev_index";
StringName current_index = PNAME("current_index");
@ -351,6 +377,9 @@ public:
void set_input_as_auto_advance(int p_input, bool p_enable);
bool is_input_set_as_auto_advance(int p_input) const;
void set_input_break_loop_at_end(int p_input, bool p_enable);
bool is_input_loop_broken_at_end(int p_input) const;
void set_input_reset(int p_input, bool p_enable);
bool is_input_reset(int p_input) const;
@ -363,7 +392,7 @@ public:
void set_allow_transition_to_self(bool p_enable);
bool is_allow_transition_to_self() const;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeTransition();
};
@ -373,7 +402,7 @@ class AnimationNodeOutput : public AnimationNode {
public:
virtual String get_caption() const override;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeOutput();
};
@ -445,7 +474,7 @@ public:
void get_node_connections(List<NodeConnection> *r_connections) const;
virtual String get_caption() const override;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
void get_node_list(List<StringName> *r_list);

View file

@ -101,12 +101,22 @@ float AnimationNodeStateMachineTransition::get_xfade_time() const {
void AnimationNodeStateMachineTransition::set_xfade_curve(const Ref<Curve> &p_curve) {
xfade_curve = p_curve;
emit_changed();
}
Ref<Curve> AnimationNodeStateMachineTransition::get_xfade_curve() const {
return xfade_curve;
}
void AnimationNodeStateMachineTransition::set_break_loop_at_end(bool p_enable) {
break_loop_at_end = p_enable;
emit_changed();
}
bool AnimationNodeStateMachineTransition::is_loop_broken_at_end() const {
return break_loop_at_end;
}
void AnimationNodeStateMachineTransition::set_reset(bool p_reset) {
reset = p_reset;
emit_changed();
@ -141,6 +151,9 @@ void AnimationNodeStateMachineTransition::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_xfade_curve", "curve"), &AnimationNodeStateMachineTransition::set_xfade_curve);
ClassDB::bind_method(D_METHOD("get_xfade_curve"), &AnimationNodeStateMachineTransition::get_xfade_curve);
ClassDB::bind_method(D_METHOD("set_break_loop_at_end", "enable"), &AnimationNodeStateMachineTransition::set_break_loop_at_end);
ClassDB::bind_method(D_METHOD("is_loop_broken_at_end"), &AnimationNodeStateMachineTransition::is_loop_broken_at_end);
ClassDB::bind_method(D_METHOD("set_reset", "reset"), &AnimationNodeStateMachineTransition::set_reset);
ClassDB::bind_method(D_METHOD("is_reset"), &AnimationNodeStateMachineTransition::is_reset);
@ -153,6 +166,7 @@ void AnimationNodeStateMachineTransition::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01,suffix:s"), "set_xfade_time", "get_xfade_time");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "xfade_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_xfade_curve", "get_xfade_curve");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "break_loop_at_end"), "set_break_loop_at_end", "is_loop_broken_at_end");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset"), "set_reset", "is_reset");
ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority");
@ -310,19 +324,19 @@ TypedArray<StringName> AnimationNodeStateMachinePlayback::_get_travel_path() con
}
float AnimationNodeStateMachinePlayback::get_current_play_pos() const {
return pos_current;
return current_nti.position;
}
float AnimationNodeStateMachinePlayback::get_current_length() const {
return len_current;
return current_nti.length;
}
float AnimationNodeStateMachinePlayback::get_fade_from_play_pos() const {
return pos_fade_from;
return fadeing_from_nti.position;
}
float AnimationNodeStateMachinePlayback::get_fade_from_length() const {
return len_fade_from;
return fadeing_from_nti.length;
}
float AnimationNodeStateMachinePlayback::get_fading_time() const {
@ -665,21 +679,22 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree,
}
}
double AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double rem = _process(p_base_path, p_state_machine, p_playback_info, p_test_only);
AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo nti = _process(p_base_path, p_state_machine, p_playback_info, p_test_only);
start_request = StringName();
next_request = false;
stop_request = false;
reset_request_on_teleport = false;
return rem;
return nti;
}
double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
_set_base_path(p_base_path);
AnimationTree *tree = p_state_machine->process_state->tree;
double p_time = p_playback_info.time;
double p_delta = p_playback_info.delta;
bool p_seek = p_playback_info.seeked;
bool p_is_external_seeking = p_playback_info.is_external_seeking;
@ -690,8 +705,8 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
if (p_state_machine->get_state_machine_type() != AnimationNodeStateMachine::STATE_MACHINE_TYPE_GROUPED) {
path.clear();
_clear_path_children(tree, p_state_machine, p_test_only);
_start(p_state_machine);
}
_start(p_state_machine);
reset_request = true;
} else {
// Reset current state.
@ -705,11 +720,11 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
travel_request = StringName();
path.clear();
playing = false;
return 0;
return AnimationNode::NodeTimeInfo();
}
if (!playing && start_request != StringName() && travel_request != StringName()) {
return 0;
return AnimationNode::NodeTimeInfo();
}
// Process start/travel request.
@ -732,7 +747,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
_start(p_state_machine);
} else {
StringName node = start_request;
ERR_FAIL_V_MSG(0, "No such node: '" + node + "'");
ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + node + "'");
}
}
@ -766,7 +781,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
teleport_request = true;
}
} else {
ERR_FAIL_V_MSG(0, "No such node: '" + temp_travel_request + "'");
ERR_FAIL_V_MSG(AnimationNode::NodeTimeInfo(), "No such node: '" + temp_travel_request + "'");
}
}
}
@ -777,16 +792,14 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
teleport_request = false;
// Clear fadeing on teleport.
fading_from = StringName();
fadeing_from_nti = AnimationNode::NodeTimeInfo();
fading_pos = 0;
// Init current length.
pos_current = 0; // Overwritten suddenly in main process.
pi.time = 0;
pi.seeked = true;
pi.is_external_seeking = false;
pi.weight = 0;
len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true);
current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true);
// Don't process first node if not necessary, insteads process next node.
_transition_to_next_recursive(tree, p_state_machine, p_test_only);
}
@ -795,7 +808,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
if (!p_state_machine->states.has(current)) {
playing = false; // Current does not exist.
_set_current(p_state_machine, StringName());
return 0;
return AnimationNode::NodeTimeInfo();
}
// Special case for grouped state machine Start/End to make priority with parent blend (means don't treat Start and End states as RESET animations).
@ -813,7 +826,7 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
fading_from = StringName();
} else {
if (!p_seek) {
fading_pos += p_time;
fading_pos += Math::abs(p_delta);
}
fade_blend = MIN(1.0, fading_pos / fading_time);
}
@ -829,18 +842,14 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
}
// Main process.
double rem = 0.0;
pi = p_playback_info;
pi.weight = fade_blend;
if (reset_request) {
reset_request = false;
pi.time = 0;
pi.seeked = true;
len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only);
rem = len_current;
} else {
rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
}
current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
// Cross-fade process.
if (fading_from != StringName()) {
@ -852,7 +861,6 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
fade_blend_inv = 1.0;
}
float fading_from_rem = 0.0;
pi = p_playback_info;
pi.weight = fade_blend_inv;
if (_reset_request_for_fading_from) {
@ -860,57 +868,41 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
pi.time = 0;
pi.seeked = true;
}
fading_from_rem = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
// Guess playback position.
if (fading_from_rem > len_fade_from) { /// Weird but ok.
len_fade_from = fading_from_rem;
}
pos_fade_from = len_fade_from - fading_from_rem;
fadeing_from_nti = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
if (fading_pos >= fading_time) {
fading_from = StringName(); // Finish fading.
// Finish fading.
fading_from = StringName();
fadeing_from_nti = AnimationNode::NodeTimeInfo();
}
}
// Guess playback position.
if (rem > len_current) { // Weird but ok.
len_current = rem;
}
pos_current = len_current - rem;
// Find next and see when to transition.
_transition_to_next_recursive(tree, p_state_machine, p_test_only);
// Predict remaining time.
double remain = rem; // If we can't predict the end of state machine, the time remaining must be INFINITY.
if (p_state_machine->get_state_machine_type() == AnimationNodeStateMachine::STATE_MACHINE_TYPE_NESTED) {
// There is no next transition.
if (!p_state_machine->has_transition_from(current)) {
if (fading_from != StringName()) {
remain = MAX(rem, fading_time - fading_pos);
} else {
remain = rem;
return current_nti.get_remain() > fadeing_from_nti.get_remain() ? current_nti : fadeing_from_nti;
}
return remain;
return current_nti;
}
}
if (current == p_state_machine->end_node) {
if (fading_from != StringName()) {
remain = MAX(0, fading_time - fading_pos);
} else {
remain = 0;
if (fading_from != StringName() && fadeing_from_nti.get_remain() > 0) {
return fadeing_from_nti;
}
return remain;
return AnimationNode::NodeTimeInfo();
}
if (!is_end()) {
return HUGE_LENGTH;
current_nti.is_infinity = true;
}
return remain;
return current_nti;
}
bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only) {
@ -952,6 +944,7 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only);
}
fading_from = StringName();
fadeing_from_nti = AnimationNode::NodeTimeInfo();
fading_time = 0;
fading_pos = 0;
}
@ -968,11 +961,10 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
_reset_request_for_fading_from = reset_request; // To avoid processing doubly, it must be reset in the fading process within _process().
reset_request = next.is_reset;
pos_fade_from = pos_current;
len_fade_from = len_current;
fadeing_from_nti = current_nti;
if (next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) {
pi.time = MIN(pos_current, len_current);
pi.time = current_nti.position;
pi.seeked = true;
pi.is_external_seeking = false;
pi.weight = 0;
@ -980,24 +972,11 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
}
// Just get length to find next recursive.
double rem = 0.0;
pi.time = 0;
pi.is_external_seeking = false;
pi.weight = 0;
if (next.is_reset) {
pi.seeked = true;
len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process.
rem = len_current;
} else {
pi.seeked = false;
rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process.
}
// Guess playback position.
if (rem > len_current) { // Weird but ok.
len_current = rem;
}
pos_current = len_current - rem;
pi.seeked = next.is_reset;
current_nti = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process.
// Fading must be processed.
if (fading_time) {
@ -1028,6 +1007,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p
playback->_next_main();
// Then, fadeing should be end.
fading_from = StringName();
fadeing_from_nti = AnimationNode::NodeTimeInfo();
fading_pos = 0;
} else {
return true;
@ -1039,7 +1019,7 @@ bool AnimationNodeStateMachinePlayback::_can_transition_to_next(AnimationTree *p
}
if (current != p_state_machine->start_node && p_next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_AT_END) {
return pos_current >= len_current - p_next.xfade;
return current_nti.get_remain(p_next.break_loop_at_end) <= p_next.xfade;
}
return true;
}
@ -1084,6 +1064,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_
next.curve = ref_transition->get_xfade_curve();
next.switch_mode = ref_transition->get_switch_mode();
next.is_reset = ref_transition->is_reset();
next.break_loop_at_end = ref_transition->is_loop_broken_at_end();
}
}
} else {
@ -1113,6 +1094,7 @@ AnimationNodeStateMachinePlayback::NextInfo AnimationNodeStateMachinePlayback::_
next.curve = ref_transition->get_xfade_curve();
next.switch_mode = ref_transition->get_switch_mode();
next.is_reset = ref_transition->is_reset();
next.break_loop_at_end = ref_transition->is_loop_broken_at_end();
}
}
@ -1233,6 +1215,7 @@ AnimationNodeStateMachinePlayback::AnimationNodeStateMachinePlayback() {
///////////////////////////////////////////////////////
void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) const {
AnimationNode::get_parameter_list(r_list);
r_list->push_back(PropertyInfo(Variant::OBJECT, playback, PROPERTY_HINT_RESOURCE_TYPE, "AnimationNodeStateMachinePlayback", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ALWAYS_DUPLICATE)); // Don't store this object in .tres, it always needs to be made as unique object.
List<StringName> advance_conditions;
for (int i = 0; i < transitions.size(); i++) {
@ -1249,6 +1232,11 @@ void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) c
}
Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName &p_parameter) const {
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
if (ret != Variant()) {
return ret;
}
if (p_parameter == playback) {
Ref<AnimationNodeStateMachinePlayback> p;
p.instantiate();
@ -1259,6 +1247,10 @@ Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName
}
bool AnimationNodeStateMachine::is_parameter_read_only(const StringName &p_parameter) const {
if (AnimationNode::is_parameter_read_only(p_parameter)) {
return true;
}
if (p_parameter == playback) {
return true;
}
@ -1622,9 +1614,9 @@ Vector2 AnimationNodeStateMachine::get_graph_offset() const {
return graph_offset;
}
double AnimationNodeStateMachine::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNodeStateMachine::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
Ref<AnimationNodeStateMachinePlayback> playback_new = get_parameter(playback);
ERR_FAIL_COND_V(playback_new.is_null(), 0.0);
ERR_FAIL_COND_V(playback_new.is_null(), AnimationNode::NodeTimeInfo());
playback_new->_set_grouped(state_machine_type == STATE_MACHINE_TYPE_GROUPED);
if (p_test_only) {
playback_new = playback_new->duplicate(); // Don't process original when testing.

View file

@ -57,6 +57,7 @@ private:
StringName advance_condition_name;
float xfade_time = 0.0;
Ref<Curve> xfade_curve;
bool break_loop_at_end = false;
bool reset = true;
int priority = 1;
String advance_expression;
@ -85,6 +86,9 @@ public:
void set_xfade_time(float p_xfade);
float get_xfade_time() const;
void set_break_loop_at_end(bool p_enable);
bool is_loop_broken_at_end() const;
void set_reset(bool p_reset);
bool is_reset() const;
@ -210,7 +214,7 @@ public:
void set_graph_offset(const Vector2 &p_offset);
Vector2 get_graph_offset() const;
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual String get_caption() const override;
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
@ -246,6 +250,7 @@ class AnimationNodeStateMachinePlayback : public Resource {
Ref<Curve> curve;
AnimationNodeStateMachineTransition::SwitchMode switch_mode;
bool is_reset;
bool break_loop_at_end;
};
struct ChildStateMachineInfo {
@ -257,18 +262,14 @@ class AnimationNodeStateMachinePlayback : public Resource {
Ref<AnimationNodeStateMachineTransition> default_transition;
String base_path;
double len_fade_from = 0.0;
double pos_fade_from = 0.0;
double len_current = 0.0;
double pos_current = 0.0;
AnimationNode::NodeTimeInfo current_nti;
StringName current;
Ref<Curve> current_curve;
Ref<AnimationNodeStateMachineTransition> group_start_transition;
Ref<AnimationNodeStateMachineTransition> group_end_transition;
AnimationNode::NodeTimeInfo fadeing_from_nti;
StringName fading_from;
float fading_time = 0.0;
float fading_pos = 0.0;
@ -301,8 +302,8 @@ class AnimationNodeStateMachinePlayback : public Resource {
bool _travel_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only);
void _start_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_test_only);
double process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only);
double _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only);
AnimationNode::NodeTimeInfo process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only);
AnimationNode::NodeTimeInfo _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only);
bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const;
bool _transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only);

View file

@ -46,6 +46,10 @@ void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const {
r_list->push_back(PropertyInfo::from_dict(d));
}
}
r_list->push_back(PropertyInfo(Variant::FLOAT, current_length, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY));
r_list->push_back(PropertyInfo(Variant::FLOAT, current_position, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY));
r_list->push_back(PropertyInfo(Variant::FLOAT, current_delta, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY));
}
Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter) const {
@ -56,8 +60,15 @@ Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter
bool AnimationNode::is_parameter_read_only(const StringName &p_parameter) const {
bool ret = false;
GDVIRTUAL_CALL(_is_parameter_read_only, p_parameter, ret);
return ret;
if (GDVIRTUAL_CALL(_is_parameter_read_only, p_parameter, ret) && ret) {
return true;
}
if (p_parameter == current_length || p_parameter == current_position || p_parameter == current_delta) {
return true;
}
return false;
}
void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_value) {
@ -81,6 +92,20 @@ Variant AnimationNode::get_parameter(const StringName &p_name) const {
return process_state->tree->property_map[path].first;
}
void AnimationNode::set_node_time_info(const NodeTimeInfo &p_node_time_info) {
set_parameter(current_length, p_node_time_info.length);
set_parameter(current_position, p_node_time_info.position);
set_parameter(current_delta, p_node_time_info.delta);
}
AnimationNode::NodeTimeInfo AnimationNode::get_node_time_info() const {
NodeTimeInfo nti;
nti.length = get_parameter(current_length);
nti.position = get_parameter(current_position);
nti.delta = get_parameter(current_delta);
return nti;
}
void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) {
Dictionary cn;
if (GDVIRTUAL_CALL(_get_child_nodes, cn)) {
@ -101,11 +126,11 @@ void AnimationNode::blend_animation(const StringName &p_animation, AnimationMixe
process_state->tree->make_animation_instance(p_animation, p_playback_info);
}
double AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
process_state = p_process_state;
double t = process(p_playback_info, p_test_only);
NodeTimeInfo nti = process(p_playback_info, p_test_only);
process_state = nullptr;
return t;
return nti;
}
void AnimationNode::make_invalid(const String &p_reason) {
@ -122,11 +147,11 @@ AnimationTree *AnimationNode::get_animation_tree() const {
return process_state->tree;
}
double AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) {
ERR_FAIL_INDEX_V(p_input, inputs.size(), 0);
AnimationNode::NodeTimeInfo AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) {
ERR_FAIL_INDEX_V(p_input, inputs.size(), NodeTimeInfo());
AnimationNodeBlendTree *blend_tree = Object::cast_to<AnimationNodeBlendTree>(node_state.parent);
ERR_FAIL_NULL_V(blend_tree, 0);
ERR_FAIL_NULL_V(blend_tree, NodeTimeInfo());
// Update connections.
StringName current_name = blend_tree->get_node_name(Ref<AnimationNode>(this));
@ -136,32 +161,31 @@ double AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_pl
StringName node_name = node_state.connections[p_input];
if (!blend_tree->has_node(node_name)) {
make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), current_name));
return 0;
return NodeTimeInfo();
}
Ref<AnimationNode> node = blend_tree->get_node(node_name);
ERR_FAIL_COND_V(node.is_null(), 0);
ERR_FAIL_COND_V(node.is_null(), NodeTimeInfo());
real_t activity = 0.0;
Vector<AnimationTree::Activity> *activity_ptr = process_state->tree->input_activity_map.getptr(node_state.base_path);
double ret = _blend_node(node, node_name, nullptr, p_playback_info, p_filter, p_sync, p_test_only, &activity);
NodeTimeInfo nti = _blend_node(node, node_name, nullptr, p_playback_info, p_filter, p_sync, p_test_only, &activity);
if (activity_ptr && p_input < activity_ptr->size()) {
activity_ptr->write[p_input].last_pass = process_state->last_pass;
activity_ptr->write[p_input].activity = activity;
}
return ret;
return nti;
}
double AnimationNode::blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) {
ERR_FAIL_COND_V(p_node.is_null(), 0);
AnimationNode::NodeTimeInfo AnimationNode::blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) {
ERR_FAIL_COND_V(p_node.is_null(), NodeTimeInfo());
p_node->node_state.connections.clear();
return _blend_node(p_node, p_subpath, this, p_playback_info, p_filter, p_sync, p_test_only, nullptr);
}
double AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only, real_t *r_activity) {
ERR_FAIL_NULL_V(process_state, 0);
AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only, real_t *r_activity) {
ERR_FAIL_NULL_V(process_state, NodeTimeInfo());
int blend_count = node_state.track_weights.size();
@ -261,7 +285,7 @@ double AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p
new_parent = p_new_parent;
new_path = String(node_state.base_path) + String(p_subpath) + "/";
} else {
ERR_FAIL_NULL_V(node_state.parent, 0);
ERR_FAIL_NULL_V(node_state.parent, NodeTimeInfo());
new_parent = node_state.parent;
new_path = String(new_parent->node_state.base_path) + String(p_subpath) + "/";
}
@ -271,7 +295,7 @@ double AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p
p_node->node_state.base_path = new_path;
p_node->node_state.parent = new_parent;
if (!p_playback_info.seeked && !p_sync && !any_valid) {
p_playback_info.time = 0.0;
p_playback_info.delta = 0.0;
return p_node->_pre_process(process_state, p_playback_info, p_test_only);
}
return p_node->_pre_process(process_state, p_playback_info, p_test_only);
@ -328,15 +352,31 @@ int AnimationNode::find_input(const String &p_name) const {
return idx;
}
double AnimationNode::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
AnimationNode::NodeTimeInfo AnimationNode::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
process_state->is_testing = p_test_only;
return _process(p_playback_info, p_test_only);
AnimationMixer::PlaybackInfo pi = p_playback_info;
if (p_playback_info.seeked) {
pi.delta = get_node_time_info().position - p_playback_info.time;
} else {
pi.time = get_node_time_info().position + p_playback_info.delta;
}
NodeTimeInfo nti = _process(pi, p_test_only);
if (!p_test_only) {
set_node_time_info(nti);
}
return nti;
}
double AnimationNode::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double ret = 0;
GDVIRTUAL_CALL(_process, p_playback_info.time, p_playback_info.seeked, p_playback_info.is_external_seeking, p_test_only, ret);
return ret;
AnimationNode::NodeTimeInfo AnimationNode::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double r_ret = 0.0;
GDVIRTUAL_CALL(_process, p_playback_info.time, p_playback_info.seeked, p_playback_info.is_external_seeking, p_test_only, r_ret);
NodeTimeInfo nti;
nti.delta = r_ret;
return nti;
}
void AnimationNode::set_filter_path(const NodePath &p_path, bool p_enable) {
@ -432,7 +472,8 @@ double AnimationNode::blend_node_ex(const StringName &p_sub_path, Ref<AnimationN
info.seeked = p_seek;
info.is_external_seeking = p_is_external_seeking;
info.weight = p_blend;
return blend_node(p_node, p_sub_path, info, p_filter, p_sync, p_test_only);
NodeTimeInfo nti = blend_node(p_node, p_sub_path, info, p_filter, p_sync, p_test_only);
return nti.length - nti.position;
}
double AnimationNode::blend_input_ex(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) {
@ -441,7 +482,8 @@ double AnimationNode::blend_input_ex(int p_input, double p_time, bool p_seek, bo
info.seeked = p_seek;
info.is_external_seeking = p_is_external_seeking;
info.weight = p_blend;
return blend_input(p_input, info, p_filter, p_sync, p_test_only);
NodeTimeInfo nti = blend_input(p_input, info, p_filter, p_sync, p_test_only);
return nti.length - nti.position;
}
void AnimationNode::_bind_methods() {
@ -568,11 +610,12 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const
if (started) {
// If started, seek.
pi.seeked = true;
pi.delta = p_delta;
root_animation_node->_pre_process(&process_state, pi, false);
started = false;
} else {
pi.seeked = false;
pi.time = p_delta;
pi.delta = p_delta;
root_animation_node->_pre_process(&process_state, pi, false);
}
}

View file

@ -63,6 +63,34 @@ public:
HashMap<NodePath, bool> filter;
bool filter_enabled = false;
// To propagate information from upstream for use in estimation of playback progress.
// These values must be taken from the result of blend_node() or blend_input() and must be essentially read-only.
// For example, if you want to change the position, you need to change the pi.time value of PlaybackInfo passed to blend_input(pi) and get the result.
struct NodeTimeInfo {
// Retain the previous frame values. These are stored into the AnimationTree's Map and exposing them as read-only values.
double length = 0.0;
double position = 0.0;
double delta = 0.0;
// Needs internally to estimate remain time, the previous frame values are not retained.
Animation::LoopMode loop_mode = Animation::LOOP_NONE;
bool is_just_looped = false; // For breaking loop, it is true when just looped.
bool is_infinity = false; // For unpredictable state machine's end.
bool is_looping() {
return loop_mode != Animation::LOOP_NONE;
}
double get_remain(bool p_break_loop = false) {
if ((is_looping() && !p_break_loop) || is_infinity) {
return HUGE_LENGTH;
}
if (p_break_loop && is_just_looped) {
return 0;
}
return length - position;
}
};
// Temporary state for blending process which needs to be stored in each AnimationNodes.
struct NodeState {
StringName base_path;
@ -84,16 +112,23 @@ public:
Array _get_filters() const;
void _set_filters(const Array &p_filters);
friend class AnimationNodeBlendTree;
double _blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr);
double _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false);
// The time information is passed from upstream to downstream by AnimationMixer::PlaybackInfo::p_playback_info until AnimationNodeAnimation processes it.
// Conversely, AnimationNodeAnimation returns the processed result as NodeTimeInfo from downstream to upstream.
NodeTimeInfo _blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr);
NodeTimeInfo _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false);
protected:
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false);
double process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false);
StringName current_length = "current_length";
StringName current_position = "current_position";
StringName current_delta = "current_delta";
virtual NodeTimeInfo process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); // To organize time information. Virtualizing for especially AnimationNodeAnimation needs to take "backward" into account.
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); // Main process.
void blend_animation(const StringName &p_animation, AnimationMixer::PlaybackInfo p_playback_info);
double blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
double blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
NodeTimeInfo blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
NodeTimeInfo blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
// Bind-able methods to expose for compatibility, moreover AnimationMixer::PlaybackInfo is not exposed.
void blend_animation_ex(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE);
@ -124,6 +159,9 @@ public:
void set_parameter(const StringName &p_name, const Variant &p_value);
Variant get_parameter(const StringName &p_name) const;
void set_node_time_info(const NodeTimeInfo &p_node_time_info); // Wrapper of set_parameter().
virtual NodeTimeInfo get_node_time_info() const; // Wrapper of get_parameter().
struct ChildNode {
StringName name;
Ref<AnimationNode> node;

View file

@ -75,6 +75,7 @@ public:
LOOP_PINGPONG,
};
// LoopedFlag is used in Animataion to "process the keys at both ends correct".
enum LoopedFlag {
LOOPED_FLAG_NONE,
LOOPED_FLAG_END,
@ -187,6 +188,7 @@ private:
};
/* BEZIER TRACK */
struct BezierKey {
Vector2 in_handle; // Relative (x always <0)
Vector2 out_handle; // Relative (x always >0)
@ -223,7 +225,7 @@ private:
}
};
/* AUDIO TRACK */
/* ANIMATION TRACK */
struct AnimationTrack : public Track {
Vector<TKey<StringName>> values;