diff options
Diffstat (limited to 'scene/animation/animation_blend_tree.cpp')
-rw-r--r-- | scene/animation/animation_blend_tree.cpp | 504 |
1 files changed, 365 insertions, 139 deletions
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index 4a01b2ad65..71f9c45eea 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -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; + bool is_just_looped = false; + + // 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); + } + 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); + } + } } else { - p_time *= backward ? -1.0 : 1.0; - cur_time = cur_time + p_time; - step = p_time; + if (cur_time < 0) { + cur_delta += cur_time; + cur_time = 0; + } else if (cur_time > cur_len) { + cur_delta += cur_time - cur_len; + cur_time = cur_len; + } + backward = false; + // 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 >= cur_len) { + cur_delta = 0; + } + } else { + if (prev_time <= 0) { + cur_delta = 0; + } + } + } } - bool is_looping = false; - if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) { + // 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)) { - if (prev_time >= 0 && cur_time < 0) { - backward = !backward; + 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_time <= anim_size && cur_time > anim_size) { - backward = !backward; + if (prev_playback_time <= anim_size && cur_playback_time > anim_size) { looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END; } - cur_time = Math::pingpong(cur_time, anim_size); } - is_looping = true; - } else if (anim->get_loop_mode() == Animation::LOOP_LINEAR) { + } else if (cur_loop_mode == Animation::LOOP_PINGPONG) { if (!Math::is_zero_approx(anim_size)) { - if (prev_time >= 0 && cur_time < 0) { + 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_time <= anim_size && cur_time > anim_size) { + if (prev_playback_time <= anim_size && cur_playback_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_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; - } - } else { - if (prev_time <= 0) { - step = 0; - } - } + 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; |