904 lines
31 KiB
C++
904 lines
31 KiB
C++
/**************************************************************************/
|
|
/* 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"
|
|
#include "animation_tree.compat.inc"
|
|
|
|
#include "animation_blend_tree.h"
|
|
#include "core/config/engine.h"
|
|
#include "scene/animation/animation_player.h"
|
|
#include "scene/scene_string_names.h"
|
|
|
|
void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const {
|
|
Array parameters;
|
|
|
|
if (GDVIRTUAL_CALL(_get_parameter_list, parameters)) {
|
|
for (int i = 0; i < parameters.size(); i++) {
|
|
Dictionary d = parameters[i];
|
|
ERR_CONTINUE(d.is_empty());
|
|
r_list->push_back(PropertyInfo::from_dict(d));
|
|
}
|
|
}
|
|
}
|
|
|
|
Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter) const {
|
|
Variant ret;
|
|
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) {
|
|
ERR_FAIL_NULL(process_state);
|
|
if (process_state->is_testing) {
|
|
return;
|
|
}
|
|
ERR_FAIL_COND(!process_state->tree->property_parent_map.has(node_state.base_path));
|
|
ERR_FAIL_COND(!process_state->tree->property_parent_map[node_state.base_path].has(p_name));
|
|
StringName path = process_state->tree->property_parent_map[node_state.base_path][p_name];
|
|
|
|
process_state->tree->property_map[path].first = p_value;
|
|
}
|
|
|
|
Variant AnimationNode::get_parameter(const StringName &p_name) const {
|
|
ERR_FAIL_NULL_V(process_state, Variant());
|
|
ERR_FAIL_COND_V(!process_state->tree->property_parent_map.has(node_state.base_path), Variant());
|
|
ERR_FAIL_COND_V(!process_state->tree->property_parent_map[node_state.base_path].has(p_name), Variant());
|
|
|
|
StringName path = process_state->tree->property_parent_map[node_state.base_path][p_name];
|
|
return process_state->tree->property_map[path].first;
|
|
}
|
|
|
|
void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) {
|
|
Dictionary cn;
|
|
if (GDVIRTUAL_CALL(_get_child_nodes, cn)) {
|
|
List<Variant> keys;
|
|
cn.get_key_list(&keys);
|
|
for (const Variant &E : keys) {
|
|
ChildNode child;
|
|
child.name = E;
|
|
child.node = cn[E];
|
|
r_child_nodes->push_back(child);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AnimationNode::blend_animation(const StringName &p_animation, AnimationMixer::PlaybackInfo p_playback_info) {
|
|
ERR_FAIL_NULL(process_state);
|
|
p_playback_info.track_weights = node_state.track_weights;
|
|
process_state->tree->make_animation_instance(p_animation, p_playback_info);
|
|
}
|
|
|
|
double AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
|
process_state = p_process_state;
|
|
double t = process(p_playback_info, p_test_only);
|
|
process_state = nullptr;
|
|
return t;
|
|
}
|
|
|
|
void AnimationNode::make_invalid(const String &p_reason) {
|
|
ERR_FAIL_NULL(process_state);
|
|
process_state->valid = false;
|
|
if (!process_state->invalid_reasons.is_empty()) {
|
|
process_state->invalid_reasons += "\n";
|
|
}
|
|
process_state->invalid_reasons += String::utf8("• ") + p_reason;
|
|
}
|
|
|
|
AnimationTree *AnimationNode::get_animation_tree() const {
|
|
ERR_FAIL_NULL_V(process_state, nullptr);
|
|
return process_state->tree;
|
|
}
|
|
|
|
double AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) {
|
|
ERR_FAIL_INDEX_V(p_input, inputs.size(), 0);
|
|
|
|
AnimationNodeBlendTree *blend_tree = Object::cast_to<AnimationNodeBlendTree>(node_state.parent);
|
|
ERR_FAIL_NULL_V(blend_tree, 0);
|
|
|
|
// Update connections.
|
|
StringName current_name = blend_tree->get_node_name(Ref<AnimationNode>(this));
|
|
node_state.connections = blend_tree->get_node_connection_array(current_name);
|
|
|
|
// Get node which is connected input port.
|
|
StringName node_name = node_state.connections[p_input];
|
|
if (!blend_tree->has_node(node_name)) {
|
|
make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), current_name));
|
|
return 0;
|
|
}
|
|
|
|
Ref<AnimationNode> node = blend_tree->get_node(node_name);
|
|
|
|
real_t activity = 0.0;
|
|
Vector<AnimationTree::Activity> *activity_ptr = process_state->tree->input_activity_map.getptr(node_state.base_path);
|
|
double ret = _blend_node(node, node_name, nullptr, p_playback_info, p_filter, p_sync, p_test_only, &activity);
|
|
|
|
if (activity_ptr && p_input < activity_ptr->size()) {
|
|
activity_ptr->write[p_input].last_pass = process_state->last_pass;
|
|
activity_ptr->write[p_input].activity = activity;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
double AnimationNode::blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) {
|
|
p_node->node_state.connections.clear();
|
|
return _blend_node(p_node, p_subpath, this, p_playback_info, p_filter, p_sync, p_test_only, nullptr);
|
|
}
|
|
|
|
double AnimationNode::_blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only, real_t *r_activity) {
|
|
ERR_FAIL_COND_V(!p_node.is_valid(), 0);
|
|
ERR_FAIL_NULL_V(process_state, 0);
|
|
|
|
int blend_count = node_state.track_weights.size();
|
|
|
|
if (p_node->node_state.track_weights.size() != blend_count) {
|
|
p_node->node_state.track_weights.resize(blend_count);
|
|
}
|
|
|
|
real_t *blendw = p_node->node_state.track_weights.ptrw();
|
|
const real_t *blendr = node_state.track_weights.ptr();
|
|
|
|
bool any_valid = false;
|
|
|
|
if (has_filter() && is_filter_enabled() && p_filter != FILTER_IGNORE) {
|
|
for (int i = 0; i < blend_count; i++) {
|
|
blendw[i] = 0.0; // All to zero by default.
|
|
}
|
|
|
|
for (const KeyValue<NodePath, bool> &E : filter) {
|
|
if (!process_state->track_map.has(E.key)) {
|
|
continue;
|
|
}
|
|
int idx = process_state->track_map[E.key];
|
|
blendw[idx] = 1.0; // Filtered goes to one.
|
|
}
|
|
|
|
switch (p_filter) {
|
|
case FILTER_IGNORE:
|
|
break; // Will not happen anyway.
|
|
case FILTER_PASS: {
|
|
// Values filtered pass, the rest don't.
|
|
for (int i = 0; i < blend_count; i++) {
|
|
if (blendw[i] == 0) { // Not filtered, does not pass.
|
|
continue;
|
|
}
|
|
|
|
blendw[i] = blendr[i] * p_playback_info.weight;
|
|
if (!Math::is_zero_approx(blendw[i])) {
|
|
any_valid = true;
|
|
}
|
|
}
|
|
|
|
} break;
|
|
case FILTER_STOP: {
|
|
// Values filtered don't pass, the rest are blended.
|
|
|
|
for (int i = 0; i < blend_count; i++) {
|
|
if (blendw[i] > 0) { // Filtered, does not pass.
|
|
continue;
|
|
}
|
|
|
|
blendw[i] = blendr[i] * p_playback_info.weight;
|
|
if (!Math::is_zero_approx(blendw[i])) {
|
|
any_valid = true;
|
|
}
|
|
}
|
|
|
|
} break;
|
|
case FILTER_BLEND: {
|
|
// Filtered values are blended, the rest are passed without blending.
|
|
|
|
for (int i = 0; i < blend_count; i++) {
|
|
if (blendw[i] == 1.0) {
|
|
blendw[i] = blendr[i] * p_playback_info.weight; // Filtered, blend.
|
|
} else {
|
|
blendw[i] = blendr[i]; // Not filtered, do not blend.
|
|
}
|
|
|
|
if (!Math::is_zero_approx(blendw[i])) {
|
|
any_valid = true;
|
|
}
|
|
}
|
|
|
|
} break;
|
|
}
|
|
} else {
|
|
for (int i = 0; i < blend_count; i++) {
|
|
// Regular blend.
|
|
blendw[i] = blendr[i] * p_playback_info.weight;
|
|
if (!Math::is_zero_approx(blendw[i])) {
|
|
any_valid = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (r_activity) {
|
|
*r_activity = 0;
|
|
for (int i = 0; i < blend_count; i++) {
|
|
*r_activity = MAX(*r_activity, Math::abs(blendw[i]));
|
|
}
|
|
}
|
|
|
|
String new_path;
|
|
AnimationNode *new_parent;
|
|
|
|
// This is the slowest part of processing, but as strings process in powers of 2, and the paths always exist, it will not result in that many allocations.
|
|
if (p_new_parent) {
|
|
new_parent = p_new_parent;
|
|
new_path = String(node_state.base_path) + String(p_subpath) + "/";
|
|
} else {
|
|
ERR_FAIL_NULL_V(node_state.parent, 0);
|
|
new_parent = node_state.parent;
|
|
new_path = String(new_parent->node_state.base_path) + String(p_subpath) + "/";
|
|
}
|
|
|
|
// 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.
|
|
p_node->node_state.base_path = new_path;
|
|
p_node->node_state.parent = new_parent;
|
|
if (!p_playback_info.seeked && !p_sync && !any_valid) {
|
|
p_playback_info.time = 0.0;
|
|
return p_node->_pre_process(process_state, p_playback_info, p_test_only);
|
|
}
|
|
return p_node->_pre_process(process_state, p_playback_info, p_test_only);
|
|
}
|
|
|
|
String AnimationNode::get_caption() const {
|
|
String ret = "Node";
|
|
GDVIRTUAL_CALL(_get_caption, ret);
|
|
return ret;
|
|
}
|
|
|
|
bool AnimationNode::add_input(const String &p_name) {
|
|
// Root nodes can't add inputs.
|
|
ERR_FAIL_COND_V(Object::cast_to<AnimationRootNode>(this) != nullptr, false);
|
|
Input input;
|
|
ERR_FAIL_COND_V(p_name.contains(".") || p_name.contains("/"), false);
|
|
input.name = p_name;
|
|
inputs.push_back(input);
|
|
emit_changed();
|
|
return true;
|
|
}
|
|
|
|
void AnimationNode::remove_input(int p_index) {
|
|
ERR_FAIL_INDEX(p_index, inputs.size());
|
|
inputs.remove_at(p_index);
|
|
emit_changed();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
double AnimationNode::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
|
process_state->is_testing = p_test_only;
|
|
return _process(p_playback_info, p_test_only);
|
|
}
|
|
|
|
double AnimationNode::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
|
double ret = 0;
|
|
GDVIRTUAL_CALL(_process, p_playback_info.time, p_playback_info.seeked, p_playback_info.is_external_seeking, p_test_only, ret);
|
|
return ret;
|
|
}
|
|
|
|
void AnimationNode::set_filter_path(const NodePath &p_path, bool p_enable) {
|
|
if (p_enable) {
|
|
filter[p_path] = true;
|
|
} else {
|
|
filter.erase(p_path);
|
|
}
|
|
}
|
|
|
|
void AnimationNode::set_filter_enabled(bool p_enable) {
|
|
filter_enabled = p_enable;
|
|
}
|
|
|
|
bool AnimationNode::is_filter_enabled() const {
|
|
return filter_enabled;
|
|
}
|
|
|
|
void AnimationNode::set_closable(bool p_closable) {
|
|
closable = p_closable;
|
|
}
|
|
|
|
bool AnimationNode::is_closable() const {
|
|
return closable;
|
|
}
|
|
|
|
bool AnimationNode::is_path_filtered(const NodePath &p_path) const {
|
|
return filter.has(p_path);
|
|
}
|
|
|
|
bool AnimationNode::has_filter() const {
|
|
bool ret = false;
|
|
GDVIRTUAL_CALL(_has_filter, ret);
|
|
return ret;
|
|
}
|
|
|
|
Array AnimationNode::_get_filters() const {
|
|
Array paths;
|
|
|
|
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.
|
|
|
|
return paths;
|
|
}
|
|
|
|
void AnimationNode::_set_filters(const Array &p_filters) {
|
|
filter.clear();
|
|
for (int i = 0; i < p_filters.size(); i++) {
|
|
set_filter_path(p_filters[i], true);
|
|
}
|
|
}
|
|
|
|
void AnimationNode::_validate_property(PropertyInfo &p_property) const {
|
|
if (!has_filter() && (p_property.name == "filter_enabled" || p_property.name == "filters")) {
|
|
p_property.usage = PROPERTY_USAGE_NONE;
|
|
}
|
|
}
|
|
|
|
Ref<AnimationNode> AnimationNode::get_child_by_name(const StringName &p_name) const {
|
|
Ref<AnimationNode> ret;
|
|
GDVIRTUAL_CALL(_get_child_by_name, p_name, ret);
|
|
return ret;
|
|
}
|
|
|
|
Ref<AnimationNode> AnimationNode::find_node_by_path(const String &p_name) const {
|
|
Vector<String> split = p_name.split("/");
|
|
Ref<AnimationNode> ret = const_cast<AnimationNode *>(this);
|
|
for (int i = 0; i < split.size(); i++) {
|
|
ret = ret->get_child_by_name(split[i]);
|
|
if (!ret.is_valid()) {
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void AnimationNode::blend_animation_ex(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag) {
|
|
AnimationMixer::PlaybackInfo info;
|
|
info.time = p_time;
|
|
info.delta = p_delta;
|
|
info.seeked = p_seeked;
|
|
info.is_external_seeking = p_is_external_seeking;
|
|
info.weight = p_blend;
|
|
info.looped_flag = p_looped_flag;
|
|
blend_animation(p_animation, info);
|
|
}
|
|
|
|
double AnimationNode::blend_node_ex(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, bool p_test_only) {
|
|
AnimationMixer::PlaybackInfo info;
|
|
info.time = p_time;
|
|
info.seeked = p_seek;
|
|
info.is_external_seeking = p_is_external_seeking;
|
|
info.weight = p_blend;
|
|
return blend_node(p_node, p_sub_path, info, p_filter, p_sync, p_test_only);
|
|
}
|
|
|
|
double AnimationNode::blend_input_ex(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) {
|
|
AnimationMixer::PlaybackInfo info;
|
|
info.time = p_time;
|
|
info.seeked = p_seek;
|
|
info.is_external_seeking = p_is_external_seeking;
|
|
info.weight = p_blend;
|
|
return blend_input(p_input, info, p_filter, p_sync, p_test_only);
|
|
}
|
|
|
|
void AnimationNode::_bind_methods() {
|
|
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);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_filter_enabled", "enable"), &AnimationNode::set_filter_enabled);
|
|
ClassDB::bind_method(D_METHOD("is_filter_enabled"), &AnimationNode::is_filter_enabled);
|
|
|
|
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", "is_external_seeking", "blend", "looped_flag"), &AnimationNode::blend_animation_ex, DEFVAL(Animation::LOOPED_FLAG_NONE));
|
|
ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "is_external_seeking", "blend", "filter", "sync", "test_only"), &AnimationNode::blend_node_ex, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(false));
|
|
ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "is_external_seeking", "blend", "filter", "sync", "test_only"), &AnimationNode::blend_input_ex, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(false));
|
|
|
|
ClassDB::bind_method(D_METHOD("set_parameter", "name", "value"), &AnimationNode::set_parameter);
|
|
ClassDB::bind_method(D_METHOD("get_parameter", "name"), &AnimationNode::get_parameter);
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_filter_enabled", "is_filter_enabled");
|
|
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "filters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_filters", "_get_filters");
|
|
|
|
GDVIRTUAL_BIND(_get_child_nodes);
|
|
GDVIRTUAL_BIND(_get_parameter_list);
|
|
GDVIRTUAL_BIND(_get_child_by_name, "name");
|
|
GDVIRTUAL_BIND(_get_parameter_default_value, "parameter");
|
|
GDVIRTUAL_BIND(_is_parameter_read_only, "parameter");
|
|
GDVIRTUAL_BIND(_process, "time", "seek", "is_external_seeking", "test_only");
|
|
GDVIRTUAL_BIND(_get_caption);
|
|
GDVIRTUAL_BIND(_has_filter);
|
|
|
|
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);
|
|
BIND_ENUM_CONSTANT(FILTER_STOP);
|
|
BIND_ENUM_CONSTANT(FILTER_BLEND);
|
|
}
|
|
|
|
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_root_animation_node(const Ref<AnimationRootNode> &p_animation_node) {
|
|
if (root_animation_node.is_valid()) {
|
|
root_animation_node->disconnect(SNAME("tree_changed"), callable_mp(this, &AnimationTree::_tree_changed));
|
|
root_animation_node->disconnect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationTree::_animation_node_renamed));
|
|
root_animation_node->disconnect(SNAME("animation_node_removed"), callable_mp(this, &AnimationTree::_animation_node_removed));
|
|
}
|
|
|
|
root_animation_node = p_animation_node;
|
|
|
|
if (root_animation_node.is_valid()) {
|
|
root_animation_node->connect(SNAME("tree_changed"), callable_mp(this, &AnimationTree::_tree_changed));
|
|
root_animation_node->connect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationTree::_animation_node_renamed));
|
|
root_animation_node->connect(SNAME("animation_node_removed"), callable_mp(this, &AnimationTree::_animation_node_removed));
|
|
}
|
|
|
|
properties_dirty = true;
|
|
|
|
update_configuration_warnings();
|
|
}
|
|
|
|
Ref<AnimationRootNode> AnimationTree::get_root_animation_node() const {
|
|
return root_animation_node;
|
|
}
|
|
|
|
bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) {
|
|
_update_properties(); // If properties need updating, update them.
|
|
|
|
if (!root_animation_node.is_valid()) {
|
|
return false;
|
|
}
|
|
|
|
{ // Setup.
|
|
process_pass++;
|
|
|
|
// Init process state.
|
|
process_state = AnimationNode::ProcessState();
|
|
process_state.tree = this;
|
|
process_state.valid = true;
|
|
process_state.invalid_reasons = "";
|
|
process_state.last_pass = process_pass;
|
|
process_state.track_map = p_track_map;
|
|
|
|
// Init node state for root AnimationNode.
|
|
root_animation_node->node_state.track_weights.resize(p_track_count);
|
|
real_t *src_blendsw = root_animation_node->node_state.track_weights.ptrw();
|
|
for (int i = 0; i < p_track_count; i++) {
|
|
src_blendsw[i] = 1.0; // By default all go to 1 for the root input.
|
|
}
|
|
root_animation_node->node_state.base_path = SceneStringNames::get_singleton()->parameters_base_path;
|
|
root_animation_node->node_state.parent = nullptr;
|
|
}
|
|
|
|
// Process.
|
|
{
|
|
PlaybackInfo pi;
|
|
|
|
if (started) {
|
|
// If started, seek.
|
|
pi.seeked = true;
|
|
root_animation_node->_pre_process(&process_state, pi, false);
|
|
started = false;
|
|
}
|
|
pi.seeked = false;
|
|
pi.time = p_delta;
|
|
root_animation_node->_pre_process(&process_state, pi, false);
|
|
}
|
|
|
|
if (!process_state.valid) {
|
|
return false; // State is not valid, abort process.
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AnimationTree::_set_active(bool p_active) {
|
|
_set_process(p_active);
|
|
started = p_active;
|
|
}
|
|
|
|
void AnimationTree::set_advance_expression_base_node(const NodePath &p_path) {
|
|
advance_expression_base_node = p_path;
|
|
}
|
|
|
|
NodePath AnimationTree::get_advance_expression_base_node() const {
|
|
return advance_expression_base_node;
|
|
}
|
|
|
|
bool AnimationTree::is_state_invalid() const {
|
|
return !process_state.valid;
|
|
}
|
|
|
|
String AnimationTree::get_invalid_state_reason() const {
|
|
return process_state.invalid_reasons;
|
|
}
|
|
|
|
uint64_t AnimationTree::get_last_process_pass() const {
|
|
return process_pass;
|
|
}
|
|
|
|
Array AnimationTree::get_configuration_warnings() const {
|
|
Array warnings = Node::get_configuration_warnings();
|
|
if (!root_animation_node.is_valid()) {
|
|
warnings.push_back(RTR("No root AnimationNode for the graph is set."));
|
|
}
|
|
return warnings;
|
|
}
|
|
|
|
void AnimationTree::_tree_changed() {
|
|
if (properties_dirty) {
|
|
return;
|
|
}
|
|
|
|
callable_mp(this, &AnimationTree::_update_properties).call_deferred();
|
|
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> p_node) {
|
|
ERR_FAIL_COND(p_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(p_node->get_instance_id())) {
|
|
property_reference_map[p_node->get_instance_id()] = p_base_path;
|
|
}
|
|
|
|
if (p_node->get_input_count() && !input_activity_map.has(p_base_path)) {
|
|
Vector<Activity> activity;
|
|
for (int i = 0; i < p_node->get_input_count(); i++) {
|
|
Activity a;
|
|
a.activity = 0;
|
|
a.last_pass = 0;
|
|
activity.push_back(a);
|
|
}
|
|
input_activity_map[p_base_path] = activity;
|
|
input_activity_map_get[String(p_base_path).substr(0, String(p_base_path).length() - 1)] = &input_activity_map[p_base_path];
|
|
}
|
|
|
|
List<PropertyInfo> plist;
|
|
p_node->get_parameter_list(&plist);
|
|
for (PropertyInfo &pinfo : plist) {
|
|
StringName key = pinfo.name;
|
|
|
|
if (!property_map.has(p_base_path + key)) {
|
|
Pair<Variant, bool> param;
|
|
param.first = p_node->get_parameter_default_value(key);
|
|
param.second = p_node->is_parameter_read_only(key);
|
|
property_map[p_base_path + key] = param;
|
|
}
|
|
|
|
property_parent_map[p_base_path][key] = p_base_path + key;
|
|
|
|
pinfo.name = p_base_path + key;
|
|
properties.push_back(pinfo);
|
|
}
|
|
|
|
List<AnimationNode::ChildNode> children;
|
|
p_node->get_child_nodes(&children);
|
|
|
|
for (const AnimationNode::ChildNode &E : children) {
|
|
_update_properties_for_node(p_base_path + E.name + "/", E.node);
|
|
}
|
|
}
|
|
|
|
void AnimationTree::_update_properties() {
|
|
if (!properties_dirty) {
|
|
return;
|
|
}
|
|
|
|
properties.clear();
|
|
property_reference_map.clear();
|
|
property_parent_map.clear();
|
|
input_activity_map.clear();
|
|
input_activity_map_get.clear();
|
|
|
|
if (root_animation_node.is_valid()) {
|
|
_update_properties_for_node(SceneStringNames::get_singleton()->parameters_base_path, root_animation_node);
|
|
}
|
|
|
|
properties_dirty = false;
|
|
|
|
notify_property_list_changed();
|
|
}
|
|
|
|
void AnimationTree::_notification(int p_what) {
|
|
switch (p_what) {
|
|
case NOTIFICATION_ENTER_TREE: {
|
|
_setup_animation_player();
|
|
if (active) {
|
|
_set_process(true);
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void AnimationTree::set_animation_player(const NodePath &p_path) {
|
|
animation_player = p_path;
|
|
if (p_path.is_empty()) {
|
|
set_root_node(SceneStringNames::get_singleton()->path_pp);
|
|
while (animation_libraries.size()) {
|
|
remove_animation_library(animation_libraries[0].name);
|
|
}
|
|
}
|
|
#ifdef TOOLS_ENABLED
|
|
emit_signal(SNAME("animation_player_changed")); // Needs to unpin AnimationPlayerEditor.
|
|
emit_signal(SNAME("mixer_updated"));
|
|
#endif // TOOLS_ENABLED
|
|
_setup_animation_player();
|
|
notify_property_list_changed();
|
|
}
|
|
|
|
NodePath AnimationTree::get_animation_player() const {
|
|
return animation_player;
|
|
}
|
|
|
|
void AnimationTree::_setup_animation_player() {
|
|
if (!is_inside_tree()) {
|
|
return;
|
|
}
|
|
|
|
cache_valid = false;
|
|
|
|
if (animation_player.is_empty()) {
|
|
clear_caches();
|
|
return;
|
|
}
|
|
|
|
// Using AnimationPlayer here is for compatibility. Changing to AnimationMixer needs extra work like error handling.
|
|
AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node_or_null(animation_player));
|
|
if (player) {
|
|
if (!player->is_connected(SNAME("caches_cleared"), callable_mp(this, &AnimationTree::_setup_animation_player))) {
|
|
player->connect(SNAME("caches_cleared"), callable_mp(this, &AnimationTree::_setup_animation_player), CONNECT_DEFERRED);
|
|
}
|
|
if (!player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationTree::_setup_animation_player))) {
|
|
player->connect(SNAME("animation_list_changed"), callable_mp(this, &AnimationTree::_setup_animation_player), CONNECT_DEFERRED);
|
|
}
|
|
Node *root = player->get_node_or_null(player->get_root_node());
|
|
if (root) {
|
|
set_root_node(get_path_to(root, true));
|
|
}
|
|
while (animation_libraries.size()) {
|
|
remove_animation_library(animation_libraries[0].name);
|
|
}
|
|
List<StringName> list;
|
|
player->get_animation_library_list(&list);
|
|
for (int i = 0; i < list.size(); i++) {
|
|
Ref<AnimationLibrary> lib = player->get_animation_library(list[i]);
|
|
if (lib.is_valid()) {
|
|
add_animation_library(list[i], lib);
|
|
}
|
|
}
|
|
}
|
|
|
|
clear_caches();
|
|
}
|
|
|
|
void AnimationTree::_validate_property(PropertyInfo &p_property) const {
|
|
AnimationMixer::_validate_property(p_property);
|
|
|
|
if (!animation_player.is_empty()) {
|
|
if (p_property.name == "root_node" || p_property.name.begins_with("libraries")) {
|
|
p_property.usage |= PROPERTY_USAGE_READ_ONLY;
|
|
}
|
|
if (p_property.name.begins_with("libraries")) {
|
|
p_property.usage &= ~PROPERTY_USAGE_STORAGE;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AnimationTree::_set(const StringName &p_name, const Variant &p_value) {
|
|
#ifndef DISABLE_DEPRECATED
|
|
String name = p_name;
|
|
if (name == "process_callback") {
|
|
set_callback_mode_process(static_cast<AnimationCallbackModeProcess>((int)p_value));
|
|
return true;
|
|
}
|
|
#endif // DISABLE_DEPRECATED
|
|
if (properties_dirty) {
|
|
_update_properties();
|
|
}
|
|
|
|
if (property_map.has(p_name)) {
|
|
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;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool AnimationTree::_get(const StringName &p_name, Variant &r_ret) const {
|
|
#ifndef DISABLE_DEPRECATED
|
|
if (p_name == "process_callback") {
|
|
r_ret = get_callback_mode_process();
|
|
return true;
|
|
}
|
|
#endif // DISABLE_DEPRECATED
|
|
if (properties_dirty) {
|
|
const_cast<AnimationTree *>(this)->_update_properties();
|
|
}
|
|
|
|
if (property_map.has(p_name)) {
|
|
r_ret = property_map[p_name].first;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void AnimationTree::_get_property_list(List<PropertyInfo> *p_list) const {
|
|
if (properties_dirty) {
|
|
const_cast<AnimationTree *>(this)->_update_properties();
|
|
}
|
|
|
|
for (const PropertyInfo &E : properties) {
|
|
p_list->push_back(E);
|
|
}
|
|
}
|
|
|
|
real_t AnimationTree::get_connection_activity(const StringName &p_path, int p_connection) const {
|
|
if (!input_activity_map_get.has(p_path)) {
|
|
return 0;
|
|
}
|
|
const Vector<Activity> *activity = input_activity_map_get[p_path];
|
|
|
|
if (!activity || p_connection < 0 || p_connection >= activity->size()) {
|
|
return 0;
|
|
}
|
|
|
|
if ((*activity)[p_connection].last_pass != process_pass) {
|
|
return 0;
|
|
}
|
|
|
|
return (*activity)[p_connection].activity;
|
|
}
|
|
|
|
void AnimationTree::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("set_tree_root", "animation_node"), &AnimationTree::set_root_animation_node);
|
|
ClassDB::bind_method(D_METHOD("get_tree_root"), &AnimationTree::get_root_animation_node);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_advance_expression_base_node", "path"), &AnimationTree::set_advance_expression_base_node);
|
|
ClassDB::bind_method(D_METHOD("get_advance_expression_base_node"), &AnimationTree::get_advance_expression_base_node);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_animation_player", "path"), &AnimationTree::set_animation_player);
|
|
ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationTree::get_animation_player);
|
|
|
|
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode"), "set_tree_root", "get_tree_root");
|
|
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::NODE_PATH, "anim_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player");
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
ADD_SIGNAL(MethodInfo(SNAME("animation_player_changed")));
|
|
#endif // TOOLS_ENABLED
|
|
}
|
|
|
|
AnimationTree::AnimationTree() {
|
|
deterministic = true;
|
|
}
|
|
|
|
AnimationTree::~AnimationTree() {
|
|
}
|