diff options
Diffstat (limited to 'scene/animation/animation_tree.cpp')
-rw-r--r-- | scene/animation/animation_tree.cpp | 926 |
1 files changed, 554 insertions, 372 deletions
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index f5c7ad254c..9f9916c1c6 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* animation_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_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_tree.h" @@ -50,10 +50,14 @@ void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const { Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter) const { Variant ret; - if (GDVIRTUAL_CALL(_get_parameter_default_value, p_parameter, ret)) { - return ret; - } - return Variant(); + GDVIRTUAL_CALL(_get_parameter_default_value, p_parameter, ret); + return ret; +} + +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; } void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_value) { @@ -62,7 +66,7 @@ void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_val ERR_FAIL_COND(!state->tree->property_parent_map[base_path].has(p_name)); StringName path = state->tree->property_parent_map[base_path][p_name]; - state->tree->property_map[path] = p_value; + state->tree->property_map[path].first = p_value; } Variant AnimationNode::get_parameter(const StringName &p_name) const { @@ -71,7 +75,7 @@ Variant AnimationNode::get_parameter(const StringName &p_name) const { ERR_FAIL_COND_V(!state->tree->property_parent_map[base_path].has(p_name), Variant()); StringName path = state->tree->property_parent_map[base_path][p_name]; - return state->tree->property_map[path]; + return state->tree->property_map[path].first; } void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) { @@ -88,7 +92,7 @@ void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) { } } -void AnimationNode::blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_seek_root, real_t p_blend, int p_pingponged) { +void AnimationNode::blend_animation(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) { ERR_FAIL_COND(!state); ERR_FAIL_COND(!state->player->has_animation(p_animation)); @@ -114,19 +118,19 @@ void AnimationNode::blend_animation(const StringName &p_animation, double p_time anim_state.time = p_time; anim_state.animation = animation; anim_state.seeked = p_seeked; - anim_state.pingponged = p_pingponged; - anim_state.seek_root = p_seek_root; + anim_state.looped_flag = p_looped_flag; + anim_state.is_external_seeking = p_is_external_seeking; state->animation_states.push_back(anim_state); } -double AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_seek_root, const Vector<StringName> &p_connections) { +double AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_is_external_seeking, const Vector<StringName> &p_connections) { base_path = p_base_path; parent = p_parent; connections = p_connections; state = p_state; - double t = process(p_time, p_seek, p_seek_root); + double t = process(p_time, p_seek, p_is_external_seeking); state = nullptr; parent = nullptr; @@ -150,7 +154,7 @@ void AnimationNode::make_invalid(const String &p_reason) { state->invalid_reasons += String::utf8("• ") + p_reason; } -double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool p_seek_root, real_t p_blend, FilterAction p_filter, bool p_sync) { +double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync) { ERR_FAIL_INDEX_V(p_input, inputs.size(), 0); ERR_FAIL_COND_V(!state, 0); @@ -169,7 +173,7 @@ double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool //inputs.write[p_input].last_pass = state->last_pass; real_t activity = 0.0; - double ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), nullptr, node, p_time, p_seek, p_seek_root, p_blend, p_filter, p_sync, &activity); + double ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), nullptr, node, p_time, p_seek, p_is_external_seeking, p_blend, p_filter, p_sync, &activity); Vector<AnimationTree::Activity> *activity_ptr = state->tree->input_activity_map.getptr(base_path); @@ -180,11 +184,11 @@ double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool return ret; } -double AnimationNode::blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_seek_root, real_t p_blend, FilterAction p_filter, bool p_sync) { - return _blend_node(p_sub_path, Vector<StringName>(), this, p_node, p_time, p_seek, p_seek_root, p_blend, p_filter, p_sync); +double AnimationNode::blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync) { + return _blend_node(p_sub_path, Vector<StringName>(), this, p_node, p_time, p_seek, p_is_external_seeking, p_blend, p_filter, p_sync); } -double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_seek_root, real_t p_blend, FilterAction p_filter, bool p_sync, real_t *r_max) { +double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, real_t *r_max) { ERR_FAIL_COND_V(!p_node.is_valid(), 0); ERR_FAIL_COND_V(!state, 0); @@ -223,7 +227,7 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Stri } blendw[i] = blendr[i] * p_blend; - if (blendw[i] > CMP_EPSILON) { + if (!Math::is_zero_approx(blendw[i])) { any_valid = true; } } @@ -238,7 +242,7 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Stri } blendw[i] = blendr[i] * p_blend; - if (blendw[i] > CMP_EPSILON) { + if (!Math::is_zero_approx(blendw[i])) { any_valid = true; } } @@ -254,7 +258,7 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Stri blendw[i] = blendr[i]; //not filtered, do not blend } - if (blendw[i] > CMP_EPSILON) { + if (!Math::is_zero_approx(blendw[i])) { any_valid = true; } } @@ -265,7 +269,7 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Stri for (int i = 0; i < blend_count; i++) { //regular blend blendw[i] = blendr[i] * p_blend; - if (blendw[i] > CMP_EPSILON) { + if (!Math::is_zero_approx(blendw[i])) { any_valid = true; } } @@ -291,50 +295,29 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Stri new_path = String(parent->base_path) + String(p_subpath) + "/"; } - // If tracks for blending don't exist for one of the animations, Rest or RESET animation is blended as init animation instead. - // Then blend weight is 0 means that the init animation blend weight is 1. - // In that case, processing only the animation with the lacking track will not process the lacking track, and will not properly apply the Reset value. - // This means that all tracks which the animations in the branch that may be blended have must be processed. - // Therefore, the blending process must be executed even if the blend weight is 0. + // This process, which depends on p_sync is needed to process sync correctly in the case of + // that a synced AnimationNodeSync exists under the un-synced AnimationNodeSync. if (!p_seek && !p_sync && !any_valid) { - return p_node->_pre_process(new_path, new_parent, state, 0, p_seek, p_seek_root, p_connections); + return p_node->_pre_process(new_path, new_parent, state, 0, p_seek, p_is_external_seeking, p_connections); } - return p_node->_pre_process(new_path, new_parent, state, p_time, p_seek, p_seek_root, p_connections); -} - -int AnimationNode::get_input_count() const { - return inputs.size(); -} - -String AnimationNode::get_input_name(int p_input) { - ERR_FAIL_INDEX_V(p_input, inputs.size(), String()); - return inputs[p_input].name; + return p_node->_pre_process(new_path, new_parent, state, p_time, p_seek, p_is_external_seeking, p_connections); } String AnimationNode::get_caption() const { - String ret; - if (GDVIRTUAL_CALL(_get_caption, ret)) { - return ret; - } - - return "Node"; + String ret = "Node"; + GDVIRTUAL_CALL(_get_caption, ret); + return ret; } -void AnimationNode::add_input(const String &p_name) { +bool AnimationNode::add_input(const String &p_name) { //root nodes can't add inputs - ERR_FAIL_COND(Object::cast_to<AnimationRootNode>(this) != nullptr); + ERR_FAIL_COND_V(Object::cast_to<AnimationRootNode>(this) != nullptr, false); Input input; - ERR_FAIL_COND(p_name.contains(".") || p_name.contains("/")); + ERR_FAIL_COND_V(p_name.contains(".") || p_name.contains("/"), false); input.name = p_name; inputs.push_back(input); emit_changed(); -} - -void AnimationNode::set_input_name(int p_input, const String &p_name) { - ERR_FAIL_INDEX(p_input, inputs.size()); - ERR_FAIL_COND(p_name.contains(".") || p_name.contains("/")); - inputs.write[p_input].name = p_name; - emit_changed(); + return true; } void AnimationNode::remove_input(int p_index) { @@ -343,13 +326,38 @@ void AnimationNode::remove_input(int p_index) { emit_changed(); } -double AnimationNode::process(double p_time, bool p_seek, bool p_seek_root) { - double ret; - if (GDVIRTUAL_CALL(_process, p_time, p_seek, p_seek_root, ret)) { - return ret; +bool AnimationNode::set_input_name(int p_input, const String &p_name) { + ERR_FAIL_INDEX_V(p_input, inputs.size(), false); + ERR_FAIL_COND_V(p_name.contains(".") || p_name.contains("/"), false); + inputs.write[p_input].name = p_name; + emit_changed(); + return true; +} + +String AnimationNode::get_input_name(int p_input) const { + ERR_FAIL_INDEX_V(p_input, inputs.size(), String()); + return inputs[p_input].name; +} + +int AnimationNode::get_input_count() const { + return inputs.size(); +} + +int AnimationNode::find_input(const String &p_name) const { + int idx = -1; + for (int i = 0; i < inputs.size(); i++) { + if (inputs[i].name == p_name) { + idx = i; + break; + } } + return idx; +} - return 0; +double AnimationNode::process(double p_time, bool p_seek, bool p_is_external_seeking) { + double ret = 0; + GDVIRTUAL_CALL(_process, p_time, p_seek, p_is_external_seeking, ret); + return ret; } void AnimationNode::set_filter_path(const NodePath &p_path, bool p_enable) { @@ -373,12 +381,9 @@ bool AnimationNode::is_path_filtered(const NodePath &p_path) const { } bool AnimationNode::has_filter() const { - bool ret; - if (GDVIRTUAL_CALL(_has_filter, ret)) { - return ret; - } - - return false; + bool ret = false; + GDVIRTUAL_CALL(_has_filter, ret); + return ret; } Array AnimationNode::_get_filters() const { @@ -407,18 +412,17 @@ void AnimationNode::_validate_property(PropertyInfo &p_property) const { Ref<AnimationNode> AnimationNode::get_child_by_name(const StringName &p_name) { Ref<AnimationNode> ret; - if (GDVIRTUAL_CALL(_get_child_by_name, p_name, ret)) { - return ret; - } - return Ref<AnimationNode>(); + GDVIRTUAL_CALL(_get_child_by_name, p_name, ret); + return ret; } void AnimationNode::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_input_count"), &AnimationNode::get_input_count); - ClassDB::bind_method(D_METHOD("get_input_name", "input"), &AnimationNode::get_input_name); - ClassDB::bind_method(D_METHOD("add_input", "name"), &AnimationNode::add_input); ClassDB::bind_method(D_METHOD("remove_input", "index"), &AnimationNode::remove_input); + ClassDB::bind_method(D_METHOD("set_input_name", "input", "name"), &AnimationNode::set_input_name); + ClassDB::bind_method(D_METHOD("get_input_name", "input"), &AnimationNode::get_input_name); + ClassDB::bind_method(D_METHOD("get_input_count"), &AnimationNode::get_input_count); + ClassDB::bind_method(D_METHOD("find_input", "name"), &AnimationNode::find_input); ClassDB::bind_method(D_METHOD("set_filter_path", "path", "enable"), &AnimationNode::set_filter_path); ClassDB::bind_method(D_METHOD("is_path_filtered", "path"), &AnimationNode::is_path_filtered); @@ -429,9 +433,9 @@ void AnimationNode::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_filters", "filters"), &AnimationNode::_set_filters); ClassDB::bind_method(D_METHOD("_get_filters"), &AnimationNode::_get_filters); - ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "seek_root", "blend", "pingponged"), &AnimationNode::blend_animation, DEFVAL(0)); - ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "seek_root", "blend", "filter", "sync"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true)); - ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "seek_root", "blend", "filter", "sync"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "is_external_seeking", "blend", "looped_flag"), &AnimationNode::blend_animation, DEFVAL(Animation::LOOPED_FLAG_NONE)); + ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "is_external_seeking", "blend", "filter", "sync"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "is_external_seeking", "blend", "filter", "sync"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true)); ClassDB::bind_method(D_METHOD("set_parameter", "name", "value"), &AnimationNode::set_parameter); ClassDB::bind_method(D_METHOD("get_parameter", "name"), &AnimationNode::get_parameter); @@ -443,13 +447,14 @@ void AnimationNode::_bind_methods() { GDVIRTUAL_BIND(_get_parameter_list); GDVIRTUAL_BIND(_get_child_by_name, "name"); GDVIRTUAL_BIND(_get_parameter_default_value, "parameter"); - GDVIRTUAL_BIND(_process, "time", "seek", "seek_root"); + GDVIRTUAL_BIND(_is_parameter_read_only, "parameter"); + GDVIRTUAL_BIND(_process, "time", "seek", "is_external_seeking"); GDVIRTUAL_BIND(_get_caption); GDVIRTUAL_BIND(_has_filter); - ADD_SIGNAL(MethodInfo("removed_from_graph")); - ADD_SIGNAL(MethodInfo("tree_changed")); + ADD_SIGNAL(MethodInfo("animation_node_renamed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name"))); + ADD_SIGNAL(MethodInfo("animation_node_removed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "name"))); BIND_ENUM_CONSTANT(FILTER_IGNORE); BIND_ENUM_CONSTANT(FILTER_PASS); @@ -462,15 +467,33 @@ AnimationNode::AnimationNode() { //////////////////// +void AnimationRootNode::_tree_changed() { + emit_signal(SNAME("tree_changed")); +} + +void AnimationRootNode::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) { + emit_signal(SNAME("animation_node_renamed"), p_oid, p_old_name, p_new_name); +} + +void AnimationRootNode::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) { + emit_signal(SNAME("animation_node_removed"), p_oid, p_node); +} + +//////////////////// + void AnimationTree::set_tree_root(const Ref<AnimationNode> &p_root) { if (root.is_valid()) { root->disconnect("tree_changed", callable_mp(this, &AnimationTree::_tree_changed)); + root->disconnect("animation_node_renamed", callable_mp(this, &AnimationTree::_animation_node_renamed)); + root->disconnect("animation_node_removed", callable_mp(this, &AnimationTree::_animation_node_removed)); } root = p_root; if (root.is_valid()) { root->connect("tree_changed", callable_mp(this, &AnimationTree::_tree_changed)); + root->connect("animation_node_renamed", callable_mp(this, &AnimationTree::_animation_node_renamed)); + root->connect("animation_node_removed", callable_mp(this, &AnimationTree::_animation_node_removed)); } properties_dirty = true; @@ -497,13 +520,7 @@ void AnimationTree::set_active(bool p_active) { } if (!active && is_inside_tree()) { - for (const TrackCache *E : playing_caches) { - if (ObjectDB::get_instance(E->object_id)) { - E->object->call(SNAME("stop")); - } - } - - playing_caches.clear(); + _clear_caches(); } } @@ -542,6 +559,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { if (!player->has_node(player->get_root())) { ERR_PRINT("AnimationTree: AnimationPlayer root is invalid."); set_active(false); + _clear_caches(); return false; } Node *parent = player->get_node(player->get_root()); @@ -549,6 +567,10 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { List<StringName> sname; player->get_animation_list(&sname); + root_motion_cache.loc = Vector3(0, 0, 0); + root_motion_cache.rot = Quaternion(0, 0, 0, 1); + root_motion_cache.scale = Vector3(1, 1, 1); + Ref<Animation> reset_anim; bool has_reset_anim = player->has_animation(SceneStringNames::get_singleton()->RESET); if (has_reset_anim) { @@ -602,6 +624,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track_value->object = child; } + track_value->is_discrete = anim->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE; track_value->is_using_angle = anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE; track_value->subpath = leftover_path; @@ -609,6 +632,13 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track = track_value; + // If a value track without a key is cached first, the initial value cannot be determined. + // It is a corner case, but which may cause problems with blending. + ERR_CONTINUE_MSG(anim->track_get_key_count(i) == 0, "AnimationTree: '" + String(E) + "', value track: '" + String(path) + "' must have at least one key to cache for blending."); + track_value->init_value = anim->track_get_key_value(i, 0); + track_value->init_value.zero(); + + // If there is a Reset Animation, it takes precedence by overwriting. if (has_reset_anim) { int rt = reset_anim->find_track(path, track_type); if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { @@ -757,7 +787,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { if (has_reset_anim) { int rt = reset_anim->find_track(path, track_type); if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { - track_bezier->init_value = reset_anim->track_get_key_value(rt, 0); + track_bezier->init_value = (reset_anim->track_get_key_value(rt, 0).operator Array())[0]; } } } break; @@ -766,6 +796,8 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track_audio->object = child; track_audio->object_id = track_audio->object->get_instance_id(); + track_audio->audio_stream.instantiate(); + track_audio->audio_stream->set_polyphony(audio_max_polyphony); track = track_audio; @@ -808,8 +840,18 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { } } else if (track_cache_type == Animation::TYPE_VALUE) { // If it has at least one angle interpolation, it also uses angle interpolation for blending. - TrackCacheValue *track_value = memnew(TrackCacheValue); + TrackCacheValue *track_value = static_cast<TrackCacheValue *>(track); + bool was_discrete = track_value->is_discrete; + bool was_using_angle = track_value->is_using_angle; + track_value->is_discrete |= anim->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE; track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE; + + if (was_discrete != track_value->is_discrete) { + ERR_PRINT_ED("Value track: " + String(path) + " with different update modes are blended. Blending prioritizes Discrete mode, so other update mode tracks will not be blended."); + } + if (was_using_angle != track_value->is_using_angle) { + WARN_PRINT_ED("Value track: " + String(path) + " with different interpolation types for rotation are blended. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value."); + } } track->setup_pass = setup_pass; @@ -853,15 +895,32 @@ void AnimationTree::_animation_player_changed() { } void AnimationTree::_clear_caches() { + _clear_audio_streams(); + _clear_playing_caches(); for (KeyValue<NodePath, TrackCache *> &K : track_cache) { memdelete(K.value); } - playing_caches.clear(); - track_cache.clear(); cache_valid = false; } +void AnimationTree::_clear_audio_streams() { + for (int i = 0; i < playing_audio_stream_players.size(); i++) { + playing_audio_stream_players[i]->call(SNAME("stop")); + playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref<AudioStream>()); + } + playing_audio_stream_players.clear(); +} + +void AnimationTree::_clear_playing_caches() { + for (const TrackCache *E : playing_caches) { + if (ObjectDB::get_instance(E->object_id)) { + E->object->call(SNAME("stop")); + } + } + playing_caches.clear(); +} + static void _call_object(Object *p_object, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred) { // Separate function to use alloca() more efficiently const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * p_params.size()); @@ -881,7 +940,9 @@ void AnimationTree::_process_graph(double p_delta) { _update_properties(); //if properties need updating, update them //check all tracks, see if they need modification - root_motion_transform = Transform3D(); + root_motion_position = Vector3(0, 0, 0); + root_motion_rotation = Quaternion(0, 0, 0, 1); + root_motion_scale = Vector3(0, 0, 0); if (!root.is_valid()) { ERR_PRINT("AnimationTree: root AnimationNode is not set, disabling playback."); @@ -968,21 +1029,65 @@ void AnimationTree::_process_graph(double p_delta) { if (!state.valid) { return; //state is not valid. do nothing. } - //apply value/transform/bezier blends to track caches and execute method/audio/animation tracks + // Init all value/transform/blend/bezier tracks that track_cache has. { - bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); + for (const KeyValue<NodePath, TrackCache *> &K : track_cache) { + TrackCache *track = K.value; + switch (track->type) { + case Animation::TYPE_POSITION_3D: { + TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); + if (track->root_motion) { + root_motion_cache.loc = Vector3(0, 0, 0); + root_motion_cache.rot = Quaternion(0, 0, 0, 1); + root_motion_cache.scale = Vector3(1, 1, 1); + } + t->loc = t->init_loc; + t->rot = t->init_rot; + t->scale = t->init_scale; + } break; + case Animation::TYPE_BLEND_SHAPE: { + TrackCacheBlendShape *t = static_cast<TrackCacheBlendShape *>(track); + t->value = t->init_value; + } break; + case Animation::TYPE_VALUE: { + TrackCacheValue *t = static_cast<TrackCacheValue *>(track); + t->value = t->init_value; + } break; + case Animation::TYPE_BEZIER: { + TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track); + t->value = t->init_value; + } break; + case Animation::TYPE_AUDIO: { + TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track); + for (KeyValue<ObjectID, PlayingAudioTrackInfo> &L : t->playing_streams) { + PlayingAudioTrackInfo &track_info = L.value; + track_info.volume = 0.0; + } + } break; + default: { + } break; + } + } + } + + // Apply value/transform/blend/bezier blends to track caches and execute method/audio/animation tracks. + { +#ifdef TOOLS_ENABLED + bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); +#endif // TOOLS_ENABLED for (const AnimationNode::AnimationState &as : state.animation_states) { Ref<Animation> a = as.animation; double time = as.time; double delta = as.delta; real_t weight = as.blend; bool seeked = as.seeked; - int pingponged = as.pingponged; + Animation::LoopedFlag looped_flag = as.looped_flag; + bool is_external_seeking = as.is_external_seeking; + bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream. #ifndef _3D_DISABLED - bool backward = signbit(delta); - bool calc_root = !seeked || as.seek_root; + bool calc_root = !seeked || is_external_seeking; #endif // _3D_DISABLED for (int i = 0; i < a->get_track_count(); i++) { @@ -991,37 +1096,32 @@ void AnimationTree::_process_graph(double p_delta) { } NodePath path = a->track_get_path(i); - - ERR_CONTINUE(!track_cache.has(path)); - + if (!track_cache.has(path)) { + continue; // No path, but avoid error spamming. + } TrackCache *track = track_cache[path]; + ERR_CONTINUE(!state.track_map.has(path)); + int blend_idx = state.track_map[path]; + ERR_CONTINUE(blend_idx < 0 || blend_idx >= state.track_count); + real_t blend = (*as.track_blends)[blend_idx] * weight; + Animation::TrackType ttype = a->track_get_type(i); if (ttype != Animation::TYPE_POSITION_3D && ttype != Animation::TYPE_ROTATION_3D && ttype != Animation::TYPE_SCALE_3D && track->type != ttype) { //broken animation, but avoid error spamming continue; } - track->root_motion = root_motion_track == path; - ERR_CONTINUE(!state.track_map.has(path)); - int blend_idx = state.track_map[path]; - - ERR_CONTINUE(blend_idx < 0 || blend_idx >= state.track_count); - - real_t blend = (*as.track_blends)[blend_idx] * weight; - switch (ttype) { case Animation::TYPE_POSITION_3D: { #ifndef _3D_DISABLED + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); + if (track->root_motion && calc_root) { - if (t->process_pass != process_pass) { - t->process_pass = process_pass; - t->loc = Vector3(0, 0, 0); - t->rot = Quaternion(0, 0, 0, 1); - t->scale = Vector3(0, 0, 0); - } double prev_time = time - delta; if (!backward) { if (prev_time < 0) { @@ -1065,10 +1165,10 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - loc[0] = _post_process_key_value(a, i, loc[0], t->object, t->bone_idx); + loc[0] = post_process_key_value(a, i, loc[0], t->object, t->bone_idx); a->position_track_interpolate(i, (double)a->get_length(), &loc[1]); - loc[1] = _post_process_key_value(a, i, loc[1], t->object, t->bone_idx); - t->loc += (loc[1] - loc[0]) * blend; + loc[1] = post_process_key_value(a, i, loc[1], t->object, t->bone_idx); + root_motion_cache.loc += (loc[1] - loc[0]) * blend; prev_time = 0; } } else { @@ -1077,10 +1177,10 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - loc[0] = _post_process_key_value(a, i, loc[0], t->object, t->bone_idx); + loc[0] = post_process_key_value(a, i, loc[0], t->object, t->bone_idx); a->position_track_interpolate(i, 0, &loc[1]); - loc[1] = _post_process_key_value(a, i, loc[1], t->object, t->bone_idx); - t->loc += (loc[1] - loc[0]) * blend; + loc[1] = post_process_key_value(a, i, loc[1], t->object, t->bone_idx); + root_motion_cache.loc += (loc[1] - loc[0]) * blend; prev_time = (double)a->get_length(); } } @@ -1089,27 +1189,21 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - loc[0] = _post_process_key_value(a, i, loc[0], t->object, t->bone_idx); - + loc[0] = post_process_key_value(a, i, loc[0], t->object, t->bone_idx); a->position_track_interpolate(i, time, &loc[1]); - loc[1] = _post_process_key_value(a, i, loc[1], t->object, t->bone_idx); - t->loc += (loc[1] - loc[0]) * blend; + loc[1] = post_process_key_value(a, i, loc[1], t->object, t->bone_idx); + root_motion_cache.loc += (loc[1] - loc[0]) * blend; prev_time = !backward ? 0 : (double)a->get_length(); + } - } else { - if (t->process_pass != process_pass) { - t->process_pass = process_pass; - t->loc = t->init_loc; - t->rot = t->init_rot; - t->scale = t->init_scale; - } + { Vector3 loc; Error err = a->position_track_interpolate(i, time, &loc); if (err != OK) { continue; } - loc = _post_process_key_value(a, i, loc, t->object, t->bone_idx); + loc = post_process_key_value(a, i, loc, t->object, t->bone_idx); t->loc += (loc - t->init_loc) * blend; } @@ -1117,14 +1211,12 @@ void AnimationTree::_process_graph(double p_delta) { } break; case Animation::TYPE_ROTATION_3D: { #ifndef _3D_DISABLED + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); + if (track->root_motion && calc_root) { - if (t->process_pass != process_pass) { - t->process_pass = process_pass; - t->loc = Vector3(0, 0, 0); - t->rot = Quaternion(0, 0, 0, 1); - t->scale = Vector3(0, 0, 0); - } double prev_time = time - delta; if (!backward) { if (prev_time < 0) { @@ -1168,10 +1260,10 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - rot[0] = _post_process_key_value(a, i, rot[0], t->object, t->bone_idx); + rot[0] = post_process_key_value(a, i, rot[0], t->object, t->bone_idx); a->rotation_track_interpolate(i, (double)a->get_length(), &rot[1]); - rot[1] = _post_process_key_value(a, i, rot[1], t->object, t->bone_idx); - t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); + rot[1] = post_process_key_value(a, i, rot[1], t->object, t->bone_idx); + root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); prev_time = 0; } } else { @@ -1180,9 +1272,9 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - rot[0] = _post_process_key_value(a, i, rot[0], t->object, t->bone_idx); + rot[0] = post_process_key_value(a, i, rot[0], t->object, t->bone_idx); a->rotation_track_interpolate(i, 0, &rot[1]); - t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); + root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); prev_time = (double)a->get_length(); } } @@ -1191,27 +1283,22 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - rot[0] = _post_process_key_value(a, i, rot[0], t->object, t->bone_idx); + rot[0] = post_process_key_value(a, i, rot[0], t->object, t->bone_idx); a->rotation_track_interpolate(i, time, &rot[1]); - rot[1] = _post_process_key_value(a, i, rot[1], t->object, t->bone_idx); - t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); + rot[1] = post_process_key_value(a, i, rot[1], t->object, t->bone_idx); + root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); prev_time = !backward ? 0 : (double)a->get_length(); + } - } else { - if (t->process_pass != process_pass) { - t->process_pass = process_pass; - t->loc = t->init_loc; - t->rot = t->init_rot; - t->scale = t->init_scale; - } + { Quaternion rot; Error err = a->rotation_track_interpolate(i, time, &rot); if (err != OK) { continue; } - rot = _post_process_key_value(a, i, rot, t->object, t->bone_idx); + rot = post_process_key_value(a, i, rot, t->object, t->bone_idx); t->rot = (t->rot * Quaternion().slerp(t->init_rot.inverse() * rot, blend)).normalized(); } @@ -1219,14 +1306,12 @@ void AnimationTree::_process_graph(double p_delta) { } break; case Animation::TYPE_SCALE_3D: { #ifndef _3D_DISABLED + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); + if (track->root_motion && calc_root) { - if (t->process_pass != process_pass) { - t->process_pass = process_pass; - t->loc = Vector3(0, 0, 0); - t->rot = Quaternion(0, 0, 0, 1); - t->scale = Vector3(0, 0, 0); - } double prev_time = time - delta; if (!backward) { if (prev_time < 0) { @@ -1270,10 +1355,10 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - scale[0] = _post_process_key_value(a, i, scale[0], t->object, t->bone_idx); + scale[0] = post_process_key_value(a, i, scale[0], t->object, t->bone_idx); a->scale_track_interpolate(i, (double)a->get_length(), &scale[1]); - t->scale += (scale[1] - scale[0]) * blend; - scale[1] = _post_process_key_value(a, i, scale[1], t->object, t->bone_idx); + root_motion_cache.scale += (scale[1] - scale[0]) * blend; + scale[1] = post_process_key_value(a, i, scale[1], t->object, t->bone_idx); prev_time = 0; } } else { @@ -1282,10 +1367,10 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - scale[0] = _post_process_key_value(a, i, scale[0], t->object, t->bone_idx); + scale[0] = post_process_key_value(a, i, scale[0], t->object, t->bone_idx); a->scale_track_interpolate(i, 0, &scale[1]); - scale[1] = _post_process_key_value(a, i, scale[1], t->object, t->bone_idx); - t->scale += (scale[1] - scale[0]) * blend; + scale[1] = post_process_key_value(a, i, scale[1], t->object, t->bone_idx); + root_motion_cache.scale += (scale[1] - scale[0]) * blend; prev_time = (double)a->get_length(); } } @@ -1294,27 +1379,22 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - scale[0] = _post_process_key_value(a, i, scale[0], t->object, t->bone_idx); + scale[0] = post_process_key_value(a, i, scale[0], t->object, t->bone_idx); a->scale_track_interpolate(i, time, &scale[1]); - scale[1] = _post_process_key_value(a, i, scale[1], t->object, t->bone_idx); - t->scale += (scale[1] - scale[0]) * blend; + scale[1] = post_process_key_value(a, i, scale[1], t->object, t->bone_idx); + root_motion_cache.scale += (scale[1] - scale[0]) * blend; prev_time = !backward ? 0 : (double)a->get_length(); + } - } else { - if (t->process_pass != process_pass) { - t->process_pass = process_pass; - t->loc = t->init_loc; - t->rot = t->init_rot; - t->scale = t->init_scale; - } + { Vector3 scale; Error err = a->scale_track_interpolate(i, time, &scale); if (err != OK) { continue; } - scale = _post_process_key_value(a, i, scale, t->object, t->bone_idx); + scale = post_process_key_value(a, i, scale, t->object, t->bone_idx); t->scale += (scale - t->init_scale) * blend; } @@ -1322,12 +1402,10 @@ void AnimationTree::_process_graph(double p_delta) { } break; case Animation::TYPE_BLEND_SHAPE: { #ifndef _3D_DISABLED - TrackCacheBlendShape *t = static_cast<TrackCacheBlendShape *>(track); - - if (t->process_pass != process_pass) { - t->process_pass = process_pass; - t->value = t->init_value; + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. } + TrackCacheBlendShape *t = static_cast<TrackCacheBlendShape *>(track); float value; @@ -1337,33 +1415,27 @@ void AnimationTree::_process_graph(double p_delta) { if (err != OK) { continue; } - value = _post_process_key_value(a, i, value, t->object, t->shape_index); + value = post_process_key_value(a, i, value, t->object, t->shape_index); t->value += (value - t->init_value) * blend; #endif // _3D_DISABLED } break; case Animation::TYPE_VALUE: { + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } TrackCacheValue *t = static_cast<TrackCacheValue *>(track); Animation::UpdateMode update_mode = a->value_track_get_update_mode(i); if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) { Variant value = a->value_track_interpolate(i, time); - value = _post_process_key_value(a, i, value, t->object); + value = post_process_key_value(a, i, value, t->object); if (value == Variant()) { continue; } - if (t->process_pass != process_pass) { - t->process_pass = process_pass; - if (!t->init_value) { - t->init_value = value; - t->init_value.zero(); - } - t->value = t->init_value; - } - // Special case for angle interpolation. if (t->is_using_angle) { // For blending consistency, it prevents rotation of more than 180 degrees from init_value. @@ -1392,24 +1464,20 @@ void AnimationTree::_process_graph(double p_delta) { } } } else { - if (blend < CMP_EPSILON) { - continue; //nothing to blend - } - if (seeked) { - int idx = a->track_find_key(i, time); + int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); if (idx < 0) { continue; } Variant value = a->track_get_key_value(i, idx); - value = _post_process_key_value(a, i, value, t->object); + value = post_process_key_value(a, i, value, t->object); t->object->set_indexed(t->subpath, value); } else { List<int> indices; - a->value_track_get_key_indices(i, time, delta, &indices, pingponged); + a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag); for (int &F : indices) { Variant value = a->track_get_key_value(i, F); - value = _post_process_key_value(a, i, value, t->object); + value = post_process_key_value(a, i, value, t->object); t->object->set_indexed(t->subpath, value); } } @@ -1417,159 +1485,132 @@ void AnimationTree::_process_graph(double p_delta) { } break; case Animation::TYPE_METHOD: { - if (blend < CMP_EPSILON) { - continue; //nothing to blend +#ifdef TOOLS_ENABLED + if (!can_call) { + continue; + } +#endif // TOOLS_ENABLED + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. } TrackCacheMethod *t = static_cast<TrackCacheMethod *>(track); if (seeked) { - int idx = a->track_find_key(i, time); + int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); if (idx < 0) { continue; } StringName method = a->method_track_get_name(i, idx); Vector<Variant> params = a->method_track_get_params(i, idx); - if (can_call) { - _call_object(t->object, method, params, false); - } + _call_object(t->object, method, params, false); } else { List<int> indices; - a->method_track_get_key_indices(i, time, delta, &indices, pingponged); + a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag); for (int &F : indices) { StringName method = a->method_track_get_name(i, F); Vector<Variant> params = a->method_track_get_params(i, F); - if (can_call) { - _call_object(t->object, method, params, true); - } + _call_object(t->object, method, params, true); } } } break; case Animation::TYPE_BEZIER: { + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track); real_t bezier = a->bezier_track_interpolate(i, time); - bezier = _post_process_key_value(a, i, bezier, t->object); - - if (t->process_pass != process_pass) { - t->process_pass = process_pass; - t->value = t->init_value; - } + bezier = post_process_key_value(a, i, bezier, t->object); t->value += (bezier - t->init_value) * blend; } break; case Animation::TYPE_AUDIO: { - if (blend < CMP_EPSILON) { - continue; //nothing to blend - } TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track); - if (seeked) { - //find whatever should be playing - int idx = a->track_find_key(i, time); - if (idx < 0) { - continue; - } - - Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); - if (!stream.is_valid()) { - t->object->call(SNAME("stop")); - t->playing = false; - playing_caches.erase(t); - } else { - double start_ofs = a->audio_track_get_key_start_offset(i, idx); - start_ofs += time - a->track_get_key_time(i, idx); - double end_ofs = a->audio_track_get_key_end_offset(i, idx); - double len = stream->get_length(); - - if (start_ofs > len - end_ofs) { - t->object->call(SNAME("stop")); - t->playing = false; - playing_caches.erase(t); - continue; - } - - t->object->call(SNAME("set_stream"), stream); - t->object->call(SNAME("play"), start_ofs); - - t->playing = true; - playing_caches.insert(t); - if (len && end_ofs > 0) { //force an end at a time - t->len = len - start_ofs - end_ofs; - } else { - t->len = 0; - } + Node *asp = Object::cast_to<Node>(t->object); + if (!asp) { + t->playing_streams.clear(); + continue; + } - t->start = time; + ObjectID oid = a->get_instance_id(); + if (!t->playing_streams.has(oid)) { + t->playing_streams[oid] = PlayingAudioTrackInfo(); + } + // The end of audio should be observed even if the blend value is 0, build up the information and store to the cache for that. + PlayingAudioTrackInfo &track_info = t->playing_streams[oid]; + track_info.length = a->get_length(); + track_info.time = time; + track_info.volume += blend; + track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE; + track_info.backward = backward; + track_info.use_blend = a->audio_track_is_use_blend(i); + + HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info; + // Find stream. + int idx = -1; + if (seeked) { + idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); + // Discard previous stream when seeking. + if (map.has(idx)) { + t->audio_stream_playback->stop_stream(map[idx].index); + map.erase(idx); } - } else { - //find stuff to play List<int> to_play; - a->track_get_key_indices_in_range(i, time, delta, &to_play, pingponged); + a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag); if (to_play.size()) { - int idx = to_play.back()->get(); - - Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); - if (!stream.is_valid()) { - t->object->call(SNAME("stop")); - t->playing = false; - playing_caches.erase(t); - } else { - double start_ofs = a->audio_track_get_key_start_offset(i, idx); - double end_ofs = a->audio_track_get_key_end_offset(i, idx); - double len = stream->get_length(); - - t->object->call(SNAME("set_stream"), stream); - t->object->call(SNAME("play"), start_ofs); + idx = to_play.back()->get(); + } + } + if (idx < 0) { + continue; + } - t->playing = true; - playing_caches.insert(t); - if (len && end_ofs > 0) { //force an end at a time - t->len = len - start_ofs - end_ofs; - } else { - t->len = 0; - } + // Play stream. + Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx); + if (stream.is_valid()) { + double start_ofs = a->audio_track_get_key_start_offset(i, idx); + double end_ofs = a->audio_track_get_key_end_offset(i, idx); + double len = stream->get_length(); - t->start = time; - } - } else if (t->playing) { - bool loop = a->get_loop_mode() != Animation::LOOP_NONE; - - bool stop = false; - - if (!loop) { - if (delta > 0) { - if (time < t->start) { - stop = true; - } - } else if (delta < 0) { - if (time > t->start) { - stop = true; - } - } - } else if (t->len > 0) { - double len = t->start > time ? (a->get_length() - t->start) + time : time - t->start; + if (seeked) { + start_ofs += time - a->track_get_key_time(i, idx); + } - if (len > t->len) { - stop = true; - } + if (t->object->call(SNAME("get_stream")) != t->audio_stream) { + t->object->call(SNAME("set_stream"), t->audio_stream); + t->audio_stream_playback.unref(); + if (!playing_audio_stream_players.has(asp)) { + playing_audio_stream_players.push_back(asp); } + } + if (!t->object->call(SNAME("is_playing"))) { + t->object->call(SNAME("play")); + } + if (!t->object->call(SNAME("has_stream_playback"))) { + t->audio_stream_playback.unref(); + continue; + } + if (t->audio_stream_playback.is_null()) { + t->audio_stream_playback = t->object->call(SNAME("get_stream_playback")); + } - if (stop) { - //time to stop - t->object->call(SNAME("stop")); - t->playing = false; - playing_caches.erase(t); - } + PlayingAudioStreamInfo pasi; + pasi.index = t->audio_stream_playback->play_stream(stream, start_ofs); + pasi.start = time; + if (len && end_ofs > 0) { // Force an end at a time. + pasi.len = len - start_ofs - end_ofs; + } else { + pasi.len = 0; } + map[idx] = pasi; } - real_t db = Math::linear_to_db(MAX(blend, 0.00001)); - t->object->call(SNAME("set_volume_db"), db); } break; case Animation::TYPE_ANIMATION: { - if (blend < CMP_EPSILON) { - continue; //nothing to blend + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. } TrackCacheAnimation *t = static_cast<TrackCacheAnimation *>(track); @@ -1581,7 +1622,7 @@ void AnimationTree::_process_graph(double p_delta) { if (seeked) { //seek - int idx = a->track_find_key(i, time); + int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); if (idx < 0) { continue; } @@ -1612,8 +1653,8 @@ void AnimationTree::_process_graph(double p_delta) { } if (player2->is_playing() || seeked) { - player2->play(anim_name); player2->seek(at_anim_pos); + player2->play(anim_name); t->playing = true; playing_caches.insert(t); } else { @@ -1623,7 +1664,7 @@ void AnimationTree::_process_graph(double p_delta) { } else { //find stuff to play List<int> to_play; - a->track_get_key_indices_in_range(i, time, delta, &to_play, pingponged); + a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag); if (to_play.size()) { int idx = to_play.back()->get(); @@ -1652,9 +1693,6 @@ void AnimationTree::_process_graph(double p_delta) { // finally, set the tracks for (const KeyValue<NodePath, TrackCache *> &K : track_cache) { TrackCache *track = K.value; - if (track->process_pass != process_pass) { - continue; //not processed, ignore - } switch (track->type) { case Animation::TYPE_POSITION_3D: { @@ -1662,12 +1700,12 @@ void AnimationTree::_process_graph(double p_delta) { TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); if (t->root_motion) { - Transform3D xform; - xform.origin = t->loc; - xform.basis.set_quaternion_scale(t->rot, Vector3(1, 1, 1) + t->scale); - - root_motion_transform = xform; - + root_motion_position = root_motion_cache.loc; + root_motion_rotation = root_motion_cache.rot; + root_motion_scale = root_motion_cache.scale - Vector3(1, 1, 1); + root_motion_position_accumulator = t->loc; + root_motion_rotation_accumulator = t->rot; + root_motion_scale_accumulator = t->scale; } else if (t->skeleton && t->bone_idx >= 0) { if (t->loc_used) { t->skeleton->set_bone_pose_position(t->bone_idx, t->loc); @@ -1704,6 +1742,10 @@ void AnimationTree::_process_graph(double p_delta) { case Animation::TYPE_VALUE: { TrackCacheValue *t = static_cast<TrackCacheValue *>(track); + if (t->is_discrete) { + break; // Don't overwrite the value set by UPDATE_DISCRETE. + } + if (t->init_value.get_type() == Variant::BOOL) { t->object->set_indexed(t->subpath, t->value.operator real_t() >= 0.5); } else { @@ -1717,6 +1759,64 @@ void AnimationTree::_process_graph(double p_delta) { t->object->set_indexed(t->subpath, t->value); } break; + case Animation::TYPE_AUDIO: { + TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track); + + // Audio ending process. + LocalVector<ObjectID> erase_maps; + for (KeyValue<ObjectID, PlayingAudioTrackInfo> &L : t->playing_streams) { + PlayingAudioTrackInfo &track_info = L.value; + float db = Math::linear_to_db(track_info.use_blend ? track_info.volume : 1.0); + LocalVector<int> erase_streams; + HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info; + for (const KeyValue<int, PlayingAudioStreamInfo> &M : map) { + PlayingAudioStreamInfo pasi = M.value; + + bool stop = false; + if (!t->audio_stream_playback->is_stream_playing(pasi.index)) { + stop = true; + } + if (!track_info.loop) { + if (!track_info.backward) { + if (track_info.time < pasi.start) { + stop = true; + } + } else if (track_info.backward) { + if (track_info.time > pasi.start) { + stop = true; + } + } + } + if (pasi.len > 0) { + double len = 0.0; + if (!track_info.backward) { + len = pasi.start > track_info.time ? (track_info.length - pasi.start) + track_info.time : track_info.time - pasi.start; + } else { + len = pasi.start < track_info.time ? (track_info.length - track_info.time) + pasi.start : pasi.start - track_info.time; + } + if (len > pasi.len) { + stop = true; + } + } + if (stop) { + // Time to stop. + t->audio_stream_playback->stop_stream(pasi.index); + erase_streams.push_back(M.key); + } else { + t->audio_stream_playback->set_stream_volume(pasi.index, db); + } + } + for (uint32_t erase_idx = 0; erase_idx < erase_streams.size(); erase_idx++) { + map.erase(erase_streams[erase_idx]); + } + if (map.size() == 0) { + erase_maps.push_back(L.key); + } + } + for (uint32_t erase_idx = 0; erase_idx < erase_maps.size(); erase_idx++) { + t->playing_streams.erase(erase_maps[erase_idx]); + } + } break; default: { } //the rest don't matter } @@ -1724,6 +1824,15 @@ void AnimationTree::_process_graph(double p_delta) { } } +Variant AnimationTree::post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) { + Variant res; + if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, const_cast<Object *>(p_object), p_object_idx, res)) { + return res; + } + + return _post_process_key_value(p_anim, p_track, p_value, p_object, p_object_idx); +} + Variant AnimationTree::_post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) { switch (p_anim->track_get_type(p_track)) { #ifndef _3D_DISABLED @@ -1786,9 +1895,11 @@ void AnimationTree::_setup_animation_player() { return; } + cache_valid = false; + AnimationPlayer *new_player = nullptr; if (!animation_player.is_empty()) { - new_player = Object::cast_to<AnimationPlayer>(get_node(animation_player)); + new_player = Object::cast_to<AnimationPlayer>(get_node_or_null(animation_player)); if (new_player && !new_player->is_connected("animation_list_changed", callable_mp(this, &AnimationTree::_animation_player_changed))) { new_player->connect("animation_list_changed", callable_mp(this, &AnimationTree::_animation_player_changed)); } @@ -1833,6 +1944,15 @@ NodePath AnimationTree::get_advance_expression_base_node() const { return advance_expression_base_node; } +void AnimationTree::set_audio_max_polyphony(int p_audio_max_polyphony) { + ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128); + audio_max_polyphony = p_audio_max_polyphony; +} + +int AnimationTree::get_audio_max_polyphony() const { + return audio_max_polyphony; +} + bool AnimationTree::is_state_invalid() const { return !state.valid; } @@ -1875,8 +1995,28 @@ NodePath AnimationTree::get_root_motion_track() const { return root_motion_track; } -Transform3D AnimationTree::get_root_motion_transform() const { - return root_motion_transform; +Vector3 AnimationTree::get_root_motion_position() const { + return root_motion_position; +} + +Quaternion AnimationTree::get_root_motion_rotation() const { + return root_motion_rotation; +} + +Vector3 AnimationTree::get_root_motion_scale() const { + return root_motion_scale; +} + +Vector3 AnimationTree::get_root_motion_position_accumulator() const { + return root_motion_position_accumulator; +} + +Quaternion AnimationTree::get_root_motion_rotation_accumulator() const { + return root_motion_rotation_accumulator; +} + +Vector3 AnimationTree::get_root_motion_scale_accumulator() const { + return root_motion_scale_accumulator; } void AnimationTree::_tree_changed() { @@ -1888,11 +2028,46 @@ void AnimationTree::_tree_changed() { properties_dirty = true; } +void AnimationTree::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) { + ERR_FAIL_COND(!property_reference_map.has(p_oid)); + String base_path = property_reference_map[p_oid]; + String old_base = base_path + p_old_name; + String new_base = base_path + p_new_name; + for (const PropertyInfo &E : properties) { + if (E.name.begins_with(old_base)) { + String new_name = E.name.replace_first(old_base, new_base); + property_map[new_name] = property_map[E.name]; + property_map.erase(E.name); + } + } + + //update tree second + properties_dirty = true; + _update_properties(); +} + +void AnimationTree::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) { + ERR_FAIL_COND(!property_reference_map.has(p_oid)); + String base_path = String(property_reference_map[p_oid]) + String(p_node); + for (const PropertyInfo &E : properties) { + if (E.name.begins_with(base_path)) { + property_map.erase(E.name); + } + } + + //update tree second + properties_dirty = true; + _update_properties(); +} + void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<AnimationNode> node) { ERR_FAIL_COND(node.is_null()); if (!property_parent_map.has(p_base_path)) { property_parent_map[p_base_path] = HashMap<StringName, StringName>(); } + if (!property_reference_map.has(node->get_instance_id())) { + property_reference_map[node->get_instance_id()] = p_base_path; + } if (node->get_input_count() && !input_activity_map.has(p_base_path)) { Vector<Activity> activity; @@ -1912,7 +2087,10 @@ void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<A StringName key = pinfo.name; if (!property_map.has(p_base_path + key)) { - property_map[p_base_path + key] = node->get_parameter_default_value(key); + Pair<Variant, bool> param; + param.first = node->get_parameter_default_value(key); + param.second = node->is_parameter_read_only(key); + property_map[p_base_path + key] = param; } property_parent_map[p_base_path][key] = p_base_path + key; @@ -1935,6 +2113,7 @@ void AnimationTree::_update_properties() { } properties.clear(); + property_reference_map.clear(); property_parent_map.clear(); input_activity_map.clear(); input_activity_map_get.clear(); @@ -1954,7 +2133,10 @@ bool AnimationTree::_set(const StringName &p_name, const Variant &p_value) { } if (property_map.has(p_name)) { - property_map[p_name] = p_value; + if (is_inside_tree() && property_map[p_name].second) { + return false; // Prevent to set property by user. + } + property_map[p_name].first = p_value; return true; } @@ -1967,7 +2149,7 @@ bool AnimationTree::_get(const StringName &p_name, Variant &r_ret) const { } if (property_map.has(p_name)) { - r_ret = property_map[p_name]; + r_ret = property_map[p_name].first; return true; } @@ -1984,20 +2166,6 @@ void AnimationTree::_get_property_list(List<PropertyInfo> *p_list) const { } } -void AnimationTree::rename_parameter(const String &p_base, const String &p_new_base) { - //rename values first - for (const PropertyInfo &E : properties) { - if (E.name.begins_with(p_base)) { - String new_name = E.name.replace_first(p_base, p_new_base); - property_map[new_name] = property_map[E.name]; - } - } - - //update tree second - properties_dirty = true; - _update_properties(); -} - real_t AnimationTree::get_connection_activity(const StringName &p_path, int p_connection) const { if (!input_activity_map_get.has(p_path)) { return 0; @@ -2034,20 +2202,30 @@ void AnimationTree::_bind_methods() { ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track); ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track); - ClassDB::bind_method(D_METHOD("get_root_motion_transform"), &AnimationTree::get_root_motion_transform); + ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationTree::set_audio_max_polyphony); + ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationTree::get_audio_max_polyphony); - ClassDB::bind_method(D_METHOD("_update_properties"), &AnimationTree::_update_properties); + ClassDB::bind_method(D_METHOD("get_root_motion_position"), &AnimationTree::get_root_motion_position); + ClassDB::bind_method(D_METHOD("get_root_motion_rotation"), &AnimationTree::get_root_motion_rotation); + ClassDB::bind_method(D_METHOD("get_root_motion_scale"), &AnimationTree::get_root_motion_scale); + ClassDB::bind_method(D_METHOD("get_root_motion_position_accumulator"), &AnimationTree::get_root_motion_position_accumulator); + ClassDB::bind_method(D_METHOD("get_root_motion_rotation_accumulator"), &AnimationTree::get_root_motion_rotation_accumulator); + ClassDB::bind_method(D_METHOD("get_root_motion_scale_accumulator"), &AnimationTree::get_root_motion_scale_accumulator); - ClassDB::bind_method(D_METHOD("rename_parameter", "old_name", "new_name"), &AnimationTree::rename_parameter); + ClassDB::bind_method(D_METHOD("_update_properties"), &AnimationTree::_update_properties); ClassDB::bind_method(D_METHOD("advance", "delta"), &AnimationTree::advance); + GDVIRTUAL_BIND(_post_process_key_value, "animation", "track", "value", "object", "object_idx"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode"), "set_tree_root", "get_tree_root"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "advance_expression_base_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node"), "set_advance_expression_base_node", "get_advance_expression_base_node"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_callback", "get_process_callback"); + ADD_GROUP("Audio", "audio_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony"); ADD_GROUP("Root Motion", "root_motion_"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track"); @@ -2056,6 +2234,10 @@ void AnimationTree::_bind_methods() { BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL); ADD_SIGNAL(MethodInfo("animation_player_changed")); + + // Signals from AnimationNodes. + ADD_SIGNAL(MethodInfo("animation_started", PropertyInfo(Variant::STRING_NAME, "anim_name"))); + ADD_SIGNAL(MethodInfo("animation_finished", PropertyInfo(Variant::STRING_NAME, "anim_name"))); } AnimationTree::AnimationTree() { |