diff options
Diffstat (limited to 'scene/animation/animation_blend_tree.cpp')
-rw-r--r-- | scene/animation/animation_blend_tree.cpp | 530 |
1 files changed, 312 insertions, 218 deletions
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 99f17a1eef..a00b1d8ee1 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* animation_blend_tree.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* animation_blend_tree.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "animation_blend_tree.h" @@ -64,17 +64,17 @@ void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const } } -double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_seek_root) { +double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_external_seeking) { AnimationPlayer *ap = state->player; ERR_FAIL_COND_V(!ap, 0); - double time = get_parameter(this->time); + double cur_time = get_parameter(time); if (!ap->has_animation(animation)) { AnimationNodeBlendTree *tree = Object::cast_to<AnimationNodeBlendTree>(parent); if (tree) { - String name = tree->get_node_name(Ref<AnimationNodeAnimation>(this)); - make_invalid(vformat(RTR("On BlendTree node '%s', animation not found: '%s'"), name, animation)); + String node_name = tree->get_node_name(Ref<AnimationNodeAnimation>(this)); + make_invalid(vformat(RTR("On BlendTree node '%s', animation not found: '%s'"), node_name, animation)); } else { make_invalid(vformat(RTR("Animation not found: '%s'"), animation)); @@ -86,58 +86,87 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_seek_r Ref<Animation> anim = ap->get_animation(animation); double anim_size = (double)anim->get_length(); double step = 0.0; - double prev_time = time; - int pingponged = 0; - bool current_backward = signbit(p_time); + double prev_time = cur_time; + Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE; + bool node_backward = play_mode == PLAY_MODE_BACKWARD; if (p_seek) { - step = p_time - time; - time = p_time; + step = p_time - cur_time; + cur_time = p_time; } else { p_time *= backward ? -1.0 : 1.0; - if (!(time == anim_size && !current_backward) && !(time == 0 && current_backward)) { - time = time + p_time; - step = p_time; - } + cur_time = cur_time + p_time; + step = p_time; } if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) { if (!Math::is_zero_approx(anim_size)) { - if ((int)Math::floor(abs(time - prev_time) / anim_size) % 2 == 0) { - if (prev_time >= 0 && time < 0) { - backward = !backward; - pingponged = -1; + if (prev_time >= 0 && cur_time < 0) { + backward = !backward; + looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START; + } + if (prev_time <= anim_size && cur_time > anim_size) { + backward = !backward; + looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END; + } + cur_time = Math::pingpong(cur_time, anim_size); + } + } 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; + } else { + if (cur_time < 0) { + step += cur_time; + cur_time = 0; + } else if (cur_time > anim_size) { + step += anim_size - cur_time; + cur_time = anim_size; + } + backward = false; + + // If ended, don't progress animation. So set delta to 0. + if (p_time > 0) { + if (play_mode == PLAY_MODE_FORWARD) { + if (prev_time >= anim_size) { + step = 0; } - if (prev_time <= anim_size && time > anim_size) { - backward = !backward; - pingponged = 1; + } else { + if (prev_time <= 0) { + step = 0; } } - time = Math::pingpong(time, anim_size); } - } else { - if (anim->get_loop_mode() == Animation::LOOP_LINEAR) { - if (!Math::is_zero_approx(anim_size)) { - time = Math::fposmod(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 prosessed. + if (state->tree) { + // 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) { + state->tree->call_deferred(SNAME("emit_signal"), "animation_started", animation); + } + // Finished. + if (prev_time < anim_size && cur_time >= anim_size) { + state->tree->call_deferred(SNAME("emit_signal"), "animation_finished", animation); } - } else if (time < 0) { - step += time; - time = 0; - } else if (time > anim_size) { - step += anim_size - time; - time = anim_size; } - backward = false; } if (play_mode == PLAY_MODE_FORWARD) { - blend_animation(animation, time, step, p_seek, p_seek_root, 1.0, pingponged); + blend_animation(animation, cur_time, step, p_seek, p_is_external_seeking, 1.0, looped_flag); } else { - blend_animation(animation, anim_size - time, -step, p_seek, p_seek_root, 1.0, pingponged); + blend_animation(animation, anim_size - cur_time, -step, p_seek, p_is_external_seeking, 1.0, looped_flag); } - set_parameter(this->time, time); + set_parameter(time, cur_time); - return anim_size - time; + return anim_size - cur_time; } String AnimationNodeAnimation::get_caption() const { @@ -200,15 +229,17 @@ AnimationNodeSync::AnimationNodeSync() { //////////////////////////////////////////////////////// void AnimationNodeOneShot::get_parameter_list(List<PropertyInfo> *r_list) const { - r_list->push_back(PropertyInfo(Variant::BOOL, active)); - r_list->push_back(PropertyInfo(Variant::BOOL, prev_active, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); + 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::INT, request, PROPERTY_HINT_ENUM, ",Fire,Abort")); 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, time_to_restart, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); } Variant AnimationNodeOneShot::get_parameter_default_value(const StringName &p_parameter) const { - if (p_parameter == active || p_parameter == prev_active) { + if (p_parameter == request) { + return ONE_SHOT_REQUEST_NONE; + } else if (p_parameter == active) { return false; } else if (p_parameter == time_to_restart) { return -1; @@ -217,19 +248,26 @@ Variant AnimationNodeOneShot::get_parameter_default_value(const StringName &p_pa } } -void AnimationNodeOneShot::set_fadein_time(float p_time) { +bool AnimationNodeOneShot::is_parameter_read_only(const StringName &p_parameter) const { + if (p_parameter == active) { + return true; + } + return false; +} + +void AnimationNodeOneShot::set_fadein_time(double p_time) { fade_in = p_time; } -void AnimationNodeOneShot::set_fadeout_time(float p_time) { +void AnimationNodeOneShot::set_fadeout_time(double p_time) { fade_out = p_time; } -float AnimationNodeOneShot::get_fadein_time() const { +double AnimationNodeOneShot::get_fadein_time() const { return fade_in; } -float AnimationNodeOneShot::get_fadeout_time() const { +double AnimationNodeOneShot::get_fadeout_time() const { return fade_out; } @@ -237,11 +275,11 @@ void AnimationNodeOneShot::set_autorestart(bool p_active) { autorestart = p_active; } -void AnimationNodeOneShot::set_autorestart_delay(float p_time) { +void AnimationNodeOneShot::set_autorestart_delay(double p_time) { autorestart_delay = p_time; } -void AnimationNodeOneShot::set_autorestart_random_delay(float p_time) { +void AnimationNodeOneShot::set_autorestart_random_delay(double p_time) { autorestart_random_delay = p_time; } @@ -249,11 +287,11 @@ bool AnimationNodeOneShot::has_autorestart() const { return autorestart; } -float AnimationNodeOneShot::get_autorestart_delay() const { +double AnimationNodeOneShot::get_autorestart_delay() const { return autorestart_delay; } -float AnimationNodeOneShot::get_autorestart_random_delay() const { +double AnimationNodeOneShot::get_autorestart_random_delay() const { return autorestart_random_delay; } @@ -273,57 +311,56 @@ bool AnimationNodeOneShot::has_filter() const { return true; } -double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_seek_root) { - bool active = get_parameter(this->active); - bool prev_active = get_parameter(this->prev_active); - double time = get_parameter(this->time); - double remaining = get_parameter(this->remaining); - double time_to_restart = get_parameter(this->time_to_restart); - - if (!active) { - //make it as if this node doesn't exist, pass input 0 by. - if (prev_active) { - set_parameter(this->prev_active, false); - } - if (time_to_restart >= 0.0 && !p_seek) { - time_to_restart -= p_time; - if (time_to_restart < 0) { - //restart - set_parameter(this->active, true); - active = true; +double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_external_seeking) { + OneShotRequest cur_request = static_cast<OneShotRequest>((int)get_parameter(request)); + bool cur_active = get_parameter(active); + double cur_time = get_parameter(time); + double cur_remaining = get_parameter(remaining); + double cur_time_to_restart = get_parameter(time_to_restart); + + set_parameter(request, ONE_SHOT_REQUEST_NONE); + + bool do_start = cur_request == ONE_SHOT_REQUEST_FIRE; + if (cur_request == ONE_SHOT_REQUEST_ABORT) { + set_parameter(active, false); + set_parameter(time_to_restart, -1); + return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); + } else if (!do_start && !cur_active) { + if (cur_time_to_restart >= 0.0 && !p_seek) { + cur_time_to_restart -= p_time; + if (cur_time_to_restart < 0) { + do_start = true; // Restart. } - set_parameter(this->time_to_restart, time_to_restart); + set_parameter(time_to_restart, cur_time_to_restart); } - - if (!active) { - return blend_input(0, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, sync); + if (!do_start) { + return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); } } bool os_seek = p_seek; - if (p_seek) { - time = p_time; + cur_time = p_time; } - bool do_start = !prev_active; if (do_start) { - time = 0; + cur_time = 0; os_seek = true; - set_parameter(this->prev_active, true); + set_parameter(request, ONE_SHOT_REQUEST_NONE); + set_parameter(active, true); } - float blend; + real_t blend; - if (time < fade_in) { + if (cur_time < fade_in) { if (fade_in > 0) { - blend = time / fade_in; + blend = cur_time / fade_in; } else { blend = 0; } - } else if (!do_start && remaining <= fade_out) { + } else if (!do_start && cur_remaining <= fade_out) { if (fade_out > 0) { - blend = (remaining / fade_out); + blend = (cur_remaining / fade_out); } else { blend = 0; } @@ -333,34 +370,32 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_seek_roo double main_rem; if (mix == MIX_MODE_ADD) { - main_rem = blend_input(0, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, sync); + main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); } else { - main_rem = blend_input(0, p_time, p_seek, p_seek_root, 1.0 - blend, FILTER_BLEND, sync); + main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - blend, FILTER_BLEND, sync); // Unlike below, processing this edge is a corner case. } - - double os_rem = blend_input(1, os_seek ? time : p_time, os_seek, p_seek_root, blend, FILTER_PASS, true); + double os_rem = blend_input(1, os_seek ? cur_time : p_time, os_seek, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_PASS, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. if (do_start) { - remaining = os_rem; + cur_remaining = os_rem; } if (!p_seek) { - time += p_time; - remaining = os_rem; - if (remaining <= 0) { - set_parameter(this->active, false); - set_parameter(this->prev_active, false); + cur_time += p_time; + cur_remaining = os_rem; + if (cur_remaining <= 0) { + set_parameter(active, false); if (autorestart) { - float restart_sec = autorestart_delay + Math::randf() * autorestart_random_delay; - set_parameter(this->time_to_restart, restart_sec); + double restart_sec = autorestart_delay + Math::randd() * autorestart_random_delay; + set_parameter(time_to_restart, restart_sec); } } } - set_parameter(this->time, time); - set_parameter(this->remaining, remaining); + set_parameter(time, cur_time); + set_parameter(remaining, cur_remaining); - return MAX(main_rem, remaining); + return MAX(main_rem, cur_remaining); } void AnimationNodeOneShot::_bind_methods() { @@ -393,6 +428,10 @@ void AnimationNodeOneShot::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "autorestart_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater,suffix:s"), "set_autorestart_delay", "get_autorestart_delay"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "autorestart_random_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater,suffix:s"), "set_autorestart_random_delay", "get_autorestart_random_delay"); + BIND_ENUM_CONSTANT(ONE_SHOT_REQUEST_NONE); + BIND_ENUM_CONSTANT(ONE_SHOT_REQUEST_FIRE); + BIND_ENUM_CONSTANT(ONE_SHOT_REQUEST_ABORT); + BIND_ENUM_CONSTANT(MIX_MODE_BLEND); BIND_ENUM_CONSTANT(MIX_MODE_ADD); } @@ -420,10 +459,10 @@ bool AnimationNodeAdd2::has_filter() const { return true; } -double AnimationNodeAdd2::process(double p_time, bool p_seek, bool p_seek_root) { +double AnimationNodeAdd2::process(double p_time, bool p_seek, bool p_is_external_seeking) { double amount = get_parameter(add_amount); - double rem0 = blend_input(0, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, sync); - blend_input(1, p_time, p_seek, p_seek_root, amount, FILTER_PASS, sync); + double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); + blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync); return rem0; } @@ -454,11 +493,11 @@ bool AnimationNodeAdd3::has_filter() const { return true; } -double AnimationNodeAdd3::process(double p_time, bool p_seek, bool p_seek_root) { +double AnimationNodeAdd3::process(double p_time, bool p_seek, bool p_is_external_seeking) { double amount = get_parameter(add_amount); - blend_input(0, p_time, p_seek, p_seek_root, MAX(0, -amount), FILTER_PASS, sync); - double rem0 = blend_input(1, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, sync); - blend_input(2, p_time, p_seek, p_seek_root, MAX(0, amount), FILTER_PASS, sync); + blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_PASS, sync); + double rem0 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync); + blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_PASS, sync); return rem0; } @@ -486,11 +525,11 @@ String AnimationNodeBlend2::get_caption() const { return "Blend2"; } -double AnimationNodeBlend2::process(double p_time, bool p_seek, bool p_seek_root) { +double AnimationNodeBlend2::process(double p_time, bool p_seek, bool p_is_external_seeking) { double amount = get_parameter(blend_amount); - double rem0 = blend_input(0, p_time, p_seek, p_seek_root, 1.0 - amount, FILTER_BLEND, sync); - double rem1 = blend_input(1, p_time, p_seek, p_seek_root, amount, FILTER_PASS, sync); + double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - amount, FILTER_BLEND, sync); + double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync); return amount > 0.5 ? rem1 : rem0; //hacky but good enough } @@ -521,11 +560,11 @@ String AnimationNodeBlend3::get_caption() const { return "Blend3"; } -double AnimationNodeBlend3::process(double p_time, bool p_seek, bool p_seek_root) { +double AnimationNodeBlend3::process(double p_time, bool p_seek, bool p_is_external_seeking) { double amount = get_parameter(blend_amount); - double rem0 = blend_input(0, p_time, p_seek, p_seek_root, MAX(0, -amount), FILTER_IGNORE, sync); - double rem1 = blend_input(1, p_time, p_seek, p_seek_root, 1.0 - ABS(amount), FILTER_IGNORE, sync); - double rem2 = blend_input(2, p_time, p_seek, p_seek_root, MAX(0, amount), FILTER_IGNORE, sync); + double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_IGNORE, sync); + double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0 - ABS(amount), FILTER_IGNORE, sync); + double rem2 = blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_IGNORE, sync); return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); //hacky but good enough } @@ -553,12 +592,12 @@ String AnimationNodeTimeScale::get_caption() const { return "TimeScale"; } -double AnimationNodeTimeScale::process(double p_time, bool p_seek, bool p_seek_root) { - double scale = get_parameter(this->scale); +double AnimationNodeTimeScale::process(double p_time, bool p_seek, bool p_is_external_seeking) { + double cur_scale = get_parameter(scale); if (p_seek) { - return blend_input(0, p_time, true, p_seek_root, 1.0, FILTER_IGNORE, true); + return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true); } else { - return blend_input(0, p_time * scale, false, p_seek_root, 1.0, FILTER_IGNORE, true); + return blend_input(0, p_time * cur_scale, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true); } } @@ -583,16 +622,16 @@ String AnimationNodeTimeSeek::get_caption() const { return "Seek"; } -double AnimationNodeTimeSeek::process(double p_time, bool p_seek, bool p_seek_root) { - double seek_pos = get_parameter(this->seek_pos); +double AnimationNodeTimeSeek::process(double p_time, bool p_seek, bool p_is_external_seeking) { + double cur_seek_pos = get_parameter(seek_pos); if (p_seek) { - return blend_input(0, p_time, true, p_seek_root, 1.0, FILTER_IGNORE, true); - } else if (seek_pos >= 0) { - double ret = blend_input(0, seek_pos, true, true, 1.0, FILTER_IGNORE, true); - set_parameter(this->seek_pos, -1.0); //reset + return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + } else if (cur_seek_pos >= 0) { + double ret = blend_input(0, cur_seek_pos, true, true, 1.0, FILTER_IGNORE, true); + set_parameter(seek_pos, -1.0); //reset return ret; } else { - return blend_input(0, p_time, false, p_seek_root, 1.0, FILTER_IGNORE, true); + return blend_input(0, p_time, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true); } } @@ -614,9 +653,10 @@ void AnimationNodeTransition::get_parameter_list(List<PropertyInfo> *r_list) con anims += inputs[i].name; } - r_list->push_back(PropertyInfo(Variant::INT, current, PROPERTY_HINT_ENUM, anims)); - r_list->push_back(PropertyInfo(Variant::INT, prev_current, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); - r_list->push_back(PropertyInfo(Variant::INT, prev, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); + r_list->push_back(PropertyInfo(Variant::STRING, current_state, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY)); // For interface. + 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)); } @@ -624,13 +664,22 @@ void AnimationNodeTransition::get_parameter_list(List<PropertyInfo> *r_list) con Variant AnimationNodeTransition::get_parameter_default_value(const StringName &p_parameter) const { if (p_parameter == time || p_parameter == prev_xfading) { return 0.0; - } else if (p_parameter == prev || p_parameter == prev_current) { + } else if (p_parameter == prev_index) { return -1; + } else if (p_parameter == transition_request || p_parameter == current_state) { + return String(); } else { return 0; } } +bool AnimationNodeTransition::is_parameter_read_only(const StringName &p_parameter) const { + if (p_parameter == current_state || p_parameter == current_index) { + return true; + } + return false; +} + String AnimationNodeTransition::get_caption() const { return "Transition"; } @@ -676,11 +725,22 @@ String AnimationNodeTransition::get_input_caption(int p_input) const { return inputs[p_input].name; } -void AnimationNodeTransition::set_xfade_time(float p_fade) { +int AnimationNodeTransition::find_input_caption(const String &p_name) const { + int idx = -1; + for (int i = 0; i < MAX_INPUTS; i++) { + if (inputs[i].name == p_name) { + idx = i; + break; + } + } + return idx; +} + +void AnimationNodeTransition::set_xfade_time(double p_fade) { xfade_time = p_fade; } -float AnimationNodeTransition::get_xfade_time() const { +double AnimationNodeTransition::get_xfade_time() const { return xfade_time; } @@ -692,89 +752,119 @@ Ref<Curve> AnimationNodeTransition::get_xfade_curve() const { return xfade_curve; } -void AnimationNodeTransition::set_from_start(bool p_from_start) { - from_start = p_from_start; +void AnimationNodeTransition::set_reset(bool p_reset) { + reset = p_reset; } -bool AnimationNodeTransition::is_from_start() const { - return from_start; +bool AnimationNodeTransition::is_reset() const { + return reset; } -double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_seek_root) { - int current = get_parameter(this->current); - int prev = get_parameter(this->prev); - int prev_current = get_parameter(this->prev_current); +double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_external_seeking) { + 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 time = get_parameter(this->time); - double prev_xfading = get_parameter(this->prev_xfading); + double cur_time = get_parameter(time); + double cur_prev_xfading = get_parameter(prev_xfading); - bool switched = current != prev_current; + bool switched = false; + bool restart = false; - if (switched) { - set_parameter(this->prev_current, current); - set_parameter(this->prev, prev_current); + if (!cur_transition_request.is_empty()) { + int new_idx = find_input_caption(cur_transition_request); + if (new_idx >= 0) { + if (cur_current_index == new_idx) { + // Transition to same state. + restart = reset; + cur_prev_xfading = 0; + set_parameter(prev_xfading, 0); + cur_prev_index = -1; + set_parameter(prev_index, -1); + } else { + switched = true; + cur_prev_index = cur_current_index; + set_parameter(prev_index, cur_current_index); + } + cur_current_index = new_idx; + set_parameter(current_index, cur_current_index); + set_parameter(current_state, cur_transition_request); + } else { + ERR_PRINT("No such input: '" + cur_transition_request + "'"); + } + cur_transition_request = String(); + set_parameter(transition_request, cur_transition_request); + } + + // Special case for restart. + if (restart) { + set_parameter(time, 0); + return blend_input(cur_current_index, 0, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true); + } - prev = prev_current; - prev_xfading = xfade_time; - time = 0; - switched = true; + if (switched) { + cur_prev_xfading = xfade_time; + cur_time = 0; } - if (current < 0 || current >= enabled_inputs || prev >= enabled_inputs) { + if (cur_current_index < 0 || cur_current_index >= enabled_inputs || cur_prev_index >= enabled_inputs) { return 0; } double rem = 0.0; - for (int i = 0; i < enabled_inputs; i++) { - if (i != current && i != prev) { - blend_input(i, p_time, p_seek, p_seek_root, 0, FILTER_IGNORE, sync); + if (sync) { + for (int i = 0; i < enabled_inputs; i++) { + if (i != cur_current_index && i != cur_prev_index) { + blend_input(i, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true); + } } } - if (prev < 0) { // process current animation, check for transition + if (cur_prev_index < 0) { // process current animation, check for transition - rem = blend_input(current, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, true); + rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true); if (p_seek) { - time = p_time; + cur_time = p_time; } else { - time += p_time; + cur_time += p_time; } - if (inputs[current].auto_advance && rem <= xfade_time) { - set_parameter(this->current, (current + 1) % enabled_inputs); + if (inputs[cur_current_index].auto_advance && rem <= xfade_time) { + set_parameter(transition_request, get_input_caption((cur_current_index + 1) % enabled_inputs)); } } else { // cross-fading from prev to current - float blend = xfade_time == 0 ? 0 : (prev_xfading / xfade_time); + real_t blend = xfade_time == 0 ? 0 : (cur_prev_xfading / xfade_time); if (xfade_curve.is_valid()) { blend = xfade_curve->sample(blend); } - if (from_start && !p_seek && switched) { //just switched, seek to start of current - - rem = blend_input(current, 0, true, p_seek_root, 1.0 - blend, FILTER_IGNORE, true); + // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + real_t blend_inv = 1.0 - blend; + if (reset && !p_seek && switched) { //just switched, seek to start of current + rem = blend_input(cur_current_index, 0, true, p_is_external_seeking, Math::is_zero_approx(blend_inv) ? CMP_EPSILON : blend_inv, FILTER_IGNORE, true); } else { - rem = blend_input(current, p_time, p_seek, p_seek_root, 1.0 - blend, FILTER_IGNORE, true); + rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, Math::is_zero_approx(blend_inv) ? CMP_EPSILON : blend_inv, FILTER_IGNORE, true); } if (p_seek) { - blend_input(prev, p_time, true, p_seek_root, blend, FILTER_IGNORE, true); - time = p_time; + blend_input(cur_prev_index, p_time, true, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_IGNORE, true); + cur_time = p_time; } else { - blend_input(prev, p_time, false, p_seek_root, blend, FILTER_IGNORE, true); - time += p_time; - prev_xfading -= p_time; - if (prev_xfading < 0) { - set_parameter(this->prev, -1); + blend_input(cur_prev_index, p_time, false, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_IGNORE, true); + cur_time += p_time; + cur_prev_xfading -= p_time; + if (cur_prev_xfading < 0) { + set_parameter(prev_index, -1); } } } - set_parameter(this->time, time); - set_parameter(this->prev_xfading, prev_xfading); + set_parameter(time, cur_time); + set_parameter(prev_xfading, cur_prev_xfading); return rem; } @@ -800,6 +890,7 @@ void AnimationNodeTransition::_bind_methods() { ClassDB::bind_method(D_METHOD("set_input_caption", "input", "caption"), &AnimationNodeTransition::set_input_caption); ClassDB::bind_method(D_METHOD("get_input_caption", "input"), &AnimationNodeTransition::get_input_caption); + ClassDB::bind_method(D_METHOD("find_input_caption", "caption"), &AnimationNodeTransition::find_input_caption); ClassDB::bind_method(D_METHOD("set_xfade_time", "time"), &AnimationNodeTransition::set_xfade_time); ClassDB::bind_method(D_METHOD("get_xfade_time"), &AnimationNodeTransition::get_xfade_time); @@ -807,13 +898,13 @@ void AnimationNodeTransition::_bind_methods() { ClassDB::bind_method(D_METHOD("set_xfade_curve", "curve"), &AnimationNodeTransition::set_xfade_curve); ClassDB::bind_method(D_METHOD("get_xfade_curve"), &AnimationNodeTransition::get_xfade_curve); - ClassDB::bind_method(D_METHOD("set_from_start", "from_start"), &AnimationNodeTransition::set_from_start); - ClassDB::bind_method(D_METHOD("is_from_start"), &AnimationNodeTransition::is_from_start); + ClassDB::bind_method(D_METHOD("set_reset", "reset"), &AnimationNodeTransition::set_reset); + ClassDB::bind_method(D_METHOD("is_reset"), &AnimationNodeTransition::is_reset); - ADD_PROPERTY(PropertyInfo(Variant::INT, "enabled_inputs", PROPERTY_HINT_RANGE, "0,64,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_enabled_inputs", "get_enabled_inputs"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "enabled_inputs", PROPERTY_HINT_RANGE, "0,31,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_enabled_inputs", "get_enabled_inputs"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,120,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, "from_start"), "set_from_start", "is_from_start"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset"), "set_reset", "is_reset"); for (int i = 0; i < MAX_INPUTS; i++) { ADD_PROPERTYI(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_input_caption", "get_input_caption", i); @@ -833,8 +924,8 @@ String AnimationNodeOutput::get_caption() const { return "Output"; } -double AnimationNodeOutput::process(double p_time, bool p_seek, bool p_seek_root) { - return blend_input(0, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, true); +double AnimationNodeOutput::process(double p_time, bool p_seek, bool p_is_external_seeking) { + return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true); } AnimationNodeOutput::AnimationNodeOutput() { @@ -1046,9 +1137,9 @@ String AnimationNodeBlendTree::get_caption() const { return "BlendTree"; } -double AnimationNodeBlendTree::process(double p_time, bool p_seek, bool p_seek_root) { +double AnimationNodeBlendTree::process(double p_time, bool p_seek, bool p_is_external_seeking) { Ref<AnimationNodeOutput> output = nodes[SceneStringNames::get_singleton()->output].node; - return _blend_node("output", nodes[SceneStringNames::get_singleton()->output].connections, this, output, p_time, p_seek, p_seek_root, 1.0, FILTER_IGNORE, true); + return _blend_node("output", nodes[SceneStringNames::get_singleton()->output].connections, this, output, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true); } void AnimationNodeBlendTree::get_node_list(List<StringName> *r_list) { @@ -1070,10 +1161,10 @@ Ref<AnimationNode> AnimationNodeBlendTree::get_child_by_name(const StringName &p } bool AnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_value) { - String name = p_name; - if (name.begins_with("nodes/")) { - String node_name = name.get_slicec('/', 1); - String what = name.get_slicec('/', 2); + String prop_name = p_name; + if (prop_name.begins_with("nodes/")) { + String node_name = prop_name.get_slicec('/', 1); + String what = prop_name.get_slicec('/', 2); if (what == "node") { Ref<AnimationNode> anode = p_value; @@ -1089,7 +1180,7 @@ bool AnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_val } return true; } - } else if (name == "node_connections") { + } else if (prop_name == "node_connections") { Array conns = p_value; ERR_FAIL_COND_V(conns.size() % 3 != 0, false); @@ -1103,10 +1194,10 @@ bool AnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_val } bool AnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_ret) const { - String name = p_name; - if (name.begins_with("nodes/")) { - String node_name = name.get_slicec('/', 1); - String what = name.get_slicec('/', 2); + String prop_name = p_name; + if (prop_name.begins_with("nodes/")) { + String node_name = prop_name.get_slicec('/', 1); + String what = prop_name.get_slicec('/', 2); if (what == "node") { if (nodes.has(node_name)) { @@ -1121,7 +1212,7 @@ bool AnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_ret) cons return true; } } - } else if (name == "node_connections") { + } else if (prop_name == "node_connections") { List<NodeConnection> nc; get_node_connections(&nc); Array conns; @@ -1150,11 +1241,11 @@ void AnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) cons names.sort_custom<StringName::AlphCompare>(); for (const StringName &E : names) { - String name = E; - if (name != "output") { - p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR)); + String prop_name = E; + if (prop_name != "output") { + p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR)); } - p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + prop_name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } p_list->push_back(PropertyInfo(Variant::ARRAY, "node_connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); @@ -1175,6 +1266,7 @@ void AnimationNodeBlendTree::_tree_changed() { void AnimationNodeBlendTree::_node_changed(const StringName &p_node) { ERR_FAIL_COND(!nodes.has(p_node)); nodes[p_node].connections.resize(nodes[p_node].node->get_input_count()); + emit_signal(SNAME("node_changed"), p_node); } void AnimationNodeBlendTree::_bind_methods() { @@ -1200,6 +1292,8 @@ void AnimationNodeBlendTree::_bind_methods() { BIND_CONSTANT(CONNECTION_ERROR_NO_OUTPUT); BIND_CONSTANT(CONNECTION_ERROR_SAME_NODE); BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_EXISTS); + + ADD_SIGNAL(MethodInfo("node_changed", PropertyInfo(Variant::STRING_NAME, "node_name"))); } void AnimationNodeBlendTree::_initialize_node_tree() { |