diff options
Diffstat (limited to 'scene/animation')
-rw-r--r-- | scene/animation/animation_blend_tree.h | 2 | ||||
-rw-r--r-- | scene/animation/animation_node_state_machine.cpp | 575 | ||||
-rw-r--r-- | scene/animation/animation_node_state_machine.h | 39 | ||||
-rw-r--r-- | scene/animation/animation_player.cpp | 386 | ||||
-rw-r--r-- | scene/animation/animation_player.h | 74 | ||||
-rw-r--r-- | scene/animation/animation_tree.cpp | 171 | ||||
-rw-r--r-- | scene/animation/animation_tree.h | 18 | ||||
-rw-r--r-- | scene/animation/root_motion_view.cpp | 9 | ||||
-rw-r--r-- | scene/animation/tween.cpp | 149 | ||||
-rw-r--r-- | scene/animation/tween.h | 13 |
10 files changed, 1072 insertions, 364 deletions
diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index 2acacd7396..73bde633cb 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -354,7 +354,7 @@ class AnimationNodeBlendTree : public AnimationRootNode { Vector<StringName> connections; }; - Map<StringName, Node> nodes; + HashMap<StringName, Node> nodes; Vector2 graph_offset; diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 5ea7f4b7d9..81df12791c 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -185,27 +185,29 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_sta return true; //nothing to do } - loops_current = 0; // reset loops, so fade does not happen immediately - Vector2 current_pos = p_state_machine->states[current].position; Vector2 target_pos = p_state_machine->states[p_travel].position; - Map<StringName, AStarCost> cost_map; + HashMap<StringName, AStarCost> cost_map; List<int> open_list; //build open list for (int i = 0; i < p_state_machine->transitions.size(); i++) { - if (p_state_machine->transitions[i].from == current) { + if (p_state_machine->transitions[i].transition->is_disabled()) { + continue; + } + + if (p_state_machine->transitions[i].local_from == current) { open_list.push_back(i); - float cost = p_state_machine->states[p_state_machine->transitions[i].to].position.distance_to(current_pos); + float cost = p_state_machine->states[p_state_machine->transitions[i].local_to].position.distance_to(current_pos); cost *= p_state_machine->transitions[i].transition->get_priority(); AStarCost ap; ap.prev = current; ap.distance = cost; - cost_map[p_state_machine->transitions[i].to] = ap; + cost_map[p_state_machine->transitions[i].local_to] = ap; - if (p_state_machine->transitions[i].to == p_travel) { //prematurely found it! :D + if (p_state_machine->transitions[i].local_to == p_travel) { //prematurely found it! :D path.push_back(p_travel); return true; } @@ -224,8 +226,8 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_sta float least_cost = 1e20; for (List<int>::Element *E = open_list.front(); E; E = E->next()) { - float cost = cost_map[p_state_machine->transitions[E->get()].to].distance; - cost += p_state_machine->states[p_state_machine->transitions[E->get()].to].position.distance_to(target_pos); + float cost = cost_map[p_state_machine->transitions[E->get()].local_to].distance; + cost += p_state_machine->states[p_state_machine->transitions[E->get()].local_to].position.distance_to(target_pos); if (cost < least_cost) { least_cost_transition = E; @@ -233,34 +235,38 @@ bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_sta } } - StringName transition_prev = p_state_machine->transitions[least_cost_transition->get()].from; - StringName transition = p_state_machine->transitions[least_cost_transition->get()].to; + StringName transition_prev = p_state_machine->transitions[least_cost_transition->get()].local_from; + StringName transition = p_state_machine->transitions[least_cost_transition->get()].local_to; for (int i = 0; i < p_state_machine->transitions.size(); i++) { - if (p_state_machine->transitions[i].from != transition || p_state_machine->transitions[i].to == transition_prev) { + if (p_state_machine->transitions[i].transition->is_disabled()) { + continue; + } + + if (p_state_machine->transitions[i].local_from != transition || p_state_machine->transitions[i].local_to == transition_prev) { continue; //not interested on those } - float distance = p_state_machine->states[p_state_machine->transitions[i].from].position.distance_to(p_state_machine->states[p_state_machine->transitions[i].to].position); + float distance = p_state_machine->states[p_state_machine->transitions[i].local_from].position.distance_to(p_state_machine->states[p_state_machine->transitions[i].local_to].position); distance *= p_state_machine->transitions[i].transition->get_priority(); - distance += cost_map[p_state_machine->transitions[i].from].distance; + distance += cost_map[p_state_machine->transitions[i].local_from].distance; - if (cost_map.has(p_state_machine->transitions[i].to)) { + if (cost_map.has(p_state_machine->transitions[i].local_to)) { //oh this was visited already, can we win the cost? - if (distance < cost_map[p_state_machine->transitions[i].to].distance) { - cost_map[p_state_machine->transitions[i].to].distance = distance; - cost_map[p_state_machine->transitions[i].to].prev = p_state_machine->transitions[i].from; + if (distance < cost_map[p_state_machine->transitions[i].local_to].distance) { + cost_map[p_state_machine->transitions[i].local_to].distance = distance; + cost_map[p_state_machine->transitions[i].local_to].prev = p_state_machine->transitions[i].local_from; } } else { //add to open list AStarCost ac; - ac.prev = p_state_machine->transitions[i].from; + ac.prev = p_state_machine->transitions[i].local_from; ac.distance = distance; - cost_map[p_state_machine->transitions[i].to] = ac; + cost_map[p_state_machine->transitions[i].local_to] = ac; open_list.push_back(i); - if (p_state_machine->transitions[i].to == p_travel) { + if (p_state_machine->transitions[i].local_to == p_travel) { found_route = true; break; } @@ -352,7 +358,6 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, 1.0, AnimationNode::FILTER_IGNORE, false); pos_current = 0; - loops_current = 0; } if (!p_state_machine->states.has(current)) { @@ -388,12 +393,8 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s } { //advance and loop check - float next_pos = len_current - rem; - - if (next_pos < pos_current) { - loops_current++; - } + end_loop = next_pos < pos_current; pos_current = next_pos; //looped } @@ -404,7 +405,11 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s if (path.size()) { for (int i = 0; i < p_state_machine->transitions.size(); i++) { - if (p_state_machine->transitions[i].from == current && p_state_machine->transitions[i].to == path[0]) { + if (p_state_machine->transitions[i].transition->is_disabled()) { + continue; + } + + if (p_state_machine->transitions[i].local_from == current && p_state_machine->transitions[i].local_to == path[0]) { next_xfade = p_state_machine->transitions[i].transition->get_xfade_time(); switch_mode = p_state_machine->transitions[i].transition->get_switch_mode(); next = path[0]; @@ -413,17 +418,39 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s } else { float priority_best = 1e20; int auto_advance_to = -1; + for (int i = 0; i < p_state_machine->transitions.size(); i++) { - bool auto_advance = false; - if (p_state_machine->transitions[i].transition->has_auto_advance()) { - auto_advance = true; + if (p_state_machine->transitions[i].transition->is_disabled()) { + continue; } - StringName advance_condition_name = p_state_machine->transitions[i].transition->get_advance_condition_name(); - if (advance_condition_name != StringName() && bool(p_state_machine->get_parameter(advance_condition_name))) { - auto_advance = true; + + // handles end_node: when end_node is reached in a sub state machine, find and activate the current_transition + if (force_auto_advance) { + if (p_state_machine->transitions[i].from == current_transition.from && p_state_machine->transitions[i].to == current_transition.to) { + auto_advance_to = i; + force_auto_advance = false; + break; + } } - if (p_state_machine->transitions[i].from == current && auto_advance) { + // handles start_node: if previous state machine is pointing to a node inside the current state machine, starts the current machine from start_node to prev_local_to + if (p_state_machine->start_node == current && p_state_machine->transitions[i].local_from == current) { + if (p_state_machine->prev_state_machine.is_valid()) { + Ref<AnimationNodeStateMachinePlayback> prev_playback = p_state_machine->prev_state_machine->get_parameter("playback"); + + if (prev_playback.is_valid()) { + StringName prev_local_to = String(prev_playback->current_transition.next).replace_first(String(p_state_machine->state_machine_name) + "/", ""); + + if (p_state_machine->transitions[i].to == prev_local_to) { + auto_advance_to = i; + prev_playback->current_transition.next = StringName(); + break; + } + } + } + } + + if (p_state_machine->transitions[i].from == current && _check_advance_condition(p_state_machine, p_state_machine->transitions[i].transition)) { if (p_state_machine->transitions[i].transition->get_priority() <= priority_best) { priority_best = p_state_machine->transitions[i].transition->get_priority(); auto_advance_to = i; @@ -432,26 +459,69 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s } if (auto_advance_to != -1) { - next = p_state_machine->transitions[auto_advance_to].to; + next = p_state_machine->transitions[auto_advance_to].local_to; + Transition tr; + tr.from = String(p_state_machine->state_machine_name) + "/" + String(p_state_machine->transitions[auto_advance_to].from); + tr.to = String(p_state_machine->transitions[auto_advance_to].to).replace_first("../", ""); + tr.next = p_state_machine->transitions[auto_advance_to].to; + current_transition = tr; next_xfade = p_state_machine->transitions[auto_advance_to].transition->get_xfade_time(); switch_mode = p_state_machine->transitions[auto_advance_to].transition->get_switch_mode(); } } + if (next == p_state_machine->end_node) { + Ref<AnimationNodeStateMachine> prev_state_machine = p_state_machine->prev_state_machine; + + if (prev_state_machine.is_valid()) { + Ref<AnimationNodeStateMachinePlayback> prev_playback = prev_state_machine->get_parameter("playback"); + + if (prev_playback.is_valid()) { + if (next_xfade) { + prev_playback->current_transition = current_transition; + prev_playback->force_auto_advance = true; + + return rem; + } + float priority_best = 1e20; + int auto_advance_to = -1; + + for (int i = 0; i < prev_state_machine->transitions.size(); i++) { + if (prev_state_machine->transitions[i].transition->is_disabled()) { + continue; + } + + if (current_transition.next == prev_state_machine->end_node && _check_advance_condition(prev_state_machine, prev_state_machine->transitions[i].transition)) { + if (prev_state_machine->transitions[i].transition->get_priority() <= priority_best) { + priority_best = prev_state_machine->transitions[i].transition->get_priority(); + auto_advance_to = i; + } + } + } + + if (auto_advance_to != -1) { + if (prev_state_machine->transitions[auto_advance_to].transition->get_xfade_time()) { + return rem; + } + } + } + } + } + //if next, see when to transition if (next != StringName()) { bool goto_next = false; if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_AT_END) { - goto_next = next_xfade >= (len_current - pos_current) || loops_current > 0; - if (loops_current > 0) { + goto_next = next_xfade >= (len_current - pos_current) || end_loop; + if (end_loop) { next_xfade = 0; } } else { goto_next = fading_from == StringName(); } - if (goto_next) { //loops should be used because fade time may be too small or zero and animation may have looped + if (goto_next) { //end_loop should be used because fade time may be too small or zero and animation may have looped if (next_xfade) { //time to fade, baby @@ -478,18 +548,38 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s } rem = len_current; //so it does not show 0 on transition - loops_current = 0; } } - //compute time left for transitions by using the end node - if (p_state_machine->end_node != StringName() && p_state_machine->end_node != current) { - rem = p_state_machine->blend_node(p_state_machine->end_node, p_state_machine->states[p_state_machine->end_node].node, 0, true, 0, AnimationNode::FILTER_IGNORE, false); + // time left must always be 1 because the end node don't lenght to compute + if (p_state_machine->end_node != current) { + rem = 1; + } else { + Ref<AnimationNodeStateMachinePlayback> prev_playback = p_state_machine->prev_state_machine->get_parameter("playback"); + + if (prev_playback.is_valid()) { + prev_playback->current_transition = current_transition; + prev_playback->force_auto_advance = true; + } } return rem; } +bool AnimationNodeStateMachinePlayback::_check_advance_condition(const Ref<AnimationNodeStateMachine> state_machine, const Ref<AnimationNodeStateMachineTransition> transition) const { + if (transition->has_auto_advance()) { + return true; + } + + StringName advance_condition_name = transition->get_advance_condition_name(); + + if (advance_condition_name != StringName() && bool(state_machine->get_parameter(advance_condition_name))) { + return true; + } + + return false; +} + void AnimationNodeStateMachinePlayback::_bind_methods() { ClassDB::bind_method(D_METHOD("travel", "to_node"), &AnimationNodeStateMachinePlayback::travel); ClassDB::bind_method(D_METHOD("start", "node"), &AnimationNodeStateMachinePlayback::start); @@ -521,6 +611,23 @@ void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) c for (const StringName &E : advance_conditions) { r_list->push_back(PropertyInfo(Variant::BOOL, E)); } + + // for (const KeyValue<StringName, State> &E : states) { + // if (E->node == ansm) { + // for (int i = 0; i < E->node->transitions.size(); i++) { + // StringName ac = E->node->transitions[i].transition->get_advance_condition_name(); + // if (ac != StringName() && advance_conditions.find(ac) == nullptr) { + // advance_conditions.push_back(ac); + // } + // } + + // advance_conditions.sort_custom<StringName::AlphCompare>(); + + // for (const StringName &E : advance_conditions) { + // r_list->push_back(PropertyInfo(Variant::BOOL, E)); + // } + // } + // } } Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName &p_parameter) const { @@ -544,6 +651,13 @@ void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<Animation states[p_name] = state; + Ref<AnimationNodeStateMachine> anodesm = p_node; + + if (anodesm.is_valid()) { + anodesm->state_machine_name = p_name; + anodesm->prev_state_machine = (Ref<AnimationNodeStateMachine>)this; + } + emit_changed(); emit_signal(SNAME("tree_changed")); @@ -570,6 +684,14 @@ void AnimationNodeStateMachine::replace_node(const StringName &p_name, Ref<Anima p_node->connect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed), varray(), CONNECT_REFERENCE_COUNTED); } +bool AnimationNodeStateMachine::can_edit_node(const StringName &p_name) const { + if (states.has(p_name)) { + return !(states[p_name].node->is_class("AnimationNodeStartState") || states[p_name].node->is_class("AnimationNodeEndState")); + } + + return true; +} + Ref<AnimationNode> AnimationNodeStateMachine::get_node(const StringName &p_name) const { ERR_FAIL_COND_V(!states.has(p_name), Ref<AnimationNode>()); @@ -610,36 +732,24 @@ bool AnimationNodeStateMachine::has_node(const StringName &p_name) const { void AnimationNodeStateMachine::remove_node(const StringName &p_name) { ERR_FAIL_COND(!states.has(p_name)); - { - Ref<AnimationNode> node = states[p_name].node; - - ERR_FAIL_COND(node.is_null()); - - node->disconnect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); + if (!can_edit_node(p_name)) { + return; } - states.erase(p_name); - //path.erase(p_name); - for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].from == p_name || transitions[i].to == p_name) { - transitions.write[i].transition->disconnect("advance_condition_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); - transitions.remove_at(i); + if (transitions[i].local_from == p_name || transitions[i].local_to == p_name) { + remove_transition_by_index(i); i--; } } - if (start_node == p_name) { - start_node = StringName(); - } - - if (end_node == p_name) { - end_node = StringName(); + { + Ref<AnimationNode> node = states[p_name].node; + ERR_FAIL_COND(node.is_null()); + node->disconnect("tree_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); } - /*if (playing && current == p_name) { - stop(); - }*/ + states.erase(p_name); emit_changed(); emit_signal(SNAME("tree_changed")); @@ -648,39 +758,73 @@ void AnimationNodeStateMachine::remove_node(const StringName &p_name) { void AnimationNodeStateMachine::rename_node(const StringName &p_name, const StringName &p_new_name) { ERR_FAIL_COND(!states.has(p_name)); ERR_FAIL_COND(states.has(p_new_name)); + ERR_FAIL_COND(!can_edit_node(p_name)); states[p_new_name] = states[p_name]; states.erase(p_name); + Ref<AnimationNodeStateMachine> anodesm = states[p_new_name].node; + if (anodesm.is_valid()) { + anodesm->state_machine_name = p_new_name; + } + for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].from == p_name) { - transitions.write[i].from = p_new_name; + if (transitions[i].local_from == p_name) { + _rename_transition(transitions[i].from, String(transitions[i].from).replace_first(p_name, p_new_name)); } - if (transitions[i].to == p_name) { - transitions.write[i].to = p_new_name; + if (transitions[i].local_to == p_name) { + _rename_transition(transitions[i].to, String(transitions[i].to).replace_first(p_name, p_new_name)); } } - if (start_node == p_name) { - start_node = p_new_name; - } + emit_signal("tree_changed"); +} - if (end_node == p_name) { - end_node = p_new_name; +void AnimationNodeStateMachine::_rename_transition(const StringName &p_name, const StringName &p_new_name) { + if (updating_transitions) { + return; } - /*if (playing && current == p_name) { - current = p_new_name; - }*/ + updating_transitions = true; + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].from == p_name) { + Vector<String> path = String(transitions[i].to).split("/"); + if (path.size() > 1) { + if (path[0] == "..") { + prev_state_machine->_rename_transition(String(state_machine_name) + "/" + p_name, String(state_machine_name) + "/" + p_new_name); + } else { + ((Ref<AnimationNodeStateMachine>)states[transitions[i].local_to].node)->_rename_transition("../" + p_name, "../" + p_new_name); + } + } + + transitions.write[i].from = p_new_name; + } - //path.clear(); //clear path - emit_signal(SNAME("tree_changed")); + if (transitions[i].to == p_name) { + Vector<String> path = String(transitions[i].from).split("/"); + if (path.size() > 1) { + if (path[0] == "..") { + prev_state_machine->_rename_transition(String(state_machine_name) + "/" + p_name, String(state_machine_name) + "/" + p_new_name); + } else { + ((Ref<AnimationNodeStateMachine>)states[transitions[i].local_from].node)->_rename_transition("../" + p_name, "../" + p_new_name); + } + } + + transitions.write[i].to = p_new_name; + } + + updating_transitions = false; + } } void AnimationNodeStateMachine::get_node_list(List<StringName> *r_nodes) const { List<StringName> nodes; for (const KeyValue<StringName, State> &E : states) { + if (E.key == end_node && !prev_state_machine.is_valid()) { + continue; + } + nodes.push_back(E.key); } nodes.sort_custom<StringName::AlphCompare>(); @@ -690,9 +834,16 @@ void AnimationNodeStateMachine::get_node_list(List<StringName> *r_nodes) const { } } +Ref<AnimationNodeStateMachine> AnimationNodeStateMachine::get_prev_state_machine() const { + return prev_state_machine; +} + bool AnimationNodeStateMachine::has_transition(const StringName &p_from, const StringName &p_to) const { + StringName from = _get_shortest_path(p_from); + StringName to = _get_shortest_path(p_to); + for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].from == p_from && transitions[i].to == p_to) { + if (transitions[i].from == from && transitions[i].to == to) { return true; } } @@ -700,32 +851,148 @@ bool AnimationNodeStateMachine::has_transition(const StringName &p_from, const S } int AnimationNodeStateMachine::find_transition(const StringName &p_from, const StringName &p_to) const { + StringName from = _get_shortest_path(p_from); + StringName to = _get_shortest_path(p_to); + for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].from == p_from && transitions[i].to == p_to) { + if (transitions[i].from == from && transitions[i].to == to) { return i; } } return -1; } +bool AnimationNodeStateMachine::_can_connect(const StringName &p_name, Vector<Ref<AnimationNodeStateMachine>> p_parents) const { + if (p_parents.is_empty()) { + Ref<AnimationNodeStateMachine> prev = (Ref<AnimationNodeStateMachine>)this; + while (prev.is_valid()) { + p_parents.push_back(prev); + prev = prev->prev_state_machine; + } + } + + if (states.has(p_name)) { + Ref<AnimationNodeStateMachine> anodesm = states[p_name].node; + + if (anodesm.is_valid() && p_parents.find(anodesm) != -1) { + return false; + } + + return true; + } + + String name = p_name; + Vector<String> path = name.split("/"); + + if (path.size() < 2) { + return false; + } + + if (path[0] == "..") { + if (prev_state_machine.is_valid()) { + return prev_state_machine->_can_connect(name.replace_first("../", ""), p_parents); + } + } else if (states.has(path[0])) { + Ref<AnimationNodeStateMachine> anodesm = states[path[0]].node; + if (anodesm.is_valid()) { + return anodesm->_can_connect(name.replace_first(path[0] + "/", ""), p_parents); + } + } + + return false; +} + +StringName AnimationNodeStateMachine::_get_shortest_path(const StringName &p_path) const { + // If p_path is something like StateMachine/../StateMachine2/State1, + // the result will be StateMachine2/State1. This avoid duplicate + // transitions when using add_transition. eg, this two calls is the same: + // + // add_transition("State1", "StateMachine/../State2", tr) + // add_transition("State1", "State2", tr) + // + // but the second call must be invalid because the transition already exists + + Vector<String> path = String(p_path).split("/"); + Vector<String> new_path; + + for (int i = 0; i < path.size(); i++) { + if (i > 0 && path[i] == ".." && new_path[i - 1] != "..") { + new_path.remove_at(i - 1); + } else { + new_path.push_back(path[i]); + } + } + + String result; + for (int i = 0; i < new_path.size(); i++) { + result += new_path[i] + "/"; + } + result.remove_at(result.length() - 1); + + return result; +} + void AnimationNodeStateMachine::add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition) { - ERR_FAIL_COND(p_from == p_to); - ERR_FAIL_COND(!states.has(p_from)); - ERR_FAIL_COND(!states.has(p_to)); + if (updating_transitions) { + return; + } + + StringName from = _get_shortest_path(p_from); + StringName to = _get_shortest_path(p_to); + Vector<String> path_from = String(from).split("/"); + Vector<String> path_to = String(to).split("/"); + + ERR_FAIL_COND(from == end_node || to == start_node); + ERR_FAIL_COND(from == to); + ERR_FAIL_COND(!_can_connect(from)); + ERR_FAIL_COND(!_can_connect(to)); ERR_FAIL_COND(p_transition.is_null()); for (int i = 0; i < transitions.size(); i++) { - ERR_FAIL_COND(transitions[i].from == p_from && transitions[i].to == p_to); + ERR_FAIL_COND(transitions[i].from == from && transitions[i].to == to); + } + + if (path_from.size() > 1 || path_to.size() > 1) { + ERR_FAIL_COND(path_from[0] == path_to[0]); } + updating_transitions = true; + + StringName local_from = String(from).get_slicec('/', 0); + StringName local_to = String(to).get_slicec('/', 0); + local_from = local_from == ".." ? "Start" : local_from; + local_to = local_to == ".." ? "End" : local_to; + Transition tr; - tr.from = p_from; - tr.to = p_to; + tr.from = from; + tr.to = to; + tr.local_from = local_from; + tr.local_to = local_to; tr.transition = p_transition; tr.transition->connect("advance_condition_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed), varray(), CONNECT_REFERENCE_COUNTED); transitions.push_back(tr); + + // do recursive + if (path_from.size() > 1) { + StringName local_path = String(from).replace_first(path_from[0] + "/", ""); + if (path_from[0] == "..") { + prev_state_machine->add_transition(local_path, String(state_machine_name) + "/" + to, p_transition); + } else { + ((Ref<AnimationNodeStateMachine>)states[path_from[0]].node)->add_transition(local_path, "../" + to, p_transition); + } + } + if (path_to.size() > 1) { + StringName local_path = String(to).replace_first(path_to[0] + "/", ""); + if (path_to[0] == "..") { + prev_state_machine->add_transition(String(state_machine_name) + "/" + from, local_path, p_transition); + } else { + ((Ref<AnimationNodeStateMachine>)states[path_to[0]].node)->add_transition("../" + from, local_path, p_transition); + } + } + + updating_transitions = false; } Ref<AnimationNodeStateMachineTransition> AnimationNodeStateMachine::get_transition(int p_transition) const { @@ -748,44 +1015,52 @@ int AnimationNodeStateMachine::get_transition_count() const { } void AnimationNodeStateMachine::remove_transition(const StringName &p_from, const StringName &p_to) { + StringName from = _get_shortest_path(p_from); + StringName to = _get_shortest_path(p_to); + for (int i = 0; i < transitions.size(); i++) { - if (transitions[i].from == p_from && transitions[i].to == p_to) { - transitions.write[i].transition->disconnect("advance_condition_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); - transitions.remove_at(i); + if (transitions[i].from == from && transitions[i].to == to) { + remove_transition_by_index(i); return; } } - - /*if (playing) { - path.clear(); - }*/ } -void AnimationNodeStateMachine::remove_transition_by_index(int p_transition) { +void AnimationNodeStateMachine::remove_transition_by_index(const int p_transition) { ERR_FAIL_INDEX(p_transition, transitions.size()); + Transition tr = transitions[p_transition]; transitions.write[p_transition].transition->disconnect("advance_condition_changed", callable_mp(this, &AnimationNodeStateMachine::_tree_changed)); transitions.remove_at(p_transition); - /*if (playing) { - path.clear(); - }*/ -} -void AnimationNodeStateMachine::set_start_node(const StringName &p_node) { - ERR_FAIL_COND(p_node != StringName() && !states.has(p_node)); - start_node = p_node; -} + Vector<String> path_from = String(tr.from).split("/"); + Vector<String> path_to = String(tr.to).split("/"); -String AnimationNodeStateMachine::get_start_node() const { - return start_node; -} + List<Vector<String>> paths; + paths.push_back(path_from); + paths.push_back(path_to); -void AnimationNodeStateMachine::set_end_node(const StringName &p_node) { - ERR_FAIL_COND(p_node != StringName() && !states.has(p_node)); - end_node = p_node; + for (List<Vector<String>>::Element *E = paths.front(); E; E = E->next()) { + if (E->get()[0].size() > 1) { + if (E->get()[0] == "..") { + prev_state_machine->_remove_transition(tr.transition); + } else if (states.has(E->get()[0])) { + Ref<AnimationNodeStateMachine> anodesm = states[E->get()[0]].node; + + if (anodesm.is_valid()) { + anodesm->_remove_transition(tr.transition); + } + } + } + } } -String AnimationNodeStateMachine::get_end_node() const { - return end_node; +void AnimationNodeStateMachine::_remove_transition(const Ref<AnimationNodeStateMachineTransition> p_transition) { + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].transition == p_transition) { + remove_transition_by_index(i); + return; + } + } } void AnimationNodeStateMachine::set_graph_offset(const Vector2 &p_offset) { @@ -807,6 +1082,18 @@ String AnimationNodeStateMachine::get_caption() const { return "StateMachine"; } +bool AnimationNodeStateMachine::has_local_transition(const StringName &p_from, const StringName &p_to) const { + StringName from = _get_shortest_path(p_from); + StringName to = _get_shortest_path(p_to); + + for (int i = 0; i < transitions.size(); i++) { + if (transitions[i].local_from == from && transitions[i].local_to == to) { + return true; + } + } + return false; +} + Ref<AnimationNode> AnimationNodeStateMachine::get_child_by_name(const StringName &p_name) { return get_node(p_name); } @@ -839,12 +1126,6 @@ bool AnimationNodeStateMachine::_set(const StringName &p_name, const Variant &p_ add_transition(trans[i], trans[i + 1], trans[i + 2]); } return true; - } else if (name == "start_node") { - set_start_node(p_value); - return true; - } else if (name == "end_node") { - set_end_node(p_value); - return true; } else if (name == "graph_offset") { set_graph_offset(p_value); return true; @@ -860,7 +1141,7 @@ bool AnimationNodeStateMachine::_get(const StringName &p_name, Variant &r_ret) c String what = name.get_slicec('/', 2); if (what == "node") { - if (states.has(node_name)) { + if (states.has(node_name) && can_edit_node(node_name)) { r_ret = states[node_name].node; return true; } @@ -874,22 +1155,21 @@ bool AnimationNodeStateMachine::_get(const StringName &p_name, Variant &r_ret) c } } else if (name == "transitions") { Array trans; - trans.resize(transitions.size() * 3); - for (int i = 0; i < transitions.size(); i++) { - trans[i * 3 + 0] = transitions[i].from; - trans[i * 3 + 1] = transitions[i].to; - trans[i * 3 + 2] = transitions[i].transition; + String from = transitions[i].from; + String to = transitions[i].to; + + if (from.get_slicec('/', 0) == ".." || to.get_slicec('/', 0) == "..") { + continue; + } + + trans.push_back(from); + trans.push_back(to); + trans.push_back(transitions[i].transition); } r_ret = trans; return true; - } else if (name == "start_node") { - r_ret = get_start_node(); - return true; - } else if (name == "end_node") { - r_ret = get_end_node(); - return true; } else if (name == "graph_offset") { r_ret = get_graph_offset(); return true; @@ -911,8 +1191,6 @@ void AnimationNodeStateMachine::_get_property_list(List<PropertyInfo> *p_list) c } p_list->push_back(PropertyInfo(Variant::ARRAY, "transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::STRING_NAME, "start_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::STRING_NAME, "end_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); p_list->push_back(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } @@ -920,10 +1198,24 @@ void AnimationNodeStateMachine::reset_state() { states.clear(); transitions.clear(); playback = "playback"; - start_node = StringName(); - end_node = StringName(); + start_node = "Start"; + end_node = "End"; graph_offset = Vector2(); + Ref<AnimationNodeStartState> s; + s.instantiate(); + State start; + start.node = s; + start.position = Vector2(200, 100); + states[start_node] = start; + + Ref<AnimationNodeEndState> e; + e.instantiate(); + State end; + end.node = e; + end.position = Vector2(900, 100); + states[end_node] = end; + emit_changed(); emit_signal(SNAME("tree_changed")); } @@ -963,15 +1255,22 @@ void AnimationNodeStateMachine::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_transition_by_index", "idx"), &AnimationNodeStateMachine::remove_transition_by_index); ClassDB::bind_method(D_METHOD("remove_transition", "from", "to"), &AnimationNodeStateMachine::remove_transition); - ClassDB::bind_method(D_METHOD("set_start_node", "name"), &AnimationNodeStateMachine::set_start_node); - ClassDB::bind_method(D_METHOD("get_start_node"), &AnimationNodeStateMachine::get_start_node); - - ClassDB::bind_method(D_METHOD("set_end_node", "name"), &AnimationNodeStateMachine::set_end_node); - ClassDB::bind_method(D_METHOD("get_end_node"), &AnimationNodeStateMachine::get_end_node); - ClassDB::bind_method(D_METHOD("set_graph_offset", "offset"), &AnimationNodeStateMachine::set_graph_offset); ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeStateMachine::get_graph_offset); } AnimationNodeStateMachine::AnimationNodeStateMachine() { + Ref<AnimationNodeStartState> s; + s.instantiate(); + State start; + start.node = s; + start.position = Vector2(200, 100); + states[start_node] = start; + + Ref<AnimationNodeEndState> e; + e.instantiate(); + State end; + end.node = e; + end.position = Vector2(900, 100); + states[end_node] = end; } diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index 3bae0fcffa..07d0579533 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -93,13 +93,19 @@ class AnimationNodeStateMachinePlayback : public Resource { StringName prev; }; - float len_total = 0.0; + struct Transition { + StringName from; + StringName to; + StringName next; + }; float len_current = 0.0; float pos_current = 0.0; - int loops_current = 0; + bool end_loop = false; StringName current; + Transition current_transition; + bool force_auto_advance = false; StringName fading_from; float fading_time = 0.0; @@ -116,6 +122,8 @@ class AnimationNodeStateMachinePlayback : public Resource { double process(AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek); + bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const; + protected: static void _bind_methods(); @@ -144,24 +152,30 @@ private: Vector2 position; }; - Map<StringName, State> states; + HashMap<StringName, State> states; struct Transition { StringName from; StringName to; + StringName local_from; + StringName local_to; Ref<AnimationNodeStateMachineTransition> transition; }; Vector<Transition> transitions; StringName playback = "playback"; - - StringName start_node; - StringName end_node; + StringName state_machine_name; + Ref<AnimationNodeStateMachine> prev_state_machine; + bool updating_transitions = false; Vector2 graph_offset; void _tree_changed(); + void _remove_transition(const Ref<AnimationNodeStateMachineTransition> p_transition); + void _rename_transition(const StringName &p_name, const StringName &p_new_name); + bool _can_connect(const StringName &p_name, const Vector<Ref<AnimationNodeStateMachine>> p_parents = Vector<Ref<AnimationNodeStateMachine>>()) const; + StringName _get_shortest_path(const StringName &p_path) const; protected: static void _bind_methods(); @@ -169,10 +183,14 @@ protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; + bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const; virtual void reset_state() override; public: + StringName start_node = "Start"; + StringName end_node = "End"; + virtual void get_parameter_list(List<PropertyInfo> *r_list) const override; virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; @@ -191,20 +209,19 @@ public: virtual void get_child_nodes(List<ChildNode> *r_child_nodes) override; bool has_transition(const StringName &p_from, const StringName &p_to) const; + bool has_local_transition(const StringName &p_from, const StringName &p_to) const; int find_transition(const StringName &p_from, const StringName &p_to) const; void add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition); Ref<AnimationNodeStateMachineTransition> get_transition(int p_transition) const; StringName get_transition_from(int p_transition) const; StringName get_transition_to(int p_transition) const; int get_transition_count() const; - void remove_transition_by_index(int p_transition); + void remove_transition_by_index(const int p_transition); void remove_transition(const StringName &p_from, const StringName &p_to); - void set_start_node(const StringName &p_node); - String get_start_node() const; + bool can_edit_node(const StringName &p_name) const; - void set_end_node(const StringName &p_node); - String get_end_node() const; + Ref<AnimationNodeStateMachine> get_prev_state_machine() const; void set_graph_offset(const Vector2 &p_offset); Vector2 get_graph_offset() const; diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 1ab2e2419e..9ace6db505 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -83,8 +83,31 @@ bool AnimationPlayer::_set(const StringName &p_name, const Variant &p_value) { set_current_animation(p_value); } else if (name.begins_with("anims/")) { + // Backwards compatibility with 3.x, add them to "default" library. String which = name.get_slicec('/', 1); - add_animation(which, p_value); + + Ref<Animation> anim = p_value; + Ref<AnimationLibrary> al; + if (!has_animation_library(StringName())) { + al.instantiate(); + add_animation_library(StringName(), al); + } else { + al = get_animation_library(StringName()); + } + al->add_animation(which, anim); + + } else if (name.begins_with("libraries")) { + Dictionary d = p_value; + while (animation_libraries.size()) { + remove_animation_library(animation_libraries[0].name); + } + List<Variant> keys; + d.get_key_list(&keys); + for (const Variant &K : keys) { + StringName lib_name = K; + Ref<AnimationLibrary> lib = d[lib_name]; + add_animation_library(lib_name, lib); + } } else if (name.begins_with("next/")) { String which = name.get_slicec('/', 1); @@ -117,9 +140,13 @@ bool AnimationPlayer::_get(const StringName &p_name, Variant &r_ret) const { r_ret = get_current_animation(); - } else if (name.begins_with("anims/")) { - String which = name.get_slicec('/', 1); - r_ret = get_animation(which); + } else if (name.begins_with("libraries")) { + Dictionary d; + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + d[animation_libraries[i].name] = animation_libraries[i].library; + } + + r_ret = d; } else if (name.begins_with("next/")) { String which = name.get_slicec('/', 1); @@ -136,7 +163,7 @@ bool AnimationPlayer::_get(const StringName &p_name, Variant &r_ret) const { for (int i = 0; i < keys.size(); i++) { array.push_back(keys[i].from); array.push_back(keys[i].to); - array.push_back(blend_times[keys[i]]); + array.push_back(blend_times.get(keys[i])); } r_ret = array; @@ -173,8 +200,9 @@ void AnimationPlayer::_validate_property(PropertyInfo &property) const { void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const { List<PropertyInfo> anim_names; + anim_names.push_back(PropertyInfo(Variant::DICTIONARY, "libraries")); + for (const KeyValue<StringName, AnimationData> &E : animation_set) { - anim_names.push_back(PropertyInfo(Variant::OBJECT, "anims/" + String(E.key), PROPERTY_HINT_RESOURCE_TYPE, "Animation", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE)); if (E.value.next != StringName()) { anim_names.push_back(PropertyInfo(Variant::STRING, "next/" + String(E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); } @@ -256,7 +284,7 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov for (int i = 0; i < a->get_track_count(); i++) { p_anim->node_cache.write[i] = nullptr; - RES resource; + Ref<Resource> resource; Vector<StringName> leftover_path; Node *child = parent->get_node_and_resource(a->track_get_path(i), resource, leftover_path); ERR_CONTINUE_MSG(!child, "On Animation: '" + p_anim->name + "', couldn't resolve track: '" + String(a->track_get_path(i)) + "'."); // couldn't find the child node @@ -560,10 +588,10 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double //StringName property=a->track_get_path(i).get_property(); - Map<StringName, TrackNodeCache::PropertyAnim>::Element *E = nc->property_anim.find(a->track_get_path(i).get_concatenated_subnames()); + HashMap<StringName, TrackNodeCache::PropertyAnim>::Iterator E = nc->property_anim.find(a->track_get_path(i).get_concatenated_subnames()); ERR_CONTINUE(!E); //should it continue, or create a new one? - TrackNodeCache::PropertyAnim *pa = &E->get(); + TrackNodeCache::PropertyAnim *pa = &E->value; Animation::UpdateMode update_mode = a->value_track_get_update_mode(i); @@ -710,10 +738,10 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double continue; } - Map<StringName, TrackNodeCache::BezierAnim>::Element *E = nc->bezier_anim.find(a->track_get_path(i).get_concatenated_subnames()); + HashMap<StringName, TrackNodeCache::BezierAnim>::Iterator E = nc->bezier_anim.find(a->track_get_path(i).get_concatenated_subnames()); ERR_CONTINUE(!E); //should it continue, or create a new one? - TrackNodeCache::BezierAnim *ba = &E->get(); + TrackNodeCache::BezierAnim *ba = &E->value; real_t bezier = a->bezier_track_interpolate(i, p_time); if (ba->accum_pass != accum_pass) { @@ -1155,71 +1183,106 @@ void AnimationPlayer::_animation_process(double p_delta) { } } -Error AnimationPlayer::add_animation(const StringName &p_name, const Ref<Animation> &p_animation) { -#ifdef DEBUG_ENABLED - ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + "."); -#endif +void AnimationPlayer::_animation_set_cache_update() { + // Relatively fast function to update all animations. - ERR_FAIL_COND_V(p_animation.is_null(), ERR_INVALID_PARAMETER); + animation_set_update_pass++; + bool clear_cache_needed = false; - if (animation_set.has(p_name)) { - _unref_anim(animation_set[p_name].animation); - animation_set[p_name].animation = p_animation; - clear_caches(); - } else { - AnimationData ad; - ad.animation = p_animation; - ad.name = p_name; - animation_set[p_name] = ad; - } + // Update changed and add otherwise + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + for (const KeyValue<StringName, Ref<Animation>> &K : animation_libraries[i].library->animations) { + StringName key = animation_libraries[i].name == StringName() ? K.key : StringName(String(animation_libraries[i].name) + "/" + String(K.key)); + if (!animation_set.has(key)) { + AnimationData ad; + ad.animation = K.value; + ad.animation_library = animation_libraries[i].name; + ad.name = key; + ad.last_update = animation_set_update_pass; + animation_set.insert(ad.name, ad); + } else { + AnimationData &ad = animation_set[key]; + if (ad.last_update != animation_set_update_pass) { + // Was not updated, update. If the animation is duplicated, the second one will be ignored. + if (ad.animation != K.value || ad.animation_library != animation_libraries[i].name) { + // Animation changed, update and clear caches. + clear_cache_needed = true; + ad.animation = K.value; + ad.animation_library = animation_libraries[i].name; + } - _ref_anim(p_animation); - notify_property_list_changed(); - return OK; -} + ad.last_update = animation_set_update_pass; + } + } + } + } -void AnimationPlayer::remove_animation(const StringName &p_name) { - ERR_FAIL_COND(!animation_set.has(p_name)); + // Check removed + List<StringName> to_erase; + for (const KeyValue<StringName, AnimationData> &E : animation_set) { + if (E.value.last_update != animation_set_update_pass) { + // Was not updated, must be erased + to_erase.push_back(E.key); + clear_cache_needed = true; + } + } - stop(); - _unref_anim(animation_set[p_name].animation); - animation_set.erase(p_name); + while (to_erase.size()) { + animation_set.erase(to_erase.front()->get()); + to_erase.pop_front(); + } - clear_caches(); - notify_property_list_changed(); + if (clear_cache_needed) { + // If something was modified or removed, caches need to be cleared + clear_caches(); + } } -void AnimationPlayer::_ref_anim(const Ref<Animation> &p_anim) { - Ref<Animation>(p_anim)->connect(SceneStringNames::get_singleton()->tracks_changed, callable_mp(this, &AnimationPlayer::_animation_changed), varray(), CONNECT_REFERENCE_COUNTED); -} +void AnimationPlayer::_animation_added(const StringName &p_name, const StringName &p_library) { + _animation_set_cache_update(); -void AnimationPlayer::_unref_anim(const Ref<Animation> &p_anim) { - Ref<Animation>(p_anim)->disconnect(SceneStringNames::get_singleton()->tracks_changed, callable_mp(this, &AnimationPlayer::_animation_changed)); + update_configuration_warnings(); } -void AnimationPlayer::rename_animation(const StringName &p_name, const StringName &p_new_name) { - ERR_FAIL_COND(!animation_set.has(p_name)); - ERR_FAIL_COND(String(p_new_name).contains("/") || String(p_new_name).contains(":")); - ERR_FAIL_COND(animation_set.has(p_new_name)); +void AnimationPlayer::_animation_removed(const StringName &p_name, const StringName &p_library) { + StringName name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); - stop(); - AnimationData ad = animation_set[p_name]; - ad.name = p_new_name; - animation_set.erase(p_name); - animation_set[p_new_name] = ad; + if (!animation_set.has(name)) { + return; // No need to update because not the one from the library being used. + } + _animation_set_cache_update(); + + // Erase blends if needed + List<BlendKey> to_erase; + for (const KeyValue<BlendKey, float> &E : blend_times) { + BlendKey bk = E.key; + if (bk.from == name || bk.to == name) { + to_erase.push_back(bk); + } + } + + while (to_erase.size()) { + blend_times.erase(to_erase.front()->get()); + to_erase.pop_front(); + } + + update_configuration_warnings(); +} +void AnimationPlayer::_rename_animation(const StringName &p_from_name, const StringName &p_to_name) { + // Rename autoplay or blends if needed. List<BlendKey> to_erase; - Map<BlendKey, float> to_insert; + HashMap<BlendKey, float, BlendKey> to_insert; for (const KeyValue<BlendKey, float> &E : blend_times) { BlendKey bk = E.key; BlendKey new_bk = bk; bool erase = false; - if (bk.from == p_name) { - new_bk.from = p_new_name; + if (bk.from == p_from_name) { + new_bk.from = p_to_name; erase = true; } - if (bk.to == p_name) { - new_bk.to = p_new_name; + if (bk.to == p_from_name) { + new_bk.to = p_to_name; erase = true; } @@ -1235,16 +1298,188 @@ void AnimationPlayer::rename_animation(const StringName &p_name, const StringNam } while (to_insert.size()) { - blend_times[to_insert.front()->key()] = to_insert.front()->get(); - to_insert.erase(to_insert.front()); + blend_times[to_insert.begin()->key] = to_insert.begin()->value; + to_insert.remove(to_insert.begin()); } - if (autoplay == p_name) { - autoplay = p_new_name; + if (autoplay == p_from_name) { + autoplay = p_to_name; } +} + +void AnimationPlayer::_animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library) { + StringName from_name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); + StringName to_name = p_library == StringName() ? p_to_name : StringName(String(p_library) + "/" + String(p_to_name)); + + if (!animation_set.has(from_name)) { + return; // No need to update because not the one from the library being used. + } + _animation_set_cache_update(); + + _rename_animation(from_name, to_name); + update_configuration_warnings(); +} + +Error AnimationPlayer::add_animation_library(const StringName &p_name, const Ref<AnimationLibrary> &p_animation_library) { + ERR_FAIL_COND_V(p_animation_library.is_null(), ERR_INVALID_PARAMETER); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + "."); +#endif + + int insert_pos = 0; + + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + ERR_FAIL_COND_V_MSG(animation_libraries[i].name == p_name, ERR_ALREADY_EXISTS, "Can't add animation library twice with name: " + String(p_name)); + ERR_FAIL_COND_V_MSG(animation_libraries[i].library == p_animation_library, ERR_ALREADY_EXISTS, "Can't add animation library twice (adding as '" + p_name.operator String() + "', exists as '" + animation_libraries[i].name.operator String() + "'."); + + if (animation_libraries[i].name.operator String() >= p_name.operator String()) { + break; + } + + insert_pos++; + } + + AnimationLibraryData ald; + ald.name = p_name; + ald.library = p_animation_library; + + animation_libraries.insert(insert_pos, ald); + + ald.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added), varray(p_name)); + ald.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added), varray(p_name)); + ald.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed), varray(p_name)); + + _animation_set_cache_update(); + + notify_property_list_changed(); + + update_configuration_warnings(); + return OK; +} + +void AnimationPlayer::remove_animation_library(const StringName &p_name) { + int at_pos = -1; + + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + if (animation_libraries[i].name == p_name) { + at_pos = i; + break; + } + } + + ERR_FAIL_COND(at_pos == -1); + + animation_libraries[at_pos].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added)); + animation_libraries[at_pos].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added)); + animation_libraries[at_pos].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed)); + + stop(); + + for (const KeyValue<StringName, Ref<Animation>> &K : animation_libraries[at_pos].library->animations) { + _unref_anim(K.value); + } + + animation_libraries.remove_at(at_pos); + _animation_set_cache_update(); - clear_caches(); notify_property_list_changed(); + update_configuration_warnings(); +} + +void AnimationPlayer::_ref_anim(const Ref<Animation> &p_anim) { + Ref<Animation>(p_anim)->connect(SceneStringNames::get_singleton()->tracks_changed, callable_mp(this, &AnimationPlayer::_animation_changed), varray(), CONNECT_REFERENCE_COUNTED); +} + +void AnimationPlayer::_unref_anim(const Ref<Animation> &p_anim) { + Ref<Animation>(p_anim)->disconnect(SceneStringNames::get_singleton()->tracks_changed, callable_mp(this, &AnimationPlayer::_animation_changed)); +} + +void AnimationPlayer::rename_animation_library(const StringName &p_name, const StringName &p_new_name) { + if (p_name == p_new_name) { + return; + } +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_MSG(String(p_new_name).contains("/") || String(p_new_name).contains(":") || String(p_new_name).contains(",") || String(p_new_name).contains("["), "Invalid animation library name: " + String(p_new_name) + "."); +#endif + + bool found = false; + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + ERR_FAIL_COND_MSG(animation_libraries[i].name == p_new_name, "Can't rename animation library to another existing name: " + String(p_new_name)); + if (animation_libraries[i].name == p_name) { + found = true; + animation_libraries[i].name = p_new_name; + // rename connections + animation_libraries[i].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added)); + animation_libraries[i].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added)); + animation_libraries[i].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed)); + + animation_libraries[i].library->connect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added), varray(p_new_name)); + animation_libraries[i].library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added), varray(p_new_name)); + animation_libraries[i].library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed), varray(p_new_name)); + + for (const KeyValue<StringName, Ref<Animation>> &K : animation_libraries[i].library->animations) { + StringName old_name = p_name == StringName() ? K.key : StringName(String(p_name) + "/" + String(K.key)); + StringName new_name = p_new_name == StringName() ? K.key : StringName(String(p_new_name) + "/" + String(K.key)); + _rename_animation(old_name, new_name); + } + } + } + + ERR_FAIL_COND(!found); + + stop(); + + animation_libraries.sort(); // Must keep alphabetical order. + + _animation_set_cache_update(); // Update cache. + + notify_property_list_changed(); +} + +bool AnimationPlayer::has_animation_library(const StringName &p_name) const { + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + if (animation_libraries[i].name == p_name) { + return true; + } + } + + return false; +} + +Ref<AnimationLibrary> AnimationPlayer::get_animation_library(const StringName &p_name) const { + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + if (animation_libraries[i].name == p_name) { + return animation_libraries[i].library; + } + } + ERR_FAIL_V(Ref<AnimationLibrary>()); +} + +TypedArray<StringName> AnimationPlayer::_get_animation_library_list() const { + TypedArray<StringName> ret; + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + ret.push_back(animation_libraries[i].name); + } + return ret; +} + +void AnimationPlayer::get_animation_library_list(List<StringName> *p_libraries) const { + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + p_libraries->push_back(animation_libraries[i].name); + } +} + +TypedArray<String> AnimationPlayer::get_configuration_warnings() const { + TypedArray<String> warnings = Node::get_configuration_warnings(); + + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + for (const KeyValue<StringName, Ref<Animation>> &K : animation_libraries[i].library->animations) { + if (animation_set.has(K.key) && animation_set[K.key].animation_library != animation_libraries[i].name) { + warnings.push_back(vformat(RTR("Animation '%s' in library '%s' is unused because another animation with the same name exists in library '%s'."), K.key, animation_libraries[i].name, animation_set[K.key].animation_library)); + } + } + } + return warnings; } bool AnimationPlayer::has_animation(const StringName &p_name) const { @@ -1531,7 +1766,7 @@ void AnimationPlayer::_animation_changed() { } void AnimationPlayer::_stop_playing_caches() { - for (Set<TrackNodeCache *>::Element *E = playing_caches.front(); E; E = E->next()) { + for (RBSet<TrackNodeCache *>::Element *E = playing_caches.front(); E; E = E->next()) { if (E->get()->node && E->get()->audio_playing) { E->get()->node->call(SNAME("stop")); } @@ -1585,7 +1820,16 @@ StringName AnimationPlayer::find_animation(const Ref<Animation> &p_animation) co } } - return ""; + return StringName(); +} + +StringName AnimationPlayer::find_animation_library(const Ref<Animation> &p_animation) const { + for (const KeyValue<StringName, AnimationData> &E : animation_set) { + if (E.value.animation == p_animation) { + return E.value.animation_library; + } + } + return StringName(); } void AnimationPlayer::set_autoplay(const String &p_name) { @@ -1764,7 +2008,10 @@ Ref<AnimatedValuesBackup> AnimationPlayer::apply_reset(bool p_user_initiated) { AnimationPlayer *aux_player = memnew(AnimationPlayer); EditorNode::get_singleton()->add_child(aux_player); - aux_player->add_animation(SceneStringNames::get_singleton()->RESET, reset_anim); + Ref<AnimationLibrary> al; + al.instantiate(); + al->add_animation(SceneStringNames::get_singleton()->RESET, reset_anim); + aux_player->add_animation_library("default", al); aux_player->set_assigned_animation(SceneStringNames::get_singleton()->RESET); // Forcing the use of the original root because the scene where original player belongs may be not the active one Node *root = get_node(get_root()); @@ -1792,9 +2039,13 @@ bool AnimationPlayer::can_apply_reset() const { #endif // TOOLS_ENABLED void AnimationPlayer::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_animation", "name", "animation"), &AnimationPlayer::add_animation); - ClassDB::bind_method(D_METHOD("remove_animation", "name"), &AnimationPlayer::remove_animation); - ClassDB::bind_method(D_METHOD("rename_animation", "name", "newname"), &AnimationPlayer::rename_animation); + ClassDB::bind_method(D_METHOD("add_animation_library", "name", "library"), &AnimationPlayer::add_animation_library); + ClassDB::bind_method(D_METHOD("remove_animation_library", "name"), &AnimationPlayer::remove_animation_library); + ClassDB::bind_method(D_METHOD("rename_animation_library", "name", "newname"), &AnimationPlayer::rename_animation_library); + ClassDB::bind_method(D_METHOD("has_animation_library", "name"), &AnimationPlayer::has_animation_library); + ClassDB::bind_method(D_METHOD("get_animation_library", "name"), &AnimationPlayer::get_animation_library); + ClassDB::bind_method(D_METHOD("get_animation_library_list"), &AnimationPlayer::_get_animation_library_list); + ClassDB::bind_method(D_METHOD("has_animation", "name"), &AnimationPlayer::has_animation); ClassDB::bind_method(D_METHOD("get_animation", "name"), &AnimationPlayer::get_animation); ClassDB::bind_method(D_METHOD("get_animation_list"), &AnimationPlayer::_get_animation_list); @@ -1838,6 +2089,7 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_root"), &AnimationPlayer::get_root); ClassDB::bind_method(D_METHOD("find_animation", "animation"), &AnimationPlayer::find_animation); + ClassDB::bind_method(D_METHOD("find_animation_library", "animation"), &AnimationPlayer::find_animation_library); ClassDB::bind_method(D_METHOD("clear_caches"), &AnimationPlayer::clear_caches); diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index a68f6b9d5b..3ef87dba28 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -36,6 +36,7 @@ #include "scene/3d/node_3d.h" #include "scene/3d/skeleton_3d.h" #include "scene/resources/animation.h" +#include "scene/resources/animation_library.h" #ifdef TOOLS_ENABLED class AnimatedValuesBackup : public RefCounted { @@ -93,7 +94,7 @@ private: struct TrackNodeCache { NodePath path; uint32_t id = 0; - RES resource; + Ref<Resource> resource; Node *node = nullptr; Node2D *node_2d = nullptr; #ifndef _3D_DISABLED @@ -131,7 +132,7 @@ private: Variant capture; }; - Map<StringName, PropertyAnim> property_anim; + HashMap<StringName, PropertyAnim> property_anim; struct BezierAnim { Vector<StringName> bezier_property; @@ -141,7 +142,7 @@ private: uint64_t accum_pass = 0; }; - Map<StringName, BezierAnim> bezier_anim; + HashMap<StringName, BezierAnim> bezier_anim; uint32_t last_setup_pass = 0; TrackNodeCache() {} @@ -152,6 +153,16 @@ private: int bone_idx = -1; int blend_shape_idx = -1; + static uint32_t hash(const TrackNodeCacheKey &p_key) { + uint32_t h = hash_one_uint64(p_key.id); + h = hash_djb2_one_32(p_key.bone_idx, h); + return hash_djb2_one_32(p_key.blend_shape_idx, h); + } + + inline bool operator==(const TrackNodeCacheKey &p_right) const { + return id == p_right.id && bone_idx == p_right.bone_idx && blend_shape_idx == p_right.blend_shape_idx; + } + inline bool operator<(const TrackNodeCacheKey &p_right) const { if (id == p_right.id) { if (blend_shape_idx == p_right.blend_shape_idx) { @@ -165,7 +176,7 @@ private: } }; - Map<TrackNodeCacheKey, TrackNodeCache> node_cache_map; + HashMap<TrackNodeCacheKey, TrackNodeCache, TrackNodeCacheKey> node_cache_map; TrackNodeCache *cache_update[NODE_CACHE_UPDATE_MAX]; int cache_update_size = 0; @@ -173,7 +184,7 @@ private: int cache_update_prop_size = 0; TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX]; int cache_update_bezier_size = 0; - Set<TrackNodeCache *> playing_caches; + RBSet<TrackNodeCache *> playing_caches; uint64_t accum_pass = 1; float speed_scale = 1.0; @@ -184,16 +195,39 @@ private: StringName next; Vector<TrackNodeCache *> node_cache; Ref<Animation> animation; + StringName animation_library; + uint64_t last_update = 0; + }; + + HashMap<StringName, AnimationData> animation_set; + + struct AnimationLibraryData { + StringName name; + Ref<AnimationLibrary> library; + bool operator<(const AnimationLibraryData &p_data) const { return name.operator String() < p_data.name.operator String(); } }; - Map<StringName, AnimationData> animation_set; + LocalVector<AnimationLibraryData> animation_libraries; + struct BlendKey { StringName from; StringName to; - bool operator<(const BlendKey &bk) const { return from == bk.from ? String(to) < String(bk.to) : String(from) < String(bk.from); } + static uint32_t hash(const BlendKey &p_key) { + return hash_one_uint64((uint64_t(p_key.from.hash()) << 32) | uint32_t(p_key.to.hash())); + } + bool operator==(const BlendKey &bk) const { + return from == bk.from && to == bk.to; + } + bool operator<(const BlendKey &bk) const { + if (from == bk.from) { + return to < bk.to; + } else { + return from < bk.from; + } + } }; - Map<BlendKey, float> blend_times; + HashMap<BlendKey, float, BlendKey> blend_times; struct PlaybackData { AnimationData *from = nullptr; @@ -261,6 +295,15 @@ private: bool playing = false; + uint64_t animation_set_update_pass = 1; + void _animation_set_cache_update(); + void _animation_added(const StringName &p_name, const StringName &p_library); + void _animation_removed(const StringName &p_name, const StringName &p_library); + void _animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library); + void _rename_animation(const StringName &p_from_name, const StringName &p_to_name); + + TypedArray<StringName> _get_animation_library_list() const; + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -272,13 +315,18 @@ protected: public: StringName find_animation(const Ref<Animation> &p_animation) const; + StringName find_animation_library(const Ref<Animation> &p_animation) const; + + Error add_animation_library(const StringName &p_name, const Ref<AnimationLibrary> &p_animation_library); + void remove_animation_library(const StringName &p_name); + void rename_animation_library(const StringName &p_name, const StringName &p_new_name); + Ref<AnimationLibrary> get_animation_library(const StringName &p_name) const; + void get_animation_library_list(List<StringName> *p_animations) const; + bool has_animation_library(const StringName &p_name) const; - Error add_animation(const StringName &p_name, const Ref<Animation> &p_animation); - void remove_animation(const StringName &p_name); - void rename_animation(const StringName &p_name, const StringName &p_new_name); - bool has_animation(const StringName &p_name) const; Ref<Animation> get_animation(const StringName &p_name) const; void get_animation_list(List<StringName> *p_animations) const; + bool has_animation(const StringName &p_name) const; void set_blend_time(const StringName &p_animation1, const StringName &p_animation2, float p_time); float get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const; @@ -340,6 +388,8 @@ public: bool can_apply_reset() const; #endif + TypedArray<String> get_configuration_warnings() const override; + AnimationPlayer(); ~AnimationPlayer(); }; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 64c71697a5..92af15d5d3 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -198,12 +198,11 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Stri blendw[i] = 0.0; //all to zero by default } - const NodePath *K = nullptr; - while ((K = filter.next(K))) { - if (!state->track_map.has(*K)) { + for (const KeyValue<NodePath, bool> &E : filter) { + if (!state->track_map.has(E.key)) { continue; } - int idx = state->track_map[*K]; + int idx = state->track_map[E.key]; blendw[idx] = 1.0; //filtered goes to one } @@ -374,9 +373,8 @@ bool AnimationNode::has_filter() const { Array AnimationNode::_get_filters() const { Array paths; - const NodePath *K = nullptr; - while ((K = filter.next(K))) { - paths.push_back(String(*K)); //use strings, so sorting is possible + for (const KeyValue<NodePath, bool> &E : filter) { + paths.push_back(String(E.key)); //use strings, so sorting is possible } paths.sort(); //done so every time the scene is saved, it does not change @@ -488,7 +486,7 @@ void AnimationTree::set_active(bool p_active) { } if (!active && is_inside_tree()) { - for (Set<TrackCache *>::Element *E = playing_caches.front(); E; E = E->next()) { + for (RBSet<TrackCache *>::Element *E = playing_caches.front(); E; E = E->next()) { if (ObjectDB::get_instance(E->get()->object_id)) { E->get()->object->call(SNAME("stop")); } @@ -540,6 +538,11 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { List<StringName> sname; player->get_animation_list(&sname); + Ref<Animation> reset_anim; + bool has_reset_anim = player->has_animation(SceneStringNames::get_singleton()->RESET); + if (has_reset_anim) { + reset_anim = player->get_animation(SceneStringNames::get_singleton()->RESET); + } for (const StringName &E : sname) { Ref<Animation> anim = player->get_animation(E); for (int i = 0; i < anim->get_track_count(); i++) { @@ -565,7 +568,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { } if (!track) { - RES resource; + Ref<Resource> resource; Vector<StringName> leftover_path; Node *child = parent->get_node_and_resource(path, resource, leftover_path); @@ -593,6 +596,12 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track = track_value; + 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_value->init_value = reset_anim->track_get_key_value(rt, 0); + } + } } break; case Animation::TYPE_POSITION_3D: case Animation::TYPE_ROTATION_3D: @@ -612,16 +621,17 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track_xform->skeleton = nullptr; track_xform->bone_idx = -1; + bool has_rest = false; if (path.get_subname_count() == 1 && Object::cast_to<Skeleton3D>(node_3d)) { Skeleton3D *sk = Object::cast_to<Skeleton3D>(node_3d); track_xform->skeleton = sk; int bone_idx = sk->find_bone(path.get_subname(0)); if (bone_idx != -1) { + has_rest = true; track_xform->bone_idx = bone_idx; Transform3D rest = sk->get_bone_rest(bone_idx); track_xform->init_loc = rest.origin; - track_xform->ref_rot = rest.basis.get_rotation_quaternion(); - track_xform->init_rot = track_xform->ref_rot.log(); + track_xform->init_rot = rest.basis.get_rotation_quaternion(); track_xform->init_scale = rest.basis.get_scale(); } } @@ -645,6 +655,25 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { } } + // For non Skeleton3D bone animation. + if (has_reset_anim && !has_rest) { + int rt = reset_anim->find_track(path, track_type); + if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { + switch (track_type) { + case Animation::TYPE_POSITION_3D: { + track_xform->init_loc = reset_anim->track_get_key_value(rt, 0); + } break; + case Animation::TYPE_ROTATION_3D: { + track_xform->init_rot = reset_anim->track_get_key_value(rt, 0); + } break; + case Animation::TYPE_SCALE_3D: { + track_xform->init_scale = reset_anim->track_get_key_value(rt, 0); + } break; + default: { + } + } + } + } #endif // _3D_DISABLED } break; case Animation::TYPE_BLEND_SHAPE: { @@ -675,6 +704,13 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track_bshape->object = mesh_3d; track_bshape->object_id = mesh_3d->get_instance_id(); track = track_bshape; + + 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_bshape->init_value = reset_anim->track_get_key_value(rt, 0); + } + } #endif } break; case Animation::TYPE_METHOD: { @@ -704,6 +740,13 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { track_bezier->object_id = track_bezier->object->get_instance_id(); track = track_bezier; + + 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); + } + } } break; case Animation::TYPE_AUDIO: { TrackCacheAudio *track_audio = memnew(TrackCacheAudio); @@ -758,11 +801,10 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { List<NodePath> to_delete; - const NodePath *K = nullptr; - while ((K = track_cache.next(K))) { - TrackCache *tc = track_cache[*K]; + for (const KeyValue<NodePath, TrackCache *> &K : track_cache) { + TrackCache *tc = track_cache[K.key]; if (tc->setup_pass != setup_pass) { - to_delete.push_back(*K); + to_delete.push_back(K.key); } } @@ -775,10 +817,9 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { state.track_map.clear(); - K = nullptr; int idx = 0; - while ((K = track_cache.next(K))) { - state.track_map[*K] = idx; + for (const KeyValue<NodePath, TrackCache *> &K : track_cache) { + state.track_map[K.key] = idx; idx++; } @@ -790,9 +831,8 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) { } void AnimationTree::_clear_caches() { - const NodePath *K = nullptr; - while ((K = track_cache.next(K))) { - memdelete(track_cache[*K]); + for (KeyValue<NodePath, TrackCache *> &K : track_cache) { + memdelete(K.value); } playing_caches.clear(); @@ -950,13 +990,13 @@ void AnimationTree::_process_graph(double p_delta) { case Animation::TYPE_POSITION_3D: { #ifndef _3D_DISABLED TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); - 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; - } if (track->root_motion) { + 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) { @@ -1026,6 +1066,12 @@ void AnimationTree::_process_graph(double p_delta) { 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); @@ -1040,13 +1086,13 @@ void AnimationTree::_process_graph(double p_delta) { case Animation::TYPE_ROTATION_3D: { #ifndef _3D_DISABLED TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); - 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; - } if (track->root_motion) { + 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) { @@ -1091,7 +1137,7 @@ void AnimationTree::_process_graph(double p_delta) { continue; } a->rotation_track_interpolate(i, (double)a->get_length(), &rot[1]); - t->rot += (rot[1].log() - rot[0].log()) * blend; + t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); prev_time = 0; } } else { @@ -1101,7 +1147,7 @@ void AnimationTree::_process_graph(double p_delta) { continue; } a->rotation_track_interpolate(i, 0, &rot[1]); - t->rot += (rot[1].log() - rot[0].log()) * blend; + t->rot = (t->rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); prev_time = 0; } } @@ -1112,10 +1158,16 @@ void AnimationTree::_process_graph(double p_delta) { } a->rotation_track_interpolate(i, time, &rot[1]); - t->rot += (rot[1].log() - rot[0].log()) * blend; + t->rot = (t->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); @@ -1123,23 +1175,20 @@ void AnimationTree::_process_graph(double p_delta) { continue; } - if (signbit(rot.dot(t->ref_rot))) { - rot = -rot; - } - t->rot += (rot.log() - t->init_rot) * blend; + t->rot = (t->rot * Quaternion().slerp(t->init_rot.inverse() * rot, blend)).normalized(); } #endif // _3D_DISABLED } break; case Animation::TYPE_SCALE_3D: { #ifndef _3D_DISABLED TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); - 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; - } if (track->root_motion) { + 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) { @@ -1209,6 +1258,12 @@ void AnimationTree::_process_graph(double p_delta) { 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); @@ -1226,7 +1281,7 @@ void AnimationTree::_process_graph(double p_delta) { if (t->process_pass != process_pass) { t->process_pass = process_pass; - t->value = 0; + t->value = t->init_value; } float value; @@ -1238,7 +1293,7 @@ void AnimationTree::_process_graph(double p_delta) { continue; } - t->value += value * blend; + t->value += (value - t->init_value) * blend; #endif // _3D_DISABLED } break; case Animation::TYPE_VALUE: { @@ -1256,10 +1311,14 @@ void AnimationTree::_process_graph(double p_delta) { if (t->process_pass != process_pass) { t->process_pass = process_pass; - t->value = value; - t->value.zero(); + if (!t->init_value) { + t->init_value = value; + t->init_value.zero(); + } + t->value = t->init_value; } + Variant::sub(value, t->init_value, value); Variant::blend(t->value, value, blend, t->value); } else { if (blend < CMP_EPSILON) { @@ -1303,10 +1362,10 @@ void AnimationTree::_process_graph(double p_delta) { if (t->process_pass != process_pass) { t->process_pass = process_pass; - t->value = 0; + t->value = t->init_value; } - t->value += bezier * blend; + t->value += (bezier - t->init_value) * blend; } break; case Animation::TYPE_AUDIO: { if (blend < CMP_EPSILON) { @@ -1505,9 +1564,8 @@ void AnimationTree::_process_graph(double p_delta) { { // finally, set the tracks - const NodePath *K = nullptr; - while ((K = track_cache.next(K))) { - TrackCache *track = track_cache[*K]; + for (const KeyValue<NodePath, TrackCache *> &K : track_cache) { + TrackCache *track = K.value; if (track->process_pass != process_pass) { continue; //not processed, ignore } @@ -1516,12 +1574,11 @@ void AnimationTree::_process_graph(double p_delta) { case Animation::TYPE_POSITION_3D: { #ifndef _3D_DISABLED TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track); - t->rot = t->rot.exp(); if (t->root_motion) { Transform3D xform; xform.origin = t->loc; - xform.basis.set_quaternion_scale(t->rot, t->scale); + xform.basis.set_quaternion_scale(t->rot, Vector3(1, 1, 1) + t->scale); root_motion_transform = xform; diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 3ccb6be073..d40d4ccbbd 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -37,6 +37,8 @@ #include "scene/resources/animation.h" class AnimationNodeBlendTree; +class AnimationNodeStartState; +class AnimationNodeEndState; class AnimationPlayer; class AnimationTree; @@ -164,6 +166,14 @@ public: AnimationRootNode() {} }; +class AnimationNodeStartState : public AnimationRootNode { + GDCLASS(AnimationNodeStartState, AnimationRootNode); +}; + +class AnimationNodeEndState : public AnimationRootNode { + GDCLASS(AnimationNodeEndState, AnimationRootNode); +}; + class AnimationTree : public Node { GDCLASS(AnimationTree, Node); @@ -198,8 +208,7 @@ private: bool rot_used = false; bool scale_used = false; Vector3 init_loc = Vector3(0, 0, 0); - Quaternion ref_rot = Quaternion(0, 0, 0, 1); - Quaternion init_rot = Quaternion(0, 0, 0, 0); + Quaternion init_rot = Quaternion(0, 0, 0, 1); Vector3 init_scale = Vector3(1, 1, 1); Vector3 loc; Quaternion rot; @@ -212,12 +221,14 @@ private: struct TrackCacheBlendShape : public TrackCache { MeshInstance3D *mesh_3d = nullptr; + float init_value = 0; float value = 0; int shape_index = -1; TrackCacheBlendShape() { type = Animation::TYPE_BLEND_SHAPE; } }; struct TrackCacheValue : public TrackCache { + Variant init_value; Variant value; Vector<StringName> subpath; TrackCacheValue() { type = Animation::TYPE_VALUE; } @@ -228,6 +239,7 @@ private: }; struct TrackCacheBezier : public TrackCache { + real_t init_value = 0.0; real_t value = 0.0; Vector<StringName> subpath; TrackCacheBezier() { @@ -254,7 +266,7 @@ private: }; HashMap<NodePath, TrackCache *> track_cache; - Set<TrackCache *> playing_caches; + RBSet<TrackCache *> playing_caches; Ref<AnimationNode> root; diff --git a/scene/animation/root_motion_view.cpp b/scene/animation/root_motion_view.cpp index 42adc1ea02..3192f5f7cd 100644 --- a/scene/animation/root_motion_view.cpp +++ b/scene/animation/root_motion_view.cpp @@ -114,9 +114,8 @@ void RootMotionView::_notification(int p_what) { first = false; transform.orthonormalize(); //don't want scale, too imprecise - transform.affine_invert(); - accumulated = transform * accumulated; + accumulated = accumulated * transform; accumulated.origin.x = Math::fposmod(accumulated.origin.x, cell_size); if (zero_y) { accumulated.origin.y = 0; @@ -134,9 +133,9 @@ void RootMotionView::_notification(int p_what) { Vector3 from(i * cell_size, 0, j * cell_size); Vector3 from_i((i + 1) * cell_size, 0, j * cell_size); Vector3 from_j(i * cell_size, 0, (j + 1) * cell_size); - from = accumulated.xform(from); - from_i = accumulated.xform(from_i); - from_j = accumulated.xform(from_j); + from = accumulated.xform_inv(from); + from_i = accumulated.xform_inv(from_i); + from_j = accumulated.xform_inv(from_j); Color c = color, c_i = color, c_j = color; c.a *= MAX(0, 1.0 - from.length() / radius); diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index c8eb270a0a..5457da472f 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -130,6 +130,7 @@ void Tween::stop() { started = false; running = false; dead = false; + total_time = 0; } void Tween::pause() { @@ -151,10 +152,6 @@ bool Tween::is_running() { return running; } -void Tween::set_valid(bool p_valid) { - valid = p_valid; -} - bool Tween::is_valid() { return valid; } @@ -272,21 +269,24 @@ bool Tween::step(float p_delta) { ERR_FAIL_COND_V_MSG(tweeners.is_empty(), false, "Tween started, but has no Tweeners."); current_step = 0; loops_done = 0; + total_time = 0; start_tweeners(); started = true; } float rem_delta = p_delta * speed_scale; bool step_active = false; + total_time += rem_delta; + +#ifdef DEBUG_ENABLED + float initial_delta = rem_delta; + bool potential_infinite = false; +#endif while (rem_delta > 0 && running) { float step_delta = rem_delta; step_active = false; -#ifdef DEBUG_ENABLED - float prev_delta = rem_delta; -#endif - for (Ref<Tweener> &tweener : tweeners.write[current_step]) { // Modified inside Tweener.step(). float temp_delta = rem_delta; @@ -311,17 +311,21 @@ bool Tween::step(float p_delta) { emit_signal(SNAME("loop_finished"), loops_done); current_step = 0; start_tweeners(); +#ifdef DEBUG_ENABLED + if (loops <= 0 && Math::is_equal_approx(rem_delta, initial_delta)) { + if (!potential_infinite) { + potential_infinite = true; + } else { + // Looped twice without using any time, this is 100% certain infinite loop. + ERR_FAIL_V_MSG(false, "Infinite loop detected. Check set_loops() description for more info."); + } + } +#endif } } else { start_tweeners(); } } - -#ifdef DEBUG_ENABLED - if (Math::is_equal_approx(rem_delta, prev_delta) && running && loops <= 0) { - ERR_FAIL_V_MSG(false, "Infinite loop detected. Check set_loops() description for more info."); - } -#endif } return true; @@ -331,7 +335,7 @@ bool Tween::can_process(bool p_tree_paused) const { if (is_bound && pause_mode == TWEEN_PAUSE_BOUND) { Node *bound_node = get_bound_node(); if (bound_node) { - return bound_node->can_process(); + return bound_node->is_inside_tree() && bound_node->can_process(); } } @@ -346,6 +350,10 @@ Node *Tween::get_bound_node() const { } } +float Tween::get_total_time() const { + return total_time; +} + real_t Tween::run_equation(TransitionType p_trans_type, EaseType p_ease_type, real_t p_time, real_t p_initial, real_t p_delta, real_t p_duration) { if (p_duration == 0) { // Special case to avoid dividing by 0 in equations. @@ -448,12 +456,12 @@ Variant Tween::interpolate_variant(Variant p_initial_val, Variant p_delta_val, f Transform2D d = p_delta_val; Transform2D r; - APPLY_EQUATION(elements[0][0]); - APPLY_EQUATION(elements[0][1]); - APPLY_EQUATION(elements[1][0]); - APPLY_EQUATION(elements[1][1]); - APPLY_EQUATION(elements[2][0]); - APPLY_EQUATION(elements[2][1]); + APPLY_EQUATION(columns[0][0]); + APPLY_EQUATION(columns[0][1]); + APPLY_EQUATION(columns[1][0]); + APPLY_EQUATION(columns[1][1]); + APPLY_EQUATION(columns[2][0]); + APPLY_EQUATION(columns[2][1]); return r; } @@ -488,15 +496,15 @@ Variant Tween::interpolate_variant(Variant p_initial_val, Variant p_delta_val, f Basis d = p_delta_val; Basis r; - APPLY_EQUATION(elements[0][0]); - APPLY_EQUATION(elements[0][1]); - APPLY_EQUATION(elements[0][2]); - APPLY_EQUATION(elements[1][0]); - APPLY_EQUATION(elements[1][1]); - APPLY_EQUATION(elements[1][2]); - APPLY_EQUATION(elements[2][0]); - APPLY_EQUATION(elements[2][1]); - APPLY_EQUATION(elements[2][2]); + APPLY_EQUATION(rows[0][0]); + APPLY_EQUATION(rows[0][1]); + APPLY_EQUATION(rows[0][2]); + APPLY_EQUATION(rows[1][0]); + APPLY_EQUATION(rows[1][1]); + APPLY_EQUATION(rows[1][2]); + APPLY_EQUATION(rows[2][0]); + APPLY_EQUATION(rows[2][1]); + APPLY_EQUATION(rows[2][2]); return r; } @@ -505,15 +513,15 @@ Variant Tween::interpolate_variant(Variant p_initial_val, Variant p_delta_val, f Transform3D d = p_delta_val; Transform3D r; - APPLY_EQUATION(basis.elements[0][0]); - APPLY_EQUATION(basis.elements[0][1]); - APPLY_EQUATION(basis.elements[0][2]); - APPLY_EQUATION(basis.elements[1][0]); - APPLY_EQUATION(basis.elements[1][1]); - APPLY_EQUATION(basis.elements[1][2]); - APPLY_EQUATION(basis.elements[2][0]); - APPLY_EQUATION(basis.elements[2][1]); - APPLY_EQUATION(basis.elements[2][2]); + APPLY_EQUATION(basis.rows[0][0]); + APPLY_EQUATION(basis.rows[0][1]); + APPLY_EQUATION(basis.rows[0][2]); + APPLY_EQUATION(basis.rows[1][0]); + APPLY_EQUATION(basis.rows[1][1]); + APPLY_EQUATION(basis.rows[1][2]); + APPLY_EQUATION(basis.rows[2][0]); + APPLY_EQUATION(basis.rows[2][1]); + APPLY_EQUATION(basis.rows[2][2]); APPLY_EQUATION(origin.x); APPLY_EQUATION(origin.y); APPLY_EQUATION(origin.z); @@ -562,12 +570,12 @@ Variant Tween::calculate_delta_value(Variant p_intial_val, Variant p_final_val) case Variant::TRANSFORM2D: { Transform2D i = p_intial_val; Transform2D f = p_final_val; - return Transform2D(f.elements[0][0] - i.elements[0][0], - f.elements[0][1] - i.elements[0][1], - f.elements[1][0] - i.elements[1][0], - f.elements[1][1] - i.elements[1][1], - f.elements[2][0] - i.elements[2][0], - f.elements[2][1] - i.elements[2][1]); + return Transform2D(f.columns[0][0] - i.columns[0][0], + f.columns[0][1] - i.columns[0][1], + f.columns[1][0] - i.columns[1][0], + f.columns[1][1] - i.columns[1][1], + f.columns[2][0] - i.columns[2][0], + f.columns[2][1] - i.columns[2][1]); } case Variant::AABB: { @@ -579,29 +587,29 @@ Variant Tween::calculate_delta_value(Variant p_intial_val, Variant p_final_val) case Variant::BASIS: { Basis i = p_intial_val; Basis f = p_final_val; - return Basis(f.elements[0][0] - i.elements[0][0], - f.elements[0][1] - i.elements[0][1], - f.elements[0][2] - i.elements[0][2], - f.elements[1][0] - i.elements[1][0], - f.elements[1][1] - i.elements[1][1], - f.elements[1][2] - i.elements[1][2], - f.elements[2][0] - i.elements[2][0], - f.elements[2][1] - i.elements[2][1], - f.elements[2][2] - i.elements[2][2]); + return Basis(f.rows[0][0] - i.rows[0][0], + f.rows[0][1] - i.rows[0][1], + f.rows[0][2] - i.rows[0][2], + f.rows[1][0] - i.rows[1][0], + f.rows[1][1] - i.rows[1][1], + f.rows[1][2] - i.rows[1][2], + f.rows[2][0] - i.rows[2][0], + f.rows[2][1] - i.rows[2][1], + f.rows[2][2] - i.rows[2][2]); } case Variant::TRANSFORM3D: { Transform3D i = p_intial_val; Transform3D f = p_final_val; - return Transform3D(f.basis.elements[0][0] - i.basis.elements[0][0], - f.basis.elements[0][1] - i.basis.elements[0][1], - f.basis.elements[0][2] - i.basis.elements[0][2], - f.basis.elements[1][0] - i.basis.elements[1][0], - f.basis.elements[1][1] - i.basis.elements[1][1], - f.basis.elements[1][2] - i.basis.elements[1][2], - f.basis.elements[2][0] - i.basis.elements[2][0], - f.basis.elements[2][1] - i.basis.elements[2][1], - f.basis.elements[2][2] - i.basis.elements[2][2], + return Transform3D(f.basis.rows[0][0] - i.basis.rows[0][0], + f.basis.rows[0][1] - i.basis.rows[0][1], + f.basis.rows[0][2] - i.basis.rows[0][2], + f.basis.rows[1][0] - i.basis.rows[1][0], + f.basis.rows[1][1] - i.basis.rows[1][1], + f.basis.rows[1][2] - i.basis.rows[1][2], + f.basis.rows[2][0] - i.basis.rows[2][0], + f.basis.rows[2][1] - i.basis.rows[2][1], + f.basis.rows[2][2] - i.basis.rows[2][2], f.origin.x - i.origin.x, f.origin.y - i.origin.y, f.origin.z - i.origin.z); @@ -624,6 +632,7 @@ void Tween::_bind_methods() { ClassDB::bind_method(D_METHOD("pause"), &Tween::pause); ClassDB::bind_method(D_METHOD("play"), &Tween::play); ClassDB::bind_method(D_METHOD("kill"), &Tween::kill); + ClassDB::bind_method(D_METHOD("get_total_elapsed_time"), &Tween::get_total_time); ClassDB::bind_method(D_METHOD("is_running"), &Tween::is_running); ClassDB::bind_method(D_METHOD("is_valid"), &Tween::is_valid); @@ -640,7 +649,7 @@ void Tween::_bind_methods() { ClassDB::bind_method(D_METHOD("parallel"), &Tween::parallel); ClassDB::bind_method(D_METHOD("chain"), &Tween::chain); - ClassDB::bind_method(D_METHOD("interpolate_value", "initial_value", "delta_value", "elapsed_time", "duration", "trans_type", "ease_type"), &Tween::interpolate_variant); + ClassDB::bind_static_method("Tween", D_METHOD("interpolate_value", "initial_value", "delta_value", "elapsed_time", "duration", "trans_type", "ease_type"), &Tween::interpolate_variant); ADD_SIGNAL(MethodInfo("step_finished", PropertyInfo(Variant::INT, "idx"))); ADD_SIGNAL(MethodInfo("loop_finished", PropertyInfo(Variant::INT, "loop_count"))); @@ -671,6 +680,14 @@ void Tween::_bind_methods() { BIND_ENUM_CONSTANT(EASE_OUT_IN); } +Tween::Tween() { + ERR_FAIL_MSG("Tween can't be created directly. Use create_tween() method."); +} + +Tween::Tween(bool p_valid) { + valid = p_valid; +} + Ref<PropertyTweener> PropertyTweener::from(Variant p_value) { initial_val = p_value; do_continue = false; @@ -838,7 +855,7 @@ bool CallbackTweener::step(float &r_delta) { Callable::CallError ce; callback.call(nullptr, 0, result, ce); if (ce.error != Callable::CallError::CALL_OK) { - ERR_FAIL_V_MSG(false, "Error calling method from CallbackTweener: " + Variant::get_call_error_text(this, callback.get_method(), nullptr, 0, ce)); + ERR_FAIL_V_MSG(false, "Error calling method from CallbackTweener: " + Variant::get_callable_error_text(callback, nullptr, 0, ce)); } finished = true; @@ -909,7 +926,7 @@ bool MethodTweener::step(float &r_delta) { Callable::CallError ce; callback.call(argptr, 1, result, ce); if (ce.error != Callable::CallError::CALL_OK) { - ERR_FAIL_V_MSG(false, "Error calling method from MethodTweener: " + Variant::get_call_error_text(this, callback.get_method(), argptr, 1, ce)); + ERR_FAIL_V_MSG(false, "Error calling method from MethodTweener: " + Variant::get_callable_error_text(callback, argptr, 1, ce)); } if (time < duration) { diff --git a/scene/animation/tween.h b/scene/animation/tween.h index 62c357dfb4..40268405cf 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -103,6 +103,7 @@ private: ObjectID bound_node; Vector<List<Ref<Tweener>>> tweeners; + float total_time = 0; int current_step = -1; int loops = 1; int loops_done = 0; @@ -115,6 +116,9 @@ private: bool valid = false; bool default_parallel = false; bool parallel_enabled = false; +#ifdef DEBUG_ENABLED + bool is_infinite = false; +#endif typedef real_t (*interpolater)(real_t t, real_t b, real_t c, real_t d); static interpolater interpolaters[TRANS_MAX][EASE_MAX]; @@ -138,7 +142,6 @@ public: void kill(); bool is_running(); - void set_valid(bool p_valid); bool is_valid(); void clear(); @@ -159,15 +162,17 @@ public: Ref<Tween> parallel(); Ref<Tween> chain(); - real_t run_equation(TransitionType p_trans_type, EaseType p_ease_type, real_t t, real_t b, real_t c, real_t d); - Variant interpolate_variant(Variant p_initial_val, Variant p_delta_val, float p_time, float p_duration, Tween::TransitionType p_trans, Tween::EaseType p_ease); + static real_t run_equation(TransitionType p_trans_type, EaseType p_ease_type, real_t t, real_t b, real_t c, real_t d); + static Variant interpolate_variant(Variant p_initial_val, Variant p_delta_val, float p_time, float p_duration, Tween::TransitionType p_trans, Tween::EaseType p_ease); Variant calculate_delta_value(Variant p_intial_val, Variant p_final_val); bool step(float p_delta); bool can_process(bool p_tree_paused) const; Node *get_bound_node() const; + float get_total_time() const; - Tween() {} + Tween(); + Tween(bool p_valid); }; VARIANT_ENUM_CAST(Tween::TweenPauseMode); |