feat: godot-engine-source-4.3-stable
This commit is contained in:
parent
c59a7dcade
commit
7125d019b5
11149 changed files with 5070401 additions and 0 deletions
21
engine/scene/animation/SCsub
Normal file
21
engine/scene/animation/SCsub
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
|
||||
# Thirdparty code
|
||||
|
||||
thirdparty_obj = []
|
||||
|
||||
env_thirdparty = env.Clone()
|
||||
env_thirdparty.disable_warnings()
|
||||
env.scene_sources += thirdparty_obj
|
||||
|
||||
# Godot source files
|
||||
|
||||
scene_obj = []
|
||||
|
||||
env.add_source_files(scene_obj, "*.cpp")
|
||||
env.scene_sources += scene_obj
|
||||
|
||||
# Needed to force rebuilding the scene files when the thirdparty code is updated.
|
||||
env.Depends(scene_obj, thirdparty_obj)
|
||||
427
engine/scene/animation/animation_blend_space_1d.cpp
Normal file
427
engine/scene/animation/animation_blend_space_1d.cpp
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
/**************************************************************************/
|
||||
/* animation_blend_space_1d.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "animation_blend_space_1d.h"
|
||||
|
||||
#include "animation_blend_tree.h"
|
||||
|
||||
void AnimationNodeBlendSpace1D::get_parameter_list(List<PropertyInfo> *r_list) const {
|
||||
AnimationNode::get_parameter_list(r_list);
|
||||
r_list->push_back(PropertyInfo(Variant::FLOAT, blend_position));
|
||||
r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
|
||||
}
|
||||
|
||||
Variant AnimationNodeBlendSpace1D::get_parameter_default_value(const StringName &p_parameter) const {
|
||||
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
|
||||
if (ret != Variant()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (p_parameter == closest) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Ref<AnimationNode> AnimationNodeBlendSpace1D::get_child_by_name(const StringName &p_name) const {
|
||||
return get_blend_point_node(p_name.operator String().to_int());
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::_validate_property(PropertyInfo &p_property) const {
|
||||
if (p_property.name.begins_with("blend_point_")) {
|
||||
String left = p_property.name.get_slicec('/', 0);
|
||||
int idx = left.get_slicec('_', 2).to_int();
|
||||
if (idx >= blend_points_used) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::_tree_changed() {
|
||||
AnimationRootNode::_tree_changed();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) {
|
||||
AnimationRootNode::_animation_node_renamed(p_oid, p_old_name, p_new_name);
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) {
|
||||
AnimationRootNode::_animation_node_removed(p_oid, p_node);
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace1D::add_blend_point, DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace1D::set_blend_point_position);
|
||||
ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace1D::get_blend_point_position);
|
||||
ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace1D::set_blend_point_node);
|
||||
ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace1D::get_blend_point_node);
|
||||
ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace1D::remove_blend_point);
|
||||
ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace1D::get_blend_point_count);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_min_space", "min_space"), &AnimationNodeBlendSpace1D::set_min_space);
|
||||
ClassDB::bind_method(D_METHOD("get_min_space"), &AnimationNodeBlendSpace1D::get_min_space);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_max_space", "max_space"), &AnimationNodeBlendSpace1D::set_max_space);
|
||||
ClassDB::bind_method(D_METHOD("get_max_space"), &AnimationNodeBlendSpace1D::get_max_space);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_snap", "snap"), &AnimationNodeBlendSpace1D::set_snap);
|
||||
ClassDB::bind_method(D_METHOD("get_snap"), &AnimationNodeBlendSpace1D::get_snap);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_value_label", "text"), &AnimationNodeBlendSpace1D::set_value_label);
|
||||
ClassDB::bind_method(D_METHOD("get_value_label"), &AnimationNodeBlendSpace1D::get_value_label);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_blend_mode", "mode"), &AnimationNodeBlendSpace1D::set_blend_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_blend_mode"), &AnimationNodeBlendSpace1D::get_blend_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlendSpace1D::set_use_sync);
|
||||
ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlendSpace1D::is_using_sync);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace1D::_add_blend_point);
|
||||
|
||||
for (int i = 0; i < MAX_BLEND_POINTS; i++) {
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_add_blend_point", "get_blend_point_node", i);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i);
|
||||
}
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_min_space", "get_min_space");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_space", "get_max_space");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_snap", "get_snap");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_value_label", "get_value_label");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Interpolated,Discrete,Carry", PROPERTY_USAGE_NO_EDITOR), "set_blend_mode", "get_blend_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_sync", "is_using_sync");
|
||||
|
||||
BIND_ENUM_CONSTANT(BLEND_MODE_INTERPOLATED);
|
||||
BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE);
|
||||
BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE_CARRY);
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::get_child_nodes(List<ChildNode> *r_child_nodes) {
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
ChildNode cn;
|
||||
cn.name = itos(i);
|
||||
cn.node = blend_points[i].node;
|
||||
r_child_nodes->push_back(cn);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::add_blend_point(const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index) {
|
||||
ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS);
|
||||
ERR_FAIL_COND(p_node.is_null());
|
||||
|
||||
ERR_FAIL_COND(p_at_index < -1 || p_at_index > blend_points_used);
|
||||
|
||||
if (p_at_index == -1 || p_at_index == blend_points_used) {
|
||||
p_at_index = blend_points_used;
|
||||
} else {
|
||||
for (int i = blend_points_used - 1; i > p_at_index; i--) {
|
||||
blend_points[i] = blend_points[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
blend_points[p_at_index].node = p_node;
|
||||
blend_points[p_at_index].position = p_position;
|
||||
|
||||
blend_points[p_at_index].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed), CONNECT_REFERENCE_COUNTED);
|
||||
blend_points[p_at_index].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED);
|
||||
blend_points[p_at_index].node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed), CONNECT_REFERENCE_COUNTED);
|
||||
|
||||
blend_points_used++;
|
||||
emit_signal(SNAME("tree_changed"));
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::set_blend_point_position(int p_point, float p_position) {
|
||||
ERR_FAIL_INDEX(p_point, blend_points_used);
|
||||
|
||||
blend_points[p_point].position = p_position;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node) {
|
||||
ERR_FAIL_INDEX(p_point, blend_points_used);
|
||||
ERR_FAIL_COND(p_node.is_null());
|
||||
|
||||
if (blend_points[p_point].node.is_valid()) {
|
||||
blend_points[p_point].node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed));
|
||||
blend_points[p_point].node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed));
|
||||
blend_points[p_point].node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed));
|
||||
}
|
||||
|
||||
blend_points[p_point].node = p_node;
|
||||
blend_points[p_point].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed), CONNECT_REFERENCE_COUNTED);
|
||||
blend_points[p_point].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED);
|
||||
blend_points[p_point].node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed), CONNECT_REFERENCE_COUNTED);
|
||||
|
||||
emit_signal(SNAME("tree_changed"));
|
||||
}
|
||||
|
||||
float AnimationNodeBlendSpace1D::get_blend_point_position(int p_point) const {
|
||||
ERR_FAIL_INDEX_V(p_point, MAX_BLEND_POINTS, 0);
|
||||
return blend_points[p_point].position;
|
||||
}
|
||||
|
||||
Ref<AnimationRootNode> AnimationNodeBlendSpace1D::get_blend_point_node(int p_point) const {
|
||||
ERR_FAIL_INDEX_V(p_point, MAX_BLEND_POINTS, Ref<AnimationRootNode>());
|
||||
return blend_points[p_point].node;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) {
|
||||
ERR_FAIL_INDEX(p_point, blend_points_used);
|
||||
|
||||
ERR_FAIL_COND(blend_points[p_point].node.is_null());
|
||||
blend_points[p_point].node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace1D::_tree_changed));
|
||||
blend_points[p_point].node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_renamed));
|
||||
blend_points[p_point].node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace1D::_animation_node_removed));
|
||||
|
||||
for (int i = p_point; i < blend_points_used - 1; i++) {
|
||||
blend_points[i] = blend_points[i + 1];
|
||||
}
|
||||
|
||||
blend_points_used--;
|
||||
|
||||
emit_signal(SNAME("animation_node_removed"), get_instance_id(), itos(p_point));
|
||||
emit_signal(SNAME("tree_changed"));
|
||||
}
|
||||
|
||||
int AnimationNodeBlendSpace1D::get_blend_point_count() const {
|
||||
return blend_points_used;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::set_min_space(float p_min) {
|
||||
min_space = p_min;
|
||||
|
||||
if (min_space >= max_space) {
|
||||
min_space = max_space - 1;
|
||||
}
|
||||
}
|
||||
|
||||
float AnimationNodeBlendSpace1D::get_min_space() const {
|
||||
return min_space;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::set_max_space(float p_max) {
|
||||
max_space = p_max;
|
||||
|
||||
if (max_space <= min_space) {
|
||||
max_space = min_space + 1;
|
||||
}
|
||||
}
|
||||
|
||||
float AnimationNodeBlendSpace1D::get_max_space() const {
|
||||
return max_space;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::set_snap(float p_snap) {
|
||||
snap = p_snap;
|
||||
}
|
||||
|
||||
float AnimationNodeBlendSpace1D::get_snap() const {
|
||||
return snap;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::set_value_label(const String &p_label) {
|
||||
value_label = p_label;
|
||||
}
|
||||
|
||||
String AnimationNodeBlendSpace1D::get_value_label() const {
|
||||
return value_label;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::set_blend_mode(BlendMode p_blend_mode) {
|
||||
blend_mode = p_blend_mode;
|
||||
}
|
||||
|
||||
AnimationNodeBlendSpace1D::BlendMode AnimationNodeBlendSpace1D::get_blend_mode() const {
|
||||
return blend_mode;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::set_use_sync(bool p_sync) {
|
||||
sync = p_sync;
|
||||
}
|
||||
|
||||
bool AnimationNodeBlendSpace1D::is_using_sync() const {
|
||||
return sync;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node) {
|
||||
if (p_index == blend_points_used) {
|
||||
add_blend_point(p_node, 0);
|
||||
} else {
|
||||
set_blend_point_node(p_index, p_node);
|
||||
}
|
||||
}
|
||||
|
||||
AnimationNode::NodeTimeInfo AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
||||
if (!blend_points_used) {
|
||||
return NodeTimeInfo();
|
||||
}
|
||||
|
||||
AnimationMixer::PlaybackInfo pi = p_playback_info;
|
||||
|
||||
if (blend_points_used == 1) {
|
||||
// only one point available, just play that animation
|
||||
pi.weight = 1.0;
|
||||
return blend_node(blend_points[0].node, blend_points[0].name, pi, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
double blend_pos = get_parameter(blend_position);
|
||||
int cur_closest = get_parameter(closest);
|
||||
NodeTimeInfo mind;
|
||||
|
||||
if (blend_mode == BLEND_MODE_INTERPOLATED) {
|
||||
int point_lower = -1;
|
||||
float pos_lower = 0.0;
|
||||
int point_higher = -1;
|
||||
float pos_higher = 0.0;
|
||||
|
||||
// find the closest two points to blend between
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
float pos = blend_points[i].position;
|
||||
|
||||
if (pos <= blend_pos) {
|
||||
if (point_lower == -1 || pos > pos_lower) {
|
||||
point_lower = i;
|
||||
pos_lower = pos;
|
||||
}
|
||||
} else if (point_higher == -1 || pos < pos_higher) {
|
||||
point_higher = i;
|
||||
pos_higher = pos;
|
||||
}
|
||||
}
|
||||
|
||||
// fill in weights
|
||||
float weights[MAX_BLEND_POINTS] = {};
|
||||
if (point_lower == -1 && point_higher != -1) {
|
||||
// we are on the left side, no other point to the left
|
||||
// we just play the next point.
|
||||
|
||||
weights[point_higher] = 1.0;
|
||||
} else if (point_higher == -1) {
|
||||
// we are on the right side, no other point to the right
|
||||
// we just play the previous point
|
||||
|
||||
weights[point_lower] = 1.0;
|
||||
} else {
|
||||
// we are between two points.
|
||||
// figure out weights, then blend the animations
|
||||
|
||||
float distance_between_points = pos_higher - pos_lower;
|
||||
|
||||
float current_pos_inbetween = blend_pos - pos_lower;
|
||||
|
||||
float blend_percentage = current_pos_inbetween / distance_between_points;
|
||||
|
||||
float blend_lower = 1.0 - blend_percentage;
|
||||
float blend_higher = blend_percentage;
|
||||
|
||||
weights[point_lower] = blend_lower;
|
||||
weights[point_higher] = blend_higher;
|
||||
}
|
||||
|
||||
// actually blend the animations now
|
||||
bool first = true;
|
||||
double max_weight = 0.0;
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (i == point_lower || i == point_higher) {
|
||||
pi.weight = weights[i];
|
||||
NodeTimeInfo t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
|
||||
if (first || pi.weight > max_weight) {
|
||||
max_weight = pi.weight;
|
||||
mind = t;
|
||||
first = false;
|
||||
}
|
||||
} else if (sync) {
|
||||
pi.weight = 0;
|
||||
blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int new_closest = -1;
|
||||
double new_closest_dist = 1e20;
|
||||
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
double d = abs(blend_points[i].position - blend_pos);
|
||||
if (d < new_closest_dist) {
|
||||
new_closest = i;
|
||||
new_closest_dist = d;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_closest != cur_closest && new_closest != -1) {
|
||||
NodeTimeInfo from;
|
||||
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
|
||||
//for ping-pong loop
|
||||
Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
|
||||
Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node);
|
||||
if (!na_c.is_null() && !na_n.is_null()) {
|
||||
na_n->set_backward(na_c->is_backward());
|
||||
}
|
||||
//see how much animation remains
|
||||
pi.seeked = false;
|
||||
pi.weight = 0;
|
||||
from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
pi.time = from.position;
|
||||
pi.seeked = true;
|
||||
pi.weight = 1.0;
|
||||
mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);
|
||||
cur_closest = new_closest;
|
||||
} else {
|
||||
pi.weight = 1.0;
|
||||
mind = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
if (sync) {
|
||||
pi = p_playback_info;
|
||||
pi.weight = 0;
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (i != cur_closest) {
|
||||
blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_parameter(closest, cur_closest);
|
||||
return mind;
|
||||
}
|
||||
|
||||
String AnimationNodeBlendSpace1D::get_caption() const {
|
||||
return "BlendSpace1D";
|
||||
}
|
||||
|
||||
AnimationNodeBlendSpace1D::AnimationNodeBlendSpace1D() {
|
||||
for (int i = 0; i < MAX_BLEND_POINTS; i++) {
|
||||
blend_points[i].name = itos(i);
|
||||
}
|
||||
}
|
||||
|
||||
AnimationNodeBlendSpace1D::~AnimationNodeBlendSpace1D() {
|
||||
}
|
||||
127
engine/scene/animation/animation_blend_space_1d.h
Normal file
127
engine/scene/animation/animation_blend_space_1d.h
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/**************************************************************************/
|
||||
/* animation_blend_space_1d.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANIMATION_BLEND_SPACE_1D_H
|
||||
#define ANIMATION_BLEND_SPACE_1D_H
|
||||
|
||||
#include "scene/animation/animation_tree.h"
|
||||
|
||||
class AnimationNodeBlendSpace1D : public AnimationRootNode {
|
||||
GDCLASS(AnimationNodeBlendSpace1D, AnimationRootNode);
|
||||
|
||||
public:
|
||||
enum BlendMode {
|
||||
BLEND_MODE_INTERPOLATED,
|
||||
BLEND_MODE_DISCRETE,
|
||||
BLEND_MODE_DISCRETE_CARRY,
|
||||
};
|
||||
|
||||
protected:
|
||||
enum {
|
||||
MAX_BLEND_POINTS = 64
|
||||
};
|
||||
|
||||
struct BlendPoint {
|
||||
StringName name;
|
||||
Ref<AnimationRootNode> node;
|
||||
float position = 0.0;
|
||||
};
|
||||
|
||||
BlendPoint blend_points[MAX_BLEND_POINTS];
|
||||
int blend_points_used = 0;
|
||||
|
||||
float max_space = 1.0;
|
||||
float min_space = -1.0;
|
||||
|
||||
float snap = 0.1;
|
||||
|
||||
String value_label = "value";
|
||||
|
||||
void _add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node);
|
||||
|
||||
StringName blend_position = "blend_position";
|
||||
StringName closest = "closest";
|
||||
|
||||
BlendMode blend_mode = BLEND_MODE_INTERPOLATED;
|
||||
|
||||
bool sync = false;
|
||||
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
static void _bind_methods();
|
||||
|
||||
virtual void _tree_changed() override;
|
||||
virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) override;
|
||||
virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override;
|
||||
|
||||
public:
|
||||
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
|
||||
virtual void get_child_nodes(List<ChildNode> *r_child_nodes) override;
|
||||
|
||||
void add_blend_point(const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index = -1);
|
||||
void set_blend_point_position(int p_point, float p_position);
|
||||
void set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node);
|
||||
|
||||
float get_blend_point_position(int p_point) const;
|
||||
Ref<AnimationRootNode> get_blend_point_node(int p_point) const;
|
||||
void remove_blend_point(int p_point);
|
||||
int get_blend_point_count() const;
|
||||
|
||||
void set_min_space(float p_min);
|
||||
float get_min_space() const;
|
||||
|
||||
void set_max_space(float p_max);
|
||||
float get_max_space() const;
|
||||
|
||||
void set_snap(float p_snap);
|
||||
float get_snap() const;
|
||||
|
||||
void set_value_label(const String &p_label);
|
||||
String get_value_label() const;
|
||||
|
||||
void set_blend_mode(BlendMode p_blend_mode);
|
||||
BlendMode get_blend_mode() const;
|
||||
|
||||
void set_use_sync(bool p_sync);
|
||||
bool is_using_sync() const;
|
||||
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
String get_caption() const override;
|
||||
|
||||
Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
|
||||
|
||||
AnimationNodeBlendSpace1D();
|
||||
~AnimationNodeBlendSpace1D();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AnimationNodeBlendSpace1D::BlendMode)
|
||||
|
||||
#endif // ANIMATION_BLEND_SPACE_1D_H
|
||||
728
engine/scene/animation/animation_blend_space_2d.cpp
Normal file
728
engine/scene/animation/animation_blend_space_2d.cpp
Normal file
|
|
@ -0,0 +1,728 @@
|
|||
/**************************************************************************/
|
||||
/* animation_blend_space_2d.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "animation_blend_space_2d.h"
|
||||
|
||||
#include "animation_blend_tree.h"
|
||||
#include "core/math/geometry_2d.h"
|
||||
|
||||
void AnimationNodeBlendSpace2D::get_parameter_list(List<PropertyInfo> *r_list) const {
|
||||
AnimationNode::get_parameter_list(r_list);
|
||||
r_list->push_back(PropertyInfo(Variant::VECTOR2, blend_position));
|
||||
r_list->push_back(PropertyInfo(Variant::INT, closest, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE));
|
||||
}
|
||||
|
||||
Variant AnimationNodeBlendSpace2D::get_parameter_default_value(const StringName &p_parameter) const {
|
||||
Variant ret = AnimationNode::get_parameter_default_value(p_parameter);
|
||||
if (ret != Variant()) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (p_parameter == closest) {
|
||||
return -1;
|
||||
} else {
|
||||
return Vector2();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::get_child_nodes(List<ChildNode> *r_child_nodes) {
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
ChildNode cn;
|
||||
cn.name = itos(i);
|
||||
cn.node = blend_points[i].node;
|
||||
r_child_nodes->push_back(cn);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::add_blend_point(const Ref<AnimationRootNode> &p_node, const Vector2 &p_position, int p_at_index) {
|
||||
ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS);
|
||||
ERR_FAIL_COND(p_node.is_null());
|
||||
ERR_FAIL_COND(p_at_index < -1 || p_at_index > blend_points_used);
|
||||
|
||||
if (p_at_index == -1 || p_at_index == blend_points_used) {
|
||||
p_at_index = blend_points_used;
|
||||
} else {
|
||||
for (int i = blend_points_used - 1; i > p_at_index; i--) {
|
||||
blend_points[i] = blend_points[i - 1];
|
||||
}
|
||||
for (int i = 0; i < triangles.size(); i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (triangles[i].points[j] >= p_at_index) {
|
||||
triangles.write[i].points[j]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
blend_points[p_at_index].node = p_node;
|
||||
blend_points[p_at_index].position = p_position;
|
||||
|
||||
blend_points[p_at_index].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed), CONNECT_REFERENCE_COUNTED);
|
||||
blend_points[p_at_index].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED);
|
||||
blend_points[p_at_index].node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_removed), CONNECT_REFERENCE_COUNTED);
|
||||
blend_points_used++;
|
||||
|
||||
_queue_auto_triangles();
|
||||
|
||||
emit_signal(SNAME("tree_changed"));
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::set_blend_point_position(int p_point, const Vector2 &p_position) {
|
||||
ERR_FAIL_INDEX(p_point, blend_points_used);
|
||||
blend_points[p_point].position = p_position;
|
||||
_queue_auto_triangles();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node) {
|
||||
ERR_FAIL_INDEX(p_point, blend_points_used);
|
||||
ERR_FAIL_COND(p_node.is_null());
|
||||
|
||||
if (blend_points[p_point].node.is_valid()) {
|
||||
blend_points[p_point].node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed));
|
||||
blend_points[p_point].node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_renamed));
|
||||
blend_points[p_point].node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_removed));
|
||||
}
|
||||
blend_points[p_point].node = p_node;
|
||||
blend_points[p_point].node->connect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed), CONNECT_REFERENCE_COUNTED);
|
||||
blend_points[p_point].node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_renamed), CONNECT_REFERENCE_COUNTED);
|
||||
blend_points[p_point].node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_removed), CONNECT_REFERENCE_COUNTED);
|
||||
|
||||
emit_signal(SNAME("tree_changed"));
|
||||
}
|
||||
|
||||
Vector2 AnimationNodeBlendSpace2D::get_blend_point_position(int p_point) const {
|
||||
ERR_FAIL_INDEX_V(p_point, MAX_BLEND_POINTS, Vector2());
|
||||
return blend_points[p_point].position;
|
||||
}
|
||||
|
||||
Ref<AnimationRootNode> AnimationNodeBlendSpace2D::get_blend_point_node(int p_point) const {
|
||||
ERR_FAIL_INDEX_V(p_point, MAX_BLEND_POINTS, Ref<AnimationRootNode>());
|
||||
return blend_points[p_point].node;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) {
|
||||
ERR_FAIL_INDEX(p_point, blend_points_used);
|
||||
|
||||
ERR_FAIL_COND(blend_points[p_point].node.is_null());
|
||||
blend_points[p_point].node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendSpace2D::_tree_changed));
|
||||
blend_points[p_point].node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_renamed));
|
||||
blend_points[p_point].node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendSpace2D::_animation_node_removed));
|
||||
|
||||
for (int i = 0; i < triangles.size(); i++) {
|
||||
bool erase = false;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (triangles[i].points[j] == p_point) {
|
||||
erase = true;
|
||||
break;
|
||||
} else if (triangles[i].points[j] > p_point) {
|
||||
triangles.write[i].points[j]--;
|
||||
}
|
||||
}
|
||||
if (erase) {
|
||||
triangles.remove_at(i);
|
||||
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = p_point; i < blend_points_used - 1; i++) {
|
||||
blend_points[i] = blend_points[i + 1];
|
||||
}
|
||||
blend_points_used--;
|
||||
|
||||
emit_signal(SNAME("animation_node_removed"), get_instance_id(), itos(p_point));
|
||||
emit_signal(SNAME("tree_changed"));
|
||||
}
|
||||
|
||||
int AnimationNodeBlendSpace2D::get_blend_point_count() const {
|
||||
return blend_points_used;
|
||||
}
|
||||
|
||||
bool AnimationNodeBlendSpace2D::has_triangle(int p_x, int p_y, int p_z) const {
|
||||
ERR_FAIL_INDEX_V(p_x, blend_points_used, false);
|
||||
ERR_FAIL_INDEX_V(p_y, blend_points_used, false);
|
||||
ERR_FAIL_INDEX_V(p_z, blend_points_used, false);
|
||||
|
||||
BlendTriangle t;
|
||||
t.points[0] = p_x;
|
||||
t.points[1] = p_y;
|
||||
t.points[2] = p_z;
|
||||
|
||||
SortArray<int> sort;
|
||||
sort.sort(t.points, 3);
|
||||
|
||||
for (int i = 0; i < triangles.size(); i++) {
|
||||
bool all_equal = true;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (triangles[i].points[j] != t.points[j]) {
|
||||
all_equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (all_equal) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::add_triangle(int p_x, int p_y, int p_z, int p_at_index) {
|
||||
ERR_FAIL_INDEX(p_x, blend_points_used);
|
||||
ERR_FAIL_INDEX(p_y, blend_points_used);
|
||||
ERR_FAIL_INDEX(p_z, blend_points_used);
|
||||
|
||||
_update_triangles();
|
||||
|
||||
BlendTriangle t;
|
||||
t.points[0] = p_x;
|
||||
t.points[1] = p_y;
|
||||
t.points[2] = p_z;
|
||||
|
||||
SortArray<int> sort;
|
||||
sort.sort(t.points, 3);
|
||||
|
||||
for (int i = 0; i < triangles.size(); i++) {
|
||||
bool all_equal = true;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (triangles[i].points[j] != t.points[j]) {
|
||||
all_equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ERR_FAIL_COND(all_equal);
|
||||
}
|
||||
|
||||
if (p_at_index == -1 || p_at_index == triangles.size()) {
|
||||
triangles.push_back(t);
|
||||
} else {
|
||||
triangles.insert(p_at_index, t);
|
||||
}
|
||||
}
|
||||
|
||||
int AnimationNodeBlendSpace2D::get_triangle_point(int p_triangle, int p_point) {
|
||||
_update_triangles();
|
||||
|
||||
ERR_FAIL_INDEX_V(p_point, 3, -1);
|
||||
ERR_FAIL_INDEX_V(p_triangle, triangles.size(), -1);
|
||||
return triangles[p_triangle].points[p_point];
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::remove_triangle(int p_triangle) {
|
||||
ERR_FAIL_INDEX(p_triangle, triangles.size());
|
||||
|
||||
triangles.remove_at(p_triangle);
|
||||
}
|
||||
|
||||
int AnimationNodeBlendSpace2D::get_triangle_count() const {
|
||||
return triangles.size();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::set_min_space(const Vector2 &p_min) {
|
||||
min_space = p_min;
|
||||
if (min_space.x >= max_space.x) {
|
||||
min_space.x = max_space.x - 1;
|
||||
}
|
||||
if (min_space.y >= max_space.y) {
|
||||
min_space.y = max_space.y - 1;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 AnimationNodeBlendSpace2D::get_min_space() const {
|
||||
return min_space;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::set_max_space(const Vector2 &p_max) {
|
||||
max_space = p_max;
|
||||
if (max_space.x <= min_space.x) {
|
||||
max_space.x = min_space.x + 1;
|
||||
}
|
||||
if (max_space.y <= min_space.y) {
|
||||
max_space.y = min_space.y + 1;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 AnimationNodeBlendSpace2D::get_max_space() const {
|
||||
return max_space;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::set_snap(const Vector2 &p_snap) {
|
||||
snap = p_snap;
|
||||
}
|
||||
|
||||
Vector2 AnimationNodeBlendSpace2D::get_snap() const {
|
||||
return snap;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::set_x_label(const String &p_label) {
|
||||
x_label = p_label;
|
||||
}
|
||||
|
||||
String AnimationNodeBlendSpace2D::get_x_label() const {
|
||||
return x_label;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::set_y_label(const String &p_label) {
|
||||
y_label = p_label;
|
||||
}
|
||||
|
||||
String AnimationNodeBlendSpace2D::get_y_label() const {
|
||||
return y_label;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node) {
|
||||
if (p_index == blend_points_used) {
|
||||
add_blend_point(p_node, Vector2());
|
||||
} else {
|
||||
set_blend_point_node(p_index, p_node);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_set_triangles(const Vector<int> &p_triangles) {
|
||||
if (auto_triangles) {
|
||||
return;
|
||||
}
|
||||
ERR_FAIL_COND(p_triangles.size() % 3 != 0);
|
||||
for (int i = 0; i < p_triangles.size(); i += 3) {
|
||||
add_triangle(p_triangles[i + 0], p_triangles[i + 1], p_triangles[i + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
Vector<int> AnimationNodeBlendSpace2D::_get_triangles() const {
|
||||
Vector<int> t;
|
||||
if (auto_triangles && trianges_dirty) {
|
||||
return t;
|
||||
}
|
||||
|
||||
t.resize(triangles.size() * 3);
|
||||
for (int i = 0; i < triangles.size(); i++) {
|
||||
t.write[i * 3 + 0] = triangles[i].points[0];
|
||||
t.write[i * 3 + 1] = triangles[i].points[1];
|
||||
t.write[i * 3 + 2] = triangles[i].points[2];
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_queue_auto_triangles() {
|
||||
if (!auto_triangles || trianges_dirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
trianges_dirty = true;
|
||||
callable_mp(this, &AnimationNodeBlendSpace2D::_update_triangles).call_deferred();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_update_triangles() {
|
||||
if (!auto_triangles || !trianges_dirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
trianges_dirty = false;
|
||||
triangles.clear();
|
||||
if (blend_points_used < 3) {
|
||||
emit_signal(SNAME("triangles_updated"));
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<Vector2> points;
|
||||
points.resize(blend_points_used);
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
points.write[i] = blend_points[i].position;
|
||||
}
|
||||
|
||||
Vector<Delaunay2D::Triangle> tr = Delaunay2D::triangulate(points);
|
||||
|
||||
for (int i = 0; i < tr.size(); i++) {
|
||||
add_triangle(tr[i].points[0], tr[i].points[1], tr[i].points[2]);
|
||||
}
|
||||
emit_signal(SNAME("triangles_updated"));
|
||||
}
|
||||
|
||||
Vector2 AnimationNodeBlendSpace2D::get_closest_point(const Vector2 &p_point) {
|
||||
_update_triangles();
|
||||
|
||||
if (triangles.size() == 0) {
|
||||
return Vector2();
|
||||
}
|
||||
|
||||
Vector2 best_point;
|
||||
bool first = true;
|
||||
|
||||
for (int i = 0; i < triangles.size(); i++) {
|
||||
Vector2 points[3];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
points[j] = get_blend_point_position(get_triangle_point(i, j));
|
||||
}
|
||||
|
||||
if (Geometry2D::is_point_in_triangle(p_point, points[0], points[1], points[2])) {
|
||||
return p_point;
|
||||
}
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
Vector2 s[2] = {
|
||||
points[j],
|
||||
points[(j + 1) % 3]
|
||||
};
|
||||
Vector2 closest_point = Geometry2D::get_closest_point_to_segment(p_point, s);
|
||||
if (first || closest_point.distance_to(p_point) < best_point.distance_to(p_point)) {
|
||||
best_point = closest_point;
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return best_point;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights) {
|
||||
if (p_pos.is_equal_approx(p_points[0])) {
|
||||
r_weights[0] = 1;
|
||||
r_weights[1] = 0;
|
||||
r_weights[2] = 0;
|
||||
return;
|
||||
}
|
||||
if (p_pos.is_equal_approx(p_points[1])) {
|
||||
r_weights[0] = 0;
|
||||
r_weights[1] = 1;
|
||||
r_weights[2] = 0;
|
||||
return;
|
||||
}
|
||||
if (p_pos.is_equal_approx(p_points[2])) {
|
||||
r_weights[0] = 0;
|
||||
r_weights[1] = 0;
|
||||
r_weights[2] = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 v0 = p_points[1] - p_points[0];
|
||||
Vector2 v1 = p_points[2] - p_points[0];
|
||||
Vector2 v2 = p_pos - p_points[0];
|
||||
|
||||
float d00 = v0.dot(v0);
|
||||
float d01 = v0.dot(v1);
|
||||
float d11 = v1.dot(v1);
|
||||
float d20 = v2.dot(v0);
|
||||
float d21 = v2.dot(v1);
|
||||
float denom = (d00 * d11 - d01 * d01);
|
||||
if (denom == 0) {
|
||||
r_weights[0] = 1;
|
||||
r_weights[1] = 0;
|
||||
r_weights[2] = 0;
|
||||
return;
|
||||
}
|
||||
float v = (d11 * d20 - d01 * d21) / denom;
|
||||
float w = (d00 * d21 - d01 * d20) / denom;
|
||||
float u = 1.0f - v - w;
|
||||
|
||||
r_weights[0] = u;
|
||||
r_weights[1] = v;
|
||||
r_weights[2] = w;
|
||||
}
|
||||
|
||||
AnimationNode::NodeTimeInfo AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
||||
_update_triangles();
|
||||
|
||||
if (!blend_points_used) {
|
||||
return NodeTimeInfo();
|
||||
}
|
||||
|
||||
Vector2 blend_pos = get_parameter(blend_position);
|
||||
int cur_closest = get_parameter(closest);
|
||||
NodeTimeInfo mind; //time of min distance point
|
||||
|
||||
AnimationMixer::PlaybackInfo pi = p_playback_info;
|
||||
|
||||
if (blend_mode == BLEND_MODE_INTERPOLATED) {
|
||||
if (triangles.is_empty()) {
|
||||
return NodeTimeInfo();
|
||||
}
|
||||
|
||||
Vector2 best_point;
|
||||
bool first = true;
|
||||
int blend_triangle = -1;
|
||||
float blend_weights[3] = { 0, 0, 0 };
|
||||
|
||||
for (int i = 0; i < triangles.size(); i++) {
|
||||
Vector2 points[3];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
points[j] = get_blend_point_position(get_triangle_point(i, j));
|
||||
}
|
||||
|
||||
if (Geometry2D::is_point_in_triangle(blend_pos, points[0], points[1], points[2])) {
|
||||
blend_triangle = i;
|
||||
_blend_triangle(blend_pos, points, blend_weights);
|
||||
break;
|
||||
}
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
Vector2 s[2] = {
|
||||
points[j],
|
||||
points[(j + 1) % 3]
|
||||
};
|
||||
Vector2 closest2 = Geometry2D::get_closest_point_to_segment(blend_pos, s);
|
||||
if (first || closest2.distance_to(blend_pos) < best_point.distance_to(blend_pos)) {
|
||||
best_point = closest2;
|
||||
blend_triangle = i;
|
||||
first = false;
|
||||
float d = s[0].distance_to(s[1]);
|
||||
if (d == 0.0) {
|
||||
blend_weights[j] = 1.0;
|
||||
blend_weights[(j + 1) % 3] = 0.0;
|
||||
blend_weights[(j + 2) % 3] = 0.0;
|
||||
} else {
|
||||
float c = s[0].distance_to(closest2) / d;
|
||||
|
||||
blend_weights[j] = 1.0 - c;
|
||||
blend_weights[(j + 1) % 3] = c;
|
||||
blend_weights[(j + 2) % 3] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V(blend_triangle == -1, NodeTimeInfo()); //should never reach here
|
||||
|
||||
int triangle_points[3];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
triangle_points[j] = get_triangle_point(blend_triangle, j);
|
||||
}
|
||||
|
||||
first = true;
|
||||
|
||||
double max_weight = 0.0;
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
bool found = false;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (i == triangle_points[j]) {
|
||||
//blend with the given weight
|
||||
pi.weight = blend_weights[j];
|
||||
NodeTimeInfo t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
|
||||
if (first || pi.weight > max_weight) {
|
||||
mind = t;
|
||||
max_weight = pi.weight;
|
||||
first = false;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sync && !found) {
|
||||
pi.weight = 0;
|
||||
blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int new_closest = -1;
|
||||
float new_closest_dist = 1e20;
|
||||
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
float d = blend_points[i].position.distance_squared_to(blend_pos);
|
||||
if (d < new_closest_dist) {
|
||||
new_closest = i;
|
||||
new_closest_dist = d;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_closest != cur_closest && new_closest != -1) {
|
||||
NodeTimeInfo from;
|
||||
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && cur_closest != -1) {
|
||||
//for ping-pong loop
|
||||
Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[cur_closest].node);
|
||||
Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node);
|
||||
if (!na_c.is_null() && !na_n.is_null()) {
|
||||
na_n->set_backward(na_c->is_backward());
|
||||
}
|
||||
//see how much animation remains
|
||||
pi.seeked = false;
|
||||
pi.weight = 0;
|
||||
from = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
pi.time = from.position;
|
||||
pi.seeked = true;
|
||||
pi.weight = 1.0;
|
||||
mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);
|
||||
cur_closest = new_closest;
|
||||
} else {
|
||||
pi.weight = 1.0;
|
||||
mind = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
|
||||
if (sync) {
|
||||
pi = p_playback_info;
|
||||
pi.weight = 0;
|
||||
for (int i = 0; i < blend_points_used; i++) {
|
||||
if (i != cur_closest) {
|
||||
blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_parameter(closest, cur_closest);
|
||||
return mind;
|
||||
}
|
||||
|
||||
String AnimationNodeBlendSpace2D::get_caption() const {
|
||||
return "BlendSpace2D";
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_validate_property(PropertyInfo &p_property) const {
|
||||
if (auto_triangles && p_property.name == "triangles") {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
if (p_property.name.begins_with("blend_point_")) {
|
||||
String left = p_property.name.get_slicec('/', 0);
|
||||
int idx = left.get_slicec('_', 2).to_int();
|
||||
if (idx >= blend_points_used) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::set_auto_triangles(bool p_enable) {
|
||||
if (auto_triangles == p_enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto_triangles = p_enable;
|
||||
_queue_auto_triangles();
|
||||
}
|
||||
|
||||
bool AnimationNodeBlendSpace2D::get_auto_triangles() const {
|
||||
return auto_triangles;
|
||||
}
|
||||
|
||||
Ref<AnimationNode> AnimationNodeBlendSpace2D::get_child_by_name(const StringName &p_name) const {
|
||||
return get_blend_point_node(p_name.operator String().to_int());
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::set_blend_mode(BlendMode p_blend_mode) {
|
||||
blend_mode = p_blend_mode;
|
||||
}
|
||||
|
||||
AnimationNodeBlendSpace2D::BlendMode AnimationNodeBlendSpace2D::get_blend_mode() const {
|
||||
return blend_mode;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::set_use_sync(bool p_sync) {
|
||||
sync = p_sync;
|
||||
}
|
||||
|
||||
bool AnimationNodeBlendSpace2D::is_using_sync() const {
|
||||
return sync;
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_tree_changed() {
|
||||
AnimationRootNode::_tree_changed();
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) {
|
||||
AnimationRootNode::_animation_node_renamed(p_oid, p_old_name, p_new_name);
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_animation_node_removed(const ObjectID &p_oid, const StringName &p_node) {
|
||||
AnimationRootNode::_animation_node_removed(p_oid, p_node);
|
||||
}
|
||||
|
||||
void AnimationNodeBlendSpace2D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace2D::add_blend_point, DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace2D::set_blend_point_position);
|
||||
ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace2D::get_blend_point_position);
|
||||
ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace2D::set_blend_point_node);
|
||||
ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace2D::get_blend_point_node);
|
||||
ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace2D::remove_blend_point);
|
||||
ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace2D::get_blend_point_count);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("add_triangle", "x", "y", "z", "at_index"), &AnimationNodeBlendSpace2D::add_triangle, DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("get_triangle_point", "triangle", "point"), &AnimationNodeBlendSpace2D::get_triangle_point);
|
||||
ClassDB::bind_method(D_METHOD("remove_triangle", "triangle"), &AnimationNodeBlendSpace2D::remove_triangle);
|
||||
ClassDB::bind_method(D_METHOD("get_triangle_count"), &AnimationNodeBlendSpace2D::get_triangle_count);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_min_space", "min_space"), &AnimationNodeBlendSpace2D::set_min_space);
|
||||
ClassDB::bind_method(D_METHOD("get_min_space"), &AnimationNodeBlendSpace2D::get_min_space);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_max_space", "max_space"), &AnimationNodeBlendSpace2D::set_max_space);
|
||||
ClassDB::bind_method(D_METHOD("get_max_space"), &AnimationNodeBlendSpace2D::get_max_space);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_snap", "snap"), &AnimationNodeBlendSpace2D::set_snap);
|
||||
ClassDB::bind_method(D_METHOD("get_snap"), &AnimationNodeBlendSpace2D::get_snap);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_x_label", "text"), &AnimationNodeBlendSpace2D::set_x_label);
|
||||
ClassDB::bind_method(D_METHOD("get_x_label"), &AnimationNodeBlendSpace2D::get_x_label);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_y_label", "text"), &AnimationNodeBlendSpace2D::set_y_label);
|
||||
ClassDB::bind_method(D_METHOD("get_y_label"), &AnimationNodeBlendSpace2D::get_y_label);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace2D::_add_blend_point);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_set_triangles", "triangles"), &AnimationNodeBlendSpace2D::_set_triangles);
|
||||
ClassDB::bind_method(D_METHOD("_get_triangles"), &AnimationNodeBlendSpace2D::_get_triangles);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_auto_triangles", "enable"), &AnimationNodeBlendSpace2D::set_auto_triangles);
|
||||
ClassDB::bind_method(D_METHOD("get_auto_triangles"), &AnimationNodeBlendSpace2D::get_auto_triangles);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_blend_mode", "mode"), &AnimationNodeBlendSpace2D::set_blend_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_blend_mode"), &AnimationNodeBlendSpace2D::get_blend_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlendSpace2D::set_use_sync);
|
||||
ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlendSpace2D::is_using_sync);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_auto_triangles", "get_auto_triangles");
|
||||
|
||||
for (int i = 0; i < MAX_BLEND_POINTS; i++) {
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_add_blend_point", "get_blend_point_node", i);
|
||||
ADD_PROPERTYI(PropertyInfo(Variant::VECTOR2, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i);
|
||||
}
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_triangles", "_get_triangles");
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_min_space", "get_min_space");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_max_space", "get_max_space");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_snap", "get_snap");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "x_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_x_label", "get_x_label");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "y_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_y_label", "get_y_label");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Interpolated,Discrete,Carry", PROPERTY_USAGE_NO_EDITOR), "set_blend_mode", "get_blend_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_sync", "is_using_sync");
|
||||
|
||||
ADD_SIGNAL(MethodInfo("triangles_updated"));
|
||||
BIND_ENUM_CONSTANT(BLEND_MODE_INTERPOLATED);
|
||||
BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE);
|
||||
BIND_ENUM_CONSTANT(BLEND_MODE_DISCRETE_CARRY);
|
||||
}
|
||||
|
||||
AnimationNodeBlendSpace2D::AnimationNodeBlendSpace2D() {
|
||||
for (int i = 0; i < MAX_BLEND_POINTS; i++) {
|
||||
blend_points[i].name = itos(i);
|
||||
}
|
||||
}
|
||||
|
||||
AnimationNodeBlendSpace2D::~AnimationNodeBlendSpace2D() {
|
||||
}
|
||||
153
engine/scene/animation/animation_blend_space_2d.h
Normal file
153
engine/scene/animation/animation_blend_space_2d.h
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/**************************************************************************/
|
||||
/* animation_blend_space_2d.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANIMATION_BLEND_SPACE_2D_H
|
||||
#define ANIMATION_BLEND_SPACE_2D_H
|
||||
|
||||
#include "scene/animation/animation_tree.h"
|
||||
|
||||
class AnimationNodeBlendSpace2D : public AnimationRootNode {
|
||||
GDCLASS(AnimationNodeBlendSpace2D, AnimationRootNode);
|
||||
|
||||
public:
|
||||
enum BlendMode {
|
||||
BLEND_MODE_INTERPOLATED,
|
||||
BLEND_MODE_DISCRETE,
|
||||
BLEND_MODE_DISCRETE_CARRY,
|
||||
};
|
||||
|
||||
protected:
|
||||
enum {
|
||||
MAX_BLEND_POINTS = 64
|
||||
};
|
||||
|
||||
struct BlendPoint {
|
||||
StringName name;
|
||||
Ref<AnimationRootNode> node;
|
||||
Vector2 position;
|
||||
};
|
||||
|
||||
BlendPoint blend_points[MAX_BLEND_POINTS];
|
||||
int blend_points_used = 0;
|
||||
|
||||
struct BlendTriangle {
|
||||
int points[3] = {};
|
||||
};
|
||||
|
||||
Vector<BlendTriangle> triangles;
|
||||
|
||||
StringName blend_position = "blend_position";
|
||||
StringName closest = "closest";
|
||||
Vector2 max_space = Vector2(1, 1);
|
||||
Vector2 min_space = Vector2(-1, -1);
|
||||
Vector2 snap = Vector2(0.1, 0.1);
|
||||
String x_label = "x";
|
||||
String y_label = "y";
|
||||
BlendMode blend_mode = BLEND_MODE_INTERPOLATED;
|
||||
|
||||
void _add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node);
|
||||
void _set_triangles(const Vector<int> &p_triangles);
|
||||
Vector<int> _get_triangles() const;
|
||||
|
||||
void _blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights);
|
||||
|
||||
bool auto_triangles = true;
|
||||
bool trianges_dirty = false;
|
||||
|
||||
void _update_triangles();
|
||||
void _queue_auto_triangles();
|
||||
|
||||
bool sync = false;
|
||||
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
static void _bind_methods();
|
||||
|
||||
virtual void _tree_changed() override;
|
||||
virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) override;
|
||||
virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override;
|
||||
|
||||
public:
|
||||
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
|
||||
virtual void get_child_nodes(List<ChildNode> *r_child_nodes) override;
|
||||
|
||||
void add_blend_point(const Ref<AnimationRootNode> &p_node, const Vector2 &p_position, int p_at_index = -1);
|
||||
void set_blend_point_position(int p_point, const Vector2 &p_position);
|
||||
void set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node);
|
||||
Vector2 get_blend_point_position(int p_point) const;
|
||||
Ref<AnimationRootNode> get_blend_point_node(int p_point) const;
|
||||
void remove_blend_point(int p_point);
|
||||
int get_blend_point_count() const;
|
||||
|
||||
bool has_triangle(int p_x, int p_y, int p_z) const;
|
||||
void add_triangle(int p_x, int p_y, int p_z, int p_at_index = -1);
|
||||
int get_triangle_point(int p_triangle, int p_point);
|
||||
void remove_triangle(int p_triangle);
|
||||
int get_triangle_count() const;
|
||||
|
||||
void set_min_space(const Vector2 &p_min);
|
||||
Vector2 get_min_space() const;
|
||||
|
||||
void set_max_space(const Vector2 &p_max);
|
||||
Vector2 get_max_space() const;
|
||||
|
||||
void set_snap(const Vector2 &p_snap);
|
||||
Vector2 get_snap() const;
|
||||
|
||||
void set_x_label(const String &p_label);
|
||||
String get_x_label() const;
|
||||
|
||||
void set_y_label(const String &p_label);
|
||||
String get_y_label() const;
|
||||
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
virtual String get_caption() const override;
|
||||
|
||||
Vector2 get_closest_point(const Vector2 &p_point);
|
||||
|
||||
void set_auto_triangles(bool p_enable);
|
||||
bool get_auto_triangles() const;
|
||||
|
||||
void set_blend_mode(BlendMode p_blend_mode);
|
||||
BlendMode get_blend_mode() const;
|
||||
|
||||
void set_use_sync(bool p_sync);
|
||||
bool is_using_sync() const;
|
||||
|
||||
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
|
||||
|
||||
AnimationNodeBlendSpace2D();
|
||||
~AnimationNodeBlendSpace2D();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AnimationNodeBlendSpace2D::BlendMode)
|
||||
|
||||
#endif // ANIMATION_BLEND_SPACE_2D_H
|
||||
1830
engine/scene/animation/animation_blend_tree.cpp
Normal file
1830
engine/scene/animation/animation_blend_tree.cpp
Normal file
File diff suppressed because it is too large
Load diff
496
engine/scene/animation/animation_blend_tree.h
Normal file
496
engine/scene/animation/animation_blend_tree.h
Normal file
|
|
@ -0,0 +1,496 @@
|
|||
/**************************************************************************/
|
||||
/* animation_blend_tree.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANIMATION_BLEND_TREE_H
|
||||
#define ANIMATION_BLEND_TREE_H
|
||||
|
||||
#include "scene/animation/animation_tree.h"
|
||||
|
||||
class AnimationNodeAnimation : public AnimationRootNode {
|
||||
GDCLASS(AnimationNodeAnimation, AnimationRootNode);
|
||||
|
||||
StringName animation;
|
||||
|
||||
bool use_custom_timeline = false;
|
||||
double timeline_length = 1.0;
|
||||
Animation::LoopMode loop_mode = Animation::LOOP_NONE;
|
||||
bool stretch_time_scale = true;
|
||||
double start_offset = 0.0;
|
||||
|
||||
uint64_t last_version = 0;
|
||||
bool skip = false;
|
||||
|
||||
public:
|
||||
enum PlayMode {
|
||||
PLAY_MODE_FORWARD,
|
||||
PLAY_MODE_BACKWARD
|
||||
};
|
||||
|
||||
void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
|
||||
virtual NodeTimeInfo get_node_time_info() const override; // Wrapper of get_parameter().
|
||||
|
||||
static Vector<String> (*get_editable_animation_list)();
|
||||
|
||||
virtual String get_caption() const override;
|
||||
virtual NodeTimeInfo process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
|
||||
void set_animation(const StringName &p_name);
|
||||
StringName get_animation() const;
|
||||
|
||||
void set_play_mode(PlayMode p_play_mode);
|
||||
PlayMode get_play_mode() const;
|
||||
|
||||
void set_backward(bool p_backward);
|
||||
bool is_backward() const;
|
||||
|
||||
void set_use_custom_timeline(bool p_use_custom_timeline);
|
||||
bool is_using_custom_timeline() const;
|
||||
|
||||
void set_timeline_length(double p_length);
|
||||
double get_timeline_length() const;
|
||||
|
||||
void set_stretch_time_scale(bool p_strech_time_scale);
|
||||
bool is_stretching_time_scale() const;
|
||||
|
||||
void set_start_offset(double p_offset);
|
||||
double get_start_offset() const;
|
||||
|
||||
void set_loop_mode(Animation::LoopMode p_loop_mode);
|
||||
Animation::LoopMode get_loop_mode() const;
|
||||
|
||||
AnimationNodeAnimation();
|
||||
|
||||
protected:
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
PlayMode play_mode = PLAY_MODE_FORWARD;
|
||||
bool backward = false; // Only used by pingpong animation.
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AnimationNodeAnimation::PlayMode)
|
||||
|
||||
class AnimationNodeSync : public AnimationNode {
|
||||
GDCLASS(AnimationNodeSync, AnimationNode);
|
||||
|
||||
protected:
|
||||
bool sync = false;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_use_sync(bool p_sync);
|
||||
bool is_using_sync() const;
|
||||
|
||||
AnimationNodeSync();
|
||||
};
|
||||
|
||||
class AnimationNodeOneShot : public AnimationNodeSync {
|
||||
GDCLASS(AnimationNodeOneShot, AnimationNodeSync);
|
||||
|
||||
public:
|
||||
enum OneShotRequest {
|
||||
ONE_SHOT_REQUEST_NONE,
|
||||
ONE_SHOT_REQUEST_FIRE,
|
||||
ONE_SHOT_REQUEST_ABORT,
|
||||
ONE_SHOT_REQUEST_FADE_OUT,
|
||||
};
|
||||
|
||||
enum MixMode {
|
||||
MIX_MODE_BLEND,
|
||||
MIX_MODE_ADD
|
||||
};
|
||||
|
||||
private:
|
||||
double fade_in = 0.0;
|
||||
Ref<Curve> fade_in_curve;
|
||||
double fade_out = 0.0;
|
||||
Ref<Curve> fade_out_curve;
|
||||
|
||||
bool auto_restart = false;
|
||||
double auto_restart_delay = 1.0;
|
||||
double auto_restart_random_delay = 0.0;
|
||||
MixMode mix = MIX_MODE_BLEND;
|
||||
bool break_loop_at_end = false;
|
||||
|
||||
StringName request = PNAME("request");
|
||||
StringName active = PNAME("active");
|
||||
StringName internal_active = PNAME("internal_active");
|
||||
StringName fade_in_remaining = "fade_in_remaining";
|
||||
StringName fade_out_remaining = "fade_out_remaining";
|
||||
StringName time_to_restart = "time_to_restart";
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
virtual bool is_parameter_read_only(const StringName &p_parameter) const override;
|
||||
|
||||
virtual String get_caption() const override;
|
||||
|
||||
void set_fade_in_time(double p_time);
|
||||
double get_fade_in_time() const;
|
||||
|
||||
void set_fade_in_curve(const Ref<Curve> &p_curve);
|
||||
Ref<Curve> get_fade_in_curve() const;
|
||||
|
||||
void set_fade_out_time(double p_time);
|
||||
double get_fade_out_time() const;
|
||||
|
||||
void set_fade_out_curve(const Ref<Curve> &p_curve);
|
||||
Ref<Curve> get_fade_out_curve() const;
|
||||
|
||||
void set_auto_restart_enabled(bool p_enabled);
|
||||
void set_auto_restart_delay(double p_time);
|
||||
void set_auto_restart_random_delay(double p_time);
|
||||
|
||||
bool is_auto_restart_enabled() const;
|
||||
double get_auto_restart_delay() const;
|
||||
double get_auto_restart_random_delay() const;
|
||||
|
||||
void set_mix_mode(MixMode p_mix);
|
||||
MixMode get_mix_mode() const;
|
||||
|
||||
void set_break_loop_at_end(bool p_enable);
|
||||
bool is_loop_broken_at_end() const;
|
||||
|
||||
virtual bool has_filter() const override;
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
|
||||
AnimationNodeOneShot();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AnimationNodeOneShot::OneShotRequest)
|
||||
VARIANT_ENUM_CAST(AnimationNodeOneShot::MixMode)
|
||||
|
||||
class AnimationNodeAdd2 : public AnimationNodeSync {
|
||||
GDCLASS(AnimationNodeAdd2, AnimationNodeSync);
|
||||
|
||||
StringName add_amount = PNAME("add_amount");
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
|
||||
virtual String get_caption() const override;
|
||||
|
||||
virtual bool has_filter() const override;
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
|
||||
AnimationNodeAdd2();
|
||||
};
|
||||
|
||||
class AnimationNodeAdd3 : public AnimationNodeSync {
|
||||
GDCLASS(AnimationNodeAdd3, AnimationNodeSync);
|
||||
|
||||
StringName add_amount = PNAME("add_amount");
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
|
||||
virtual String get_caption() const override;
|
||||
|
||||
virtual bool has_filter() const override;
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
|
||||
AnimationNodeAdd3();
|
||||
};
|
||||
|
||||
class AnimationNodeBlend2 : public AnimationNodeSync {
|
||||
GDCLASS(AnimationNodeBlend2, AnimationNodeSync);
|
||||
|
||||
StringName blend_amount = PNAME("blend_amount");
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
|
||||
virtual String get_caption() const override;
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
|
||||
virtual bool has_filter() const override;
|
||||
AnimationNodeBlend2();
|
||||
};
|
||||
|
||||
class AnimationNodeBlend3 : public AnimationNodeSync {
|
||||
GDCLASS(AnimationNodeBlend3, AnimationNodeSync);
|
||||
|
||||
StringName blend_amount = PNAME("blend_amount");
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
|
||||
virtual String get_caption() const override;
|
||||
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
AnimationNodeBlend3();
|
||||
};
|
||||
|
||||
class AnimationNodeSub2 : public AnimationNodeSync {
|
||||
GDCLASS(AnimationNodeSub2, AnimationNodeSync);
|
||||
|
||||
StringName sub_amount = PNAME("sub_amount");
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
|
||||
virtual String get_caption() const override;
|
||||
|
||||
virtual bool has_filter() const override;
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
|
||||
AnimationNodeSub2();
|
||||
};
|
||||
|
||||
class AnimationNodeTimeScale : public AnimationNode {
|
||||
GDCLASS(AnimationNodeTimeScale, AnimationNode);
|
||||
|
||||
StringName scale = PNAME("scale");
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
|
||||
virtual String get_caption() const override;
|
||||
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
|
||||
AnimationNodeTimeScale();
|
||||
};
|
||||
|
||||
class AnimationNodeTimeSeek : public AnimationNode {
|
||||
GDCLASS(AnimationNodeTimeSeek, AnimationNode);
|
||||
|
||||
StringName seek_pos_request = PNAME("seek_request");
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
|
||||
virtual String get_caption() const override;
|
||||
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
|
||||
AnimationNodeTimeSeek();
|
||||
};
|
||||
|
||||
class AnimationNodeTransition : public AnimationNodeSync {
|
||||
GDCLASS(AnimationNodeTransition, AnimationNodeSync);
|
||||
|
||||
struct InputData {
|
||||
bool auto_advance = false;
|
||||
bool break_loop_at_end = false;
|
||||
bool reset = true;
|
||||
};
|
||||
Vector<InputData> input_data;
|
||||
|
||||
StringName prev_xfading = "prev_xfading";
|
||||
StringName prev_index = "prev_index";
|
||||
StringName current_index = PNAME("current_index");
|
||||
StringName current_state = PNAME("current_state");
|
||||
StringName transition_request = PNAME("transition_request");
|
||||
|
||||
StringName prev_frame_current = "pf_current";
|
||||
StringName prev_frame_current_idx = "pf_current_idx";
|
||||
|
||||
double xfade_time = 0.0;
|
||||
Ref<Curve> xfade_curve;
|
||||
bool allow_transition_to_self = false;
|
||||
|
||||
bool pending_update = false;
|
||||
|
||||
protected:
|
||||
bool _get(const StringName &p_path, Variant &r_ret) const;
|
||||
bool _set(const StringName &p_path, const Variant &p_value);
|
||||
static void _bind_methods();
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
virtual bool is_parameter_read_only(const StringName &p_parameter) const override;
|
||||
|
||||
virtual String get_caption() const override;
|
||||
|
||||
void set_input_count(int p_inputs);
|
||||
|
||||
virtual bool add_input(const String &p_name) override;
|
||||
virtual void remove_input(int p_index) override;
|
||||
virtual bool set_input_name(int p_input, const String &p_name) override;
|
||||
|
||||
void set_input_as_auto_advance(int p_input, bool p_enable);
|
||||
bool is_input_set_as_auto_advance(int p_input) const;
|
||||
|
||||
void set_input_break_loop_at_end(int p_input, bool p_enable);
|
||||
bool is_input_loop_broken_at_end(int p_input) const;
|
||||
|
||||
void set_input_reset(int p_input, bool p_enable);
|
||||
bool is_input_reset(int p_input) const;
|
||||
|
||||
void set_xfade_time(double p_fade);
|
||||
double get_xfade_time() const;
|
||||
|
||||
void set_xfade_curve(const Ref<Curve> &p_curve);
|
||||
Ref<Curve> get_xfade_curve() const;
|
||||
|
||||
void set_allow_transition_to_self(bool p_enable);
|
||||
bool is_allow_transition_to_self() const;
|
||||
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
|
||||
AnimationNodeTransition();
|
||||
};
|
||||
|
||||
class AnimationNodeOutput : public AnimationNode {
|
||||
GDCLASS(AnimationNodeOutput, AnimationNode);
|
||||
|
||||
public:
|
||||
virtual String get_caption() const override;
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
AnimationNodeOutput();
|
||||
};
|
||||
|
||||
/////
|
||||
|
||||
class AnimationNodeBlendTree : public AnimationRootNode {
|
||||
GDCLASS(AnimationNodeBlendTree, AnimationRootNode);
|
||||
|
||||
struct Node {
|
||||
Ref<AnimationNode> node;
|
||||
Vector2 position;
|
||||
Vector<StringName> connections;
|
||||
};
|
||||
|
||||
RBMap<StringName, Node, StringName::AlphCompare> nodes;
|
||||
|
||||
Vector2 graph_offset;
|
||||
|
||||
void _node_changed(const StringName &p_node);
|
||||
|
||||
void _initialize_node_tree();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
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;
|
||||
|
||||
virtual void _tree_changed() override;
|
||||
virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) override;
|
||||
virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override;
|
||||
|
||||
virtual void reset_state() override;
|
||||
|
||||
public:
|
||||
enum ConnectionError {
|
||||
CONNECTION_OK,
|
||||
CONNECTION_ERROR_NO_INPUT,
|
||||
CONNECTION_ERROR_NO_INPUT_INDEX,
|
||||
CONNECTION_ERROR_NO_OUTPUT,
|
||||
CONNECTION_ERROR_SAME_NODE,
|
||||
CONNECTION_ERROR_CONNECTION_EXISTS,
|
||||
//no need to check for cycles due to tree topology
|
||||
};
|
||||
|
||||
void add_node(const StringName &p_name, Ref<AnimationNode> p_node, const Vector2 &p_position = Vector2());
|
||||
Ref<AnimationNode> get_node(const StringName &p_name) const;
|
||||
void remove_node(const StringName &p_name);
|
||||
void rename_node(const StringName &p_name, const StringName &p_new_name);
|
||||
bool has_node(const StringName &p_name) const;
|
||||
StringName get_node_name(const Ref<AnimationNode> &p_node) const;
|
||||
Vector<StringName> get_node_connection_array(const StringName &p_name) const;
|
||||
|
||||
void set_node_position(const StringName &p_node, const Vector2 &p_position);
|
||||
Vector2 get_node_position(const StringName &p_node) const;
|
||||
|
||||
virtual void get_child_nodes(List<ChildNode> *r_child_nodes) override;
|
||||
|
||||
void connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node);
|
||||
void disconnect_node(const StringName &p_node, int p_input_index);
|
||||
|
||||
struct NodeConnection {
|
||||
StringName input_node;
|
||||
int input_index = 0;
|
||||
StringName output_node;
|
||||
};
|
||||
|
||||
ConnectionError can_connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) const;
|
||||
void get_node_connections(List<NodeConnection> *r_connections) const;
|
||||
|
||||
virtual String get_caption() const override;
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
|
||||
void get_node_list(List<StringName> *r_list);
|
||||
|
||||
void set_graph_offset(const Vector2 &p_graph_offset);
|
||||
Vector2 get_graph_offset() const;
|
||||
|
||||
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
|
||||
#endif
|
||||
|
||||
AnimationNodeBlendTree();
|
||||
~AnimationNodeBlendTree();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AnimationNodeBlendTree::ConnectionError)
|
||||
|
||||
#endif // ANIMATION_BLEND_TREE_H
|
||||
44
engine/scene/animation/animation_mixer.compat.inc
Normal file
44
engine/scene/animation/animation_mixer.compat.inc
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/**************************************************************************/
|
||||
/* animation_mixer.compat.inc */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
||||
Variant AnimationMixer::_post_process_key_value_bind_compat_86687(const Ref<Animation> &p_anim, int p_track, Variant p_value, Object *p_object, int p_object_idx) {
|
||||
if (!p_object) {
|
||||
return Variant();
|
||||
}
|
||||
return _post_process_key_value(p_anim, p_track, p_value, p_object->get_instance_id(), p_object_idx);
|
||||
}
|
||||
|
||||
void AnimationMixer::_bind_compatibility_methods() {
|
||||
ClassDB::bind_compatibility_method(D_METHOD("_post_process_key_value", "animation", "track", "value", "object", "object_idx"), &AnimationMixer::_post_process_key_value_bind_compat_86687);
|
||||
}
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
||||
2417
engine/scene/animation/animation_mixer.cpp
Normal file
2417
engine/scene/animation/animation_mixer.cpp
Normal file
File diff suppressed because it is too large
Load diff
501
engine/scene/animation/animation_mixer.h
Normal file
501
engine/scene/animation/animation_mixer.h
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
/**************************************************************************/
|
||||
/* animation_mixer.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANIMATION_MIXER_H
|
||||
#define ANIMATION_MIXER_H
|
||||
|
||||
#include "scene/animation/tween.h"
|
||||
#include "scene/main/node.h"
|
||||
#include "scene/resources/animation.h"
|
||||
#include "scene/resources/animation_library.h"
|
||||
#include "scene/resources/audio_stream_polyphonic.h"
|
||||
|
||||
class AnimatedValuesBackup;
|
||||
|
||||
class AnimationMixer : public Node {
|
||||
GDCLASS(AnimationMixer, Node);
|
||||
friend AnimatedValuesBackup;
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool editing = false;
|
||||
bool dummy = false;
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
bool reset_on_save = true;
|
||||
|
||||
public:
|
||||
enum AnimationCallbackModeProcess {
|
||||
ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS,
|
||||
ANIMATION_CALLBACK_MODE_PROCESS_IDLE,
|
||||
ANIMATION_CALLBACK_MODE_PROCESS_MANUAL,
|
||||
};
|
||||
|
||||
enum AnimationCallbackModeMethod {
|
||||
ANIMATION_CALLBACK_MODE_METHOD_DEFERRED,
|
||||
ANIMATION_CALLBACK_MODE_METHOD_IMMEDIATE,
|
||||
};
|
||||
|
||||
enum AnimationCallbackModeDiscrete {
|
||||
ANIMATION_CALLBACK_MODE_DISCRETE_DOMINANT,
|
||||
ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE,
|
||||
ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS,
|
||||
};
|
||||
|
||||
/* ---- Data ---- */
|
||||
struct AnimationLibraryData {
|
||||
StringName name;
|
||||
Ref<AnimationLibrary> library;
|
||||
bool operator<(const AnimationLibraryData &p_data) const { return name.operator String() < p_data.name.operator String(); }
|
||||
};
|
||||
|
||||
struct AnimationData {
|
||||
String name;
|
||||
Ref<Animation> animation;
|
||||
StringName animation_library;
|
||||
uint64_t last_update = 0;
|
||||
};
|
||||
|
||||
struct PlaybackInfo {
|
||||
double time = 0.0;
|
||||
double delta = 0.0;
|
||||
bool seeked = false;
|
||||
bool is_external_seeking = false;
|
||||
Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
|
||||
real_t weight = 0.0;
|
||||
Vector<real_t> track_weights;
|
||||
};
|
||||
|
||||
struct AnimationInstance {
|
||||
AnimationData animation_data;
|
||||
PlaybackInfo playback_info;
|
||||
};
|
||||
|
||||
protected:
|
||||
/* ---- Data lists ---- */
|
||||
LocalVector<AnimationLibraryData> animation_libraries;
|
||||
HashMap<StringName, AnimationData> animation_set; // HashMap<Library name + Animation name, AnimationData>
|
||||
|
||||
TypedArray<StringName> _get_animation_library_list() const;
|
||||
Vector<String> _get_animation_list() const {
|
||||
List<StringName> animations;
|
||||
get_animation_list(&animations);
|
||||
Vector<String> ret;
|
||||
while (animations.size()) {
|
||||
ret.push_back(animations.front()->get());
|
||||
animations.pop_front();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// For caches.
|
||||
uint64_t animation_set_update_pass = 1;
|
||||
void _animation_set_cache_update();
|
||||
|
||||
// Signals.
|
||||
virtual void _animation_added(const StringName &p_name, const StringName &p_library);
|
||||
virtual void _animation_removed(const StringName &p_name, const StringName &p_library);
|
||||
virtual void _animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library);
|
||||
virtual void _animation_changed(const StringName &p_name);
|
||||
|
||||
/* ---- General settings for animation ---- */
|
||||
AnimationCallbackModeProcess callback_mode_process = ANIMATION_CALLBACK_MODE_PROCESS_IDLE;
|
||||
AnimationCallbackModeMethod callback_mode_method = ANIMATION_CALLBACK_MODE_METHOD_DEFERRED;
|
||||
AnimationCallbackModeDiscrete callback_mode_discrete = ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE;
|
||||
int audio_max_polyphony = 32;
|
||||
NodePath root_node;
|
||||
|
||||
bool processing = false;
|
||||
bool active = true;
|
||||
|
||||
void _set_process(bool p_process, bool p_force = false);
|
||||
|
||||
/* ---- Caches for blending ---- */
|
||||
bool cache_valid = false;
|
||||
uint64_t setup_pass = 1;
|
||||
uint64_t process_pass = 1;
|
||||
|
||||
struct TrackCache {
|
||||
bool root_motion = false;
|
||||
uint64_t setup_pass = 0;
|
||||
Animation::TrackType type = Animation::TrackType::TYPE_ANIMATION;
|
||||
NodePath path;
|
||||
ObjectID object_id;
|
||||
real_t total_weight = 0.0;
|
||||
|
||||
TrackCache() = default;
|
||||
TrackCache(const TrackCache &p_other) :
|
||||
root_motion(p_other.root_motion),
|
||||
setup_pass(p_other.setup_pass),
|
||||
type(p_other.type),
|
||||
object_id(p_other.object_id),
|
||||
total_weight(p_other.total_weight) {}
|
||||
|
||||
virtual ~TrackCache() {}
|
||||
};
|
||||
|
||||
struct TrackCacheTransform : public TrackCache {
|
||||
#ifndef _3D_DISABLED
|
||||
ObjectID skeleton_id;
|
||||
#endif // _3D_DISABLED
|
||||
int bone_idx = -1;
|
||||
bool loc_used = false;
|
||||
bool rot_used = false;
|
||||
bool scale_used = false;
|
||||
Vector3 init_loc = Vector3(0, 0, 0);
|
||||
Quaternion init_rot = Quaternion(0, 0, 0, 1);
|
||||
Vector3 init_scale = Vector3(1, 1, 1);
|
||||
Vector3 loc;
|
||||
Quaternion rot;
|
||||
Vector3 scale;
|
||||
|
||||
TrackCacheTransform(const TrackCacheTransform &p_other) :
|
||||
TrackCache(p_other),
|
||||
#ifndef _3D_DISABLED
|
||||
skeleton_id(p_other.skeleton_id),
|
||||
#endif
|
||||
bone_idx(p_other.bone_idx),
|
||||
loc_used(p_other.loc_used),
|
||||
rot_used(p_other.rot_used),
|
||||
scale_used(p_other.scale_used),
|
||||
init_loc(p_other.init_loc),
|
||||
init_rot(p_other.init_rot),
|
||||
init_scale(p_other.init_scale),
|
||||
loc(p_other.loc),
|
||||
rot(p_other.rot),
|
||||
scale(p_other.scale) {
|
||||
}
|
||||
|
||||
TrackCacheTransform() {
|
||||
type = Animation::TYPE_POSITION_3D;
|
||||
}
|
||||
~TrackCacheTransform() {}
|
||||
};
|
||||
|
||||
struct RootMotionCache {
|
||||
Vector3 loc = Vector3(0, 0, 0);
|
||||
Quaternion rot = Quaternion(0, 0, 0, 1);
|
||||
Vector3 scale = Vector3(1, 1, 1);
|
||||
};
|
||||
|
||||
struct TrackCacheBlendShape : public TrackCache {
|
||||
float init_value = 0;
|
||||
float value = 0;
|
||||
int shape_index = -1;
|
||||
|
||||
TrackCacheBlendShape(const TrackCacheBlendShape &p_other) :
|
||||
TrackCache(p_other),
|
||||
init_value(p_other.init_value),
|
||||
value(p_other.value),
|
||||
shape_index(p_other.shape_index) {}
|
||||
|
||||
TrackCacheBlendShape() { type = Animation::TYPE_BLEND_SHAPE; }
|
||||
~TrackCacheBlendShape() {}
|
||||
};
|
||||
|
||||
struct TrackCacheValue : public TrackCache {
|
||||
Variant init_value;
|
||||
Variant value;
|
||||
Vector<StringName> subpath;
|
||||
|
||||
// TODO: There are many boolean, can be packed into one integer.
|
||||
bool is_init = false;
|
||||
bool use_continuous = false;
|
||||
bool use_discrete = false;
|
||||
bool is_using_angle = false;
|
||||
bool is_variant_interpolatable = true;
|
||||
|
||||
Variant element_size;
|
||||
|
||||
TrackCacheValue(const TrackCacheValue &p_other) :
|
||||
TrackCache(p_other),
|
||||
init_value(p_other.init_value),
|
||||
value(p_other.value),
|
||||
subpath(p_other.subpath),
|
||||
is_init(p_other.is_init),
|
||||
use_continuous(p_other.use_continuous),
|
||||
use_discrete(p_other.use_discrete),
|
||||
is_using_angle(p_other.is_using_angle),
|
||||
is_variant_interpolatable(p_other.is_variant_interpolatable),
|
||||
element_size(p_other.element_size) {}
|
||||
|
||||
TrackCacheValue() { type = Animation::TYPE_VALUE; }
|
||||
~TrackCacheValue() {
|
||||
// Clear ref to avoid leaking.
|
||||
init_value = Variant();
|
||||
value = Variant();
|
||||
}
|
||||
};
|
||||
|
||||
struct TrackCacheMethod : public TrackCache {
|
||||
TrackCacheMethod() { type = Animation::TYPE_METHOD; }
|
||||
~TrackCacheMethod() {}
|
||||
};
|
||||
|
||||
// Audio stream information for each audio stream placed on the track.
|
||||
struct PlayingAudioStreamInfo {
|
||||
AudioStreamPlaybackPolyphonic::ID index = -1; // ID retrieved from AudioStreamPlaybackPolyphonic.
|
||||
double start = 0.0;
|
||||
double len = 0.0;
|
||||
};
|
||||
|
||||
// Audio track information for mixng and ending.
|
||||
struct PlayingAudioTrackInfo {
|
||||
HashMap<int, PlayingAudioStreamInfo> stream_info;
|
||||
double length = 0.0;
|
||||
double time = 0.0;
|
||||
real_t volume = 0.0;
|
||||
bool loop = false;
|
||||
bool backward = false;
|
||||
bool use_blend = false;
|
||||
};
|
||||
|
||||
struct TrackCacheAudio : public TrackCache {
|
||||
Ref<AudioStreamPolyphonic> audio_stream;
|
||||
Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
|
||||
HashMap<ObjectID, PlayingAudioTrackInfo> playing_streams; // Key is Animation resource ObjectID.
|
||||
AudioServer::PlaybackType playback_type;
|
||||
StringName bus;
|
||||
|
||||
TrackCacheAudio(const TrackCacheAudio &p_other) :
|
||||
TrackCache(p_other),
|
||||
audio_stream(p_other.audio_stream),
|
||||
audio_stream_playback(p_other.audio_stream_playback),
|
||||
playing_streams(p_other.playing_streams),
|
||||
playback_type(p_other.playback_type) {}
|
||||
|
||||
TrackCacheAudio() {
|
||||
type = Animation::TYPE_AUDIO;
|
||||
}
|
||||
~TrackCacheAudio() {}
|
||||
};
|
||||
|
||||
struct TrackCacheAnimation : public TrackCache {
|
||||
bool playing = false;
|
||||
|
||||
TrackCacheAnimation() {
|
||||
type = Animation::TYPE_ANIMATION;
|
||||
}
|
||||
~TrackCacheAnimation() {}
|
||||
};
|
||||
|
||||
RootMotionCache root_motion_cache;
|
||||
HashMap<Animation::TypeHash, TrackCache *> track_cache;
|
||||
HashSet<TrackCache *> playing_caches;
|
||||
Vector<Node *> playing_audio_stream_players;
|
||||
|
||||
// Helpers.
|
||||
void _clear_caches();
|
||||
void _clear_audio_streams();
|
||||
void _clear_playing_caches();
|
||||
void _init_root_motion_cache();
|
||||
bool _update_caches();
|
||||
|
||||
/* ---- Audio ---- */
|
||||
AudioServer::PlaybackType playback_type;
|
||||
|
||||
/* ---- Blending processor ---- */
|
||||
LocalVector<AnimationInstance> animation_instances;
|
||||
HashMap<NodePath, int> track_map;
|
||||
int track_count = 0;
|
||||
bool deterministic = false;
|
||||
|
||||
/* ---- Root motion accumulator for Skeleton3D ---- */
|
||||
NodePath root_motion_track;
|
||||
Vector3 root_motion_position = Vector3(0, 0, 0);
|
||||
Quaternion root_motion_rotation = Quaternion(0, 0, 0, 1);
|
||||
Vector3 root_motion_scale = Vector3(0, 0, 0);
|
||||
Vector3 root_motion_position_accumulator = Vector3(0, 0, 0);
|
||||
Quaternion root_motion_rotation_accumulator = Quaternion(0, 0, 0, 1);
|
||||
Vector3 root_motion_scale_accumulator = Vector3(1, 1, 1);
|
||||
|
||||
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;
|
||||
void _notification(int p_what);
|
||||
virtual void _validate_property(PropertyInfo &p_property) const;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
|
||||
#endif
|
||||
|
||||
static void _bind_methods();
|
||||
void _node_removed(Node *p_node);
|
||||
|
||||
// Helper for extended class.
|
||||
virtual void _set_active(bool p_active);
|
||||
virtual void _remove_animation(const StringName &p_name);
|
||||
virtual void _rename_animation(const StringName &p_from_name, const StringName &p_to_name);
|
||||
|
||||
/* ---- Blending processor ---- */
|
||||
virtual void _process_animation(double p_delta, bool p_update_only = false);
|
||||
|
||||
// For post process with retrieved key value during blending.
|
||||
virtual Variant _post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx = -1);
|
||||
Variant post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx = -1);
|
||||
GDVIRTUAL5RC(Variant, _post_process_key_value, Ref<Animation>, int, Variant, ObjectID, int);
|
||||
|
||||
void _blend_init();
|
||||
virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map);
|
||||
virtual void _blend_capture(double p_delta);
|
||||
void _blend_calc_total_weight(); // For undeterministic blending.
|
||||
void _blend_process(double p_delta, bool p_update_only = false);
|
||||
void _blend_apply();
|
||||
virtual void _blend_post_process();
|
||||
void _call_object(ObjectID p_object_id, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred);
|
||||
|
||||
/* ---- Capture feature ---- */
|
||||
struct CaptureCache {
|
||||
Ref<Animation> animation;
|
||||
double remain = 0.0;
|
||||
double step = 0.0;
|
||||
Tween::TransitionType trans_type = Tween::TRANS_LINEAR;
|
||||
Tween::EaseType ease_type = Tween::EASE_IN;
|
||||
|
||||
void clear() {
|
||||
animation.unref();
|
||||
remain = 0.0;
|
||||
step = 0.0;
|
||||
}
|
||||
|
||||
CaptureCache() {}
|
||||
~CaptureCache() {
|
||||
clear();
|
||||
}
|
||||
} capture_cache;
|
||||
void blend_capture(double p_delta); // To blend capture track with all other animations.
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
virtual Variant _post_process_key_value_bind_compat_86687(const Ref<Animation> &p_anim, int p_track, Variant p_value, Object *p_object, int p_object_idx = -1);
|
||||
static void _bind_compatibility_methods();
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
public:
|
||||
/* ---- Data lists ---- */
|
||||
Dictionary *get_animation_libraries();
|
||||
|
||||
void get_animation_library_list(List<StringName> *p_animations) const;
|
||||
Ref<AnimationLibrary> get_animation_library(const StringName &p_name) const;
|
||||
bool has_animation_library(const StringName &p_name) 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);
|
||||
|
||||
void get_animation_list(List<StringName> *p_animations) const;
|
||||
Ref<Animation> get_animation(const StringName &p_name) const;
|
||||
bool has_animation(const StringName &p_name) const;
|
||||
StringName find_animation(const Ref<Animation> &p_animation) const;
|
||||
|
||||
/* ---- General settings for animation ---- */
|
||||
void set_active(bool p_active);
|
||||
bool is_active() const;
|
||||
|
||||
void set_deterministic(bool p_deterministic);
|
||||
bool is_deterministic() const;
|
||||
|
||||
void set_root_node(const NodePath &p_path);
|
||||
NodePath get_root_node() const;
|
||||
|
||||
void set_callback_mode_process(AnimationCallbackModeProcess p_mode);
|
||||
AnimationCallbackModeProcess get_callback_mode_process() const;
|
||||
|
||||
void set_callback_mode_method(AnimationCallbackModeMethod p_mode);
|
||||
AnimationCallbackModeMethod get_callback_mode_method() const;
|
||||
|
||||
void set_callback_mode_discrete(AnimationCallbackModeDiscrete p_mode);
|
||||
AnimationCallbackModeDiscrete get_callback_mode_discrete() const;
|
||||
|
||||
/* ---- Audio ---- */
|
||||
void set_audio_max_polyphony(int p_audio_max_polyphony);
|
||||
int get_audio_max_polyphony() const;
|
||||
|
||||
/* ---- Root motion accumulator for Skeleton3D ---- */
|
||||
void set_root_motion_track(const NodePath &p_track);
|
||||
NodePath get_root_motion_track() const;
|
||||
|
||||
Vector3 get_root_motion_position() const;
|
||||
Quaternion get_root_motion_rotation() const;
|
||||
Vector3 get_root_motion_scale() const;
|
||||
|
||||
Vector3 get_root_motion_position_accumulator() const;
|
||||
Quaternion get_root_motion_rotation_accumulator() const;
|
||||
Vector3 get_root_motion_scale_accumulator() const;
|
||||
|
||||
/* ---- Blending processor ---- */
|
||||
void make_animation_instance(const StringName &p_name, const PlaybackInfo p_playback_info);
|
||||
void clear_animation_instances();
|
||||
virtual void advance(double p_time);
|
||||
virtual void clear_caches(); // Must be called by hand if an animation was modified after added.
|
||||
|
||||
/* ---- Capture feature ---- */
|
||||
void capture(const StringName &p_name, double p_duration, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN);
|
||||
|
||||
/* ---- Reset on save ---- */
|
||||
void set_reset_on_save_enabled(bool p_enabled);
|
||||
bool is_reset_on_save_enabled() const;
|
||||
bool can_apply_reset() const;
|
||||
void _build_backup_track_cache();
|
||||
Ref<AnimatedValuesBackup> make_backup();
|
||||
void restore(const Ref<AnimatedValuesBackup> &p_backup);
|
||||
void reset();
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
Ref<AnimatedValuesBackup> apply_reset(bool p_user_initiated = false);
|
||||
|
||||
void set_editing(bool p_editing);
|
||||
bool is_editing() const;
|
||||
|
||||
void set_dummy(bool p_dummy);
|
||||
bool is_dummy() const;
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
AnimationMixer();
|
||||
~AnimationMixer();
|
||||
};
|
||||
|
||||
class AnimatedValuesBackup : public RefCounted {
|
||||
GDCLASS(AnimatedValuesBackup, RefCounted);
|
||||
|
||||
HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> data;
|
||||
|
||||
public:
|
||||
void set_data(const HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> p_data);
|
||||
HashMap<Animation::TypeHash, AnimationMixer::TrackCache *> get_data() const;
|
||||
void clear_data();
|
||||
|
||||
AnimationMixer::TrackCache *get_cache_copy(AnimationMixer::TrackCache *p_cache) const;
|
||||
|
||||
~AnimatedValuesBackup() { clear_data(); }
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AnimationMixer::AnimationCallbackModeProcess);
|
||||
VARIANT_ENUM_CAST(AnimationMixer::AnimationCallbackModeMethod);
|
||||
VARIANT_ENUM_CAST(AnimationMixer::AnimationCallbackModeDiscrete);
|
||||
|
||||
#endif // ANIMATION_MIXER_H
|
||||
1861
engine/scene/animation/animation_node_state_machine.cpp
Normal file
1861
engine/scene/animation/animation_node_state_machine.cpp
Normal file
File diff suppressed because it is too large
Load diff
352
engine/scene/animation/animation_node_state_machine.h
Normal file
352
engine/scene/animation/animation_node_state_machine.h
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
/**************************************************************************/
|
||||
/* animation_node_state_machine.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANIMATION_NODE_STATE_MACHINE_H
|
||||
#define ANIMATION_NODE_STATE_MACHINE_H
|
||||
|
||||
#include "core/math/expression.h"
|
||||
#include "scene/animation/animation_tree.h"
|
||||
|
||||
class AnimationNodeStateMachineTransition : public Resource {
|
||||
GDCLASS(AnimationNodeStateMachineTransition, Resource);
|
||||
|
||||
public:
|
||||
enum SwitchMode {
|
||||
SWITCH_MODE_IMMEDIATE,
|
||||
SWITCH_MODE_SYNC,
|
||||
SWITCH_MODE_AT_END,
|
||||
};
|
||||
|
||||
enum AdvanceMode {
|
||||
ADVANCE_MODE_DISABLED,
|
||||
ADVANCE_MODE_ENABLED,
|
||||
ADVANCE_MODE_AUTO,
|
||||
};
|
||||
|
||||
private:
|
||||
SwitchMode switch_mode = SWITCH_MODE_IMMEDIATE;
|
||||
AdvanceMode advance_mode = ADVANCE_MODE_ENABLED;
|
||||
StringName advance_condition;
|
||||
StringName advance_condition_name;
|
||||
float xfade_time = 0.0;
|
||||
Ref<Curve> xfade_curve;
|
||||
bool break_loop_at_end = false;
|
||||
bool reset = true;
|
||||
int priority = 1;
|
||||
String advance_expression;
|
||||
|
||||
friend class AnimationNodeStateMachinePlayback;
|
||||
Ref<Expression> expression;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_switch_mode(SwitchMode p_mode);
|
||||
SwitchMode get_switch_mode() const;
|
||||
|
||||
void set_advance_mode(AdvanceMode p_mode);
|
||||
AdvanceMode get_advance_mode() const;
|
||||
|
||||
void set_advance_condition(const StringName &p_condition);
|
||||
StringName get_advance_condition() const;
|
||||
|
||||
StringName get_advance_condition_name() const;
|
||||
|
||||
void set_advance_expression(const String &p_expression);
|
||||
String get_advance_expression() const;
|
||||
|
||||
void set_xfade_time(float p_xfade);
|
||||
float get_xfade_time() const;
|
||||
|
||||
void set_break_loop_at_end(bool p_enable);
|
||||
bool is_loop_broken_at_end() const;
|
||||
|
||||
void set_reset(bool p_reset);
|
||||
bool is_reset() const;
|
||||
|
||||
void set_xfade_curve(const Ref<Curve> &p_curve);
|
||||
Ref<Curve> get_xfade_curve() const;
|
||||
|
||||
void set_priority(int p_priority);
|
||||
int get_priority() const;
|
||||
|
||||
AnimationNodeStateMachineTransition();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AnimationNodeStateMachineTransition::SwitchMode)
|
||||
VARIANT_ENUM_CAST(AnimationNodeStateMachineTransition::AdvanceMode)
|
||||
|
||||
class AnimationNodeStateMachinePlayback;
|
||||
|
||||
class AnimationNodeStateMachine : public AnimationRootNode {
|
||||
GDCLASS(AnimationNodeStateMachine, AnimationRootNode);
|
||||
|
||||
public:
|
||||
static StringName START_NODE;
|
||||
static StringName END_NODE;
|
||||
|
||||
enum StateMachineType {
|
||||
STATE_MACHINE_TYPE_ROOT,
|
||||
STATE_MACHINE_TYPE_NESTED,
|
||||
STATE_MACHINE_TYPE_GROUPED,
|
||||
};
|
||||
|
||||
private:
|
||||
friend class AnimationNodeStateMachinePlayback;
|
||||
|
||||
StateMachineType state_machine_type = STATE_MACHINE_TYPE_ROOT;
|
||||
|
||||
struct State {
|
||||
Ref<AnimationRootNode> node;
|
||||
Vector2 position;
|
||||
};
|
||||
|
||||
HashMap<StringName, State> states;
|
||||
bool allow_transition_to_self = false;
|
||||
bool reset_ends = false;
|
||||
|
||||
struct Transition {
|
||||
StringName from;
|
||||
StringName to;
|
||||
Ref<AnimationNodeStateMachineTransition> transition;
|
||||
};
|
||||
|
||||
Vector<Transition> transitions;
|
||||
|
||||
StringName playback = "playback";
|
||||
bool updating_transitions = false;
|
||||
|
||||
Vector2 graph_offset;
|
||||
|
||||
void _remove_transition(const Ref<AnimationNodeStateMachineTransition> p_transition);
|
||||
void _rename_transitions(const StringName &p_name, const StringName &p_new_name);
|
||||
bool _can_connect(const StringName &p_name);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
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;
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
||||
bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const;
|
||||
|
||||
virtual void _tree_changed() override;
|
||||
virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) override;
|
||||
virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node) override;
|
||||
|
||||
virtual void reset_state() override;
|
||||
|
||||
public:
|
||||
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
|
||||
virtual bool is_parameter_read_only(const StringName &p_parameter) const override;
|
||||
|
||||
void add_node(const StringName &p_name, Ref<AnimationNode> p_node, const Vector2 &p_position = Vector2());
|
||||
void replace_node(const StringName &p_name, Ref<AnimationNode> p_node);
|
||||
Ref<AnimationNode> get_node(const StringName &p_name) const;
|
||||
void remove_node(const StringName &p_name);
|
||||
void rename_node(const StringName &p_name, const StringName &p_new_name);
|
||||
bool has_node(const StringName &p_name) const;
|
||||
StringName get_node_name(const Ref<AnimationNode> &p_node) const;
|
||||
void get_node_list(List<StringName> *r_nodes) const;
|
||||
|
||||
void set_node_position(const StringName &p_name, const Vector2 &p_position);
|
||||
Vector2 get_node_position(const StringName &p_name) const;
|
||||
|
||||
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_transition_from(const StringName &p_from) const;
|
||||
bool has_transition_to(const StringName &p_to) const;
|
||||
int find_transition(const StringName &p_from, const StringName &p_to) const;
|
||||
Vector<int> find_transition_from(const StringName &p_from) const;
|
||||
Vector<int> find_transition_to(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;
|
||||
bool is_transition_across_group(int p_transition) const;
|
||||
void remove_transition_by_index(const int p_transition);
|
||||
void remove_transition(const StringName &p_from, const StringName &p_to);
|
||||
|
||||
void set_state_machine_type(StateMachineType p_state_machine_type);
|
||||
StateMachineType get_state_machine_type() const;
|
||||
|
||||
void set_allow_transition_to_self(bool p_enable);
|
||||
bool is_allow_transition_to_self() const;
|
||||
|
||||
void set_reset_ends(bool p_enable);
|
||||
bool are_ends_reset() const;
|
||||
|
||||
bool can_edit_node(const StringName &p_name) const;
|
||||
|
||||
void set_graph_offset(const Vector2 &p_offset);
|
||||
Vector2 get_graph_offset() const;
|
||||
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
|
||||
virtual String get_caption() const override;
|
||||
|
||||
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
|
||||
#endif
|
||||
|
||||
AnimationNodeStateMachine();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AnimationNodeStateMachine::StateMachineType);
|
||||
|
||||
class AnimationNodeStateMachinePlayback : public Resource {
|
||||
GDCLASS(AnimationNodeStateMachinePlayback, Resource);
|
||||
|
||||
friend class AnimationNodeStateMachine;
|
||||
|
||||
struct AStarCost {
|
||||
float distance = 0.0;
|
||||
StringName prev;
|
||||
};
|
||||
|
||||
struct TransitionInfo {
|
||||
StringName from;
|
||||
StringName to;
|
||||
StringName next;
|
||||
};
|
||||
|
||||
struct NextInfo {
|
||||
StringName node;
|
||||
double xfade;
|
||||
Ref<Curve> curve;
|
||||
AnimationNodeStateMachineTransition::SwitchMode switch_mode;
|
||||
bool is_reset;
|
||||
bool break_loop_at_end;
|
||||
};
|
||||
|
||||
struct ChildStateMachineInfo {
|
||||
Ref<AnimationNodeStateMachinePlayback> playback;
|
||||
Vector<StringName> path;
|
||||
bool is_reset = false;
|
||||
};
|
||||
|
||||
Ref<AnimationNodeStateMachineTransition> default_transition;
|
||||
String base_path;
|
||||
|
||||
AnimationNode::NodeTimeInfo current_nti;
|
||||
StringName current;
|
||||
Ref<Curve> current_curve;
|
||||
|
||||
Ref<AnimationNodeStateMachineTransition> group_start_transition;
|
||||
Ref<AnimationNodeStateMachineTransition> group_end_transition;
|
||||
|
||||
AnimationNode::NodeTimeInfo fadeing_from_nti;
|
||||
StringName fading_from;
|
||||
float fading_time = 0.0;
|
||||
float fading_pos = 0.0;
|
||||
|
||||
Vector<StringName> path;
|
||||
bool playing = false;
|
||||
|
||||
StringName start_request;
|
||||
StringName travel_request;
|
||||
bool reset_request = false;
|
||||
bool reset_request_on_teleport = false;
|
||||
bool _reset_request_for_fading_from = false;
|
||||
bool next_request = false;
|
||||
bool stop_request = false;
|
||||
bool teleport_request = false;
|
||||
|
||||
bool is_grouped = false;
|
||||
|
||||
void _travel_main(const StringName &p_state, bool p_reset_on_teleport = true);
|
||||
void _start_main(const StringName &p_state, bool p_reset = true);
|
||||
void _next_main();
|
||||
void _stop_main();
|
||||
|
||||
bool _make_travel_path(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, Vector<StringName> &r_path, bool p_test_only);
|
||||
String _validate_path(AnimationNodeStateMachine *p_state_machine, const String &p_path);
|
||||
bool _travel(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_is_allow_transition_to_self, bool p_test_only);
|
||||
void _start(AnimationNodeStateMachine *p_state_machine);
|
||||
|
||||
void _clear_path_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only);
|
||||
bool _travel_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only);
|
||||
void _start_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_test_only);
|
||||
|
||||
AnimationNode::NodeTimeInfo process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only);
|
||||
AnimationNode::NodeTimeInfo _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only);
|
||||
|
||||
bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const;
|
||||
bool _transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, double p_delta, bool p_test_only);
|
||||
NextInfo _find_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine) const;
|
||||
Ref<AnimationNodeStateMachineTransition> _check_group_transition(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const AnimationNodeStateMachine::Transition &p_transition, Ref<AnimationNodeStateMachine> &r_state_machine, bool &r_bypass) const;
|
||||
bool _can_transition_to_next(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, NextInfo p_next, bool p_test_only);
|
||||
|
||||
void _set_current(AnimationNodeStateMachine *p_state_machine, const StringName &p_state);
|
||||
void _set_grouped(bool p_is_grouped);
|
||||
void _set_base_path(const String &p_base_path);
|
||||
Ref<AnimationNodeStateMachinePlayback> _get_parent_playback(AnimationTree *p_tree) const;
|
||||
Ref<AnimationNodeStateMachine> _get_parent_state_machine(AnimationTree *p_tree) const;
|
||||
Ref<AnimationNodeStateMachineTransition> _get_group_start_transition() const;
|
||||
Ref<AnimationNodeStateMachineTransition> _get_group_end_transition() const;
|
||||
|
||||
TypedArray<StringName> _get_travel_path() const;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void travel(const StringName &p_state, bool p_reset_on_teleport = true);
|
||||
void start(const StringName &p_state, bool p_reset = true);
|
||||
void next();
|
||||
void stop();
|
||||
bool is_playing() const;
|
||||
bool is_end() const;
|
||||
StringName get_current_node() const;
|
||||
StringName get_fading_from_node() const;
|
||||
Vector<StringName> get_travel_path() const;
|
||||
float get_current_play_pos() const;
|
||||
float get_current_length() const;
|
||||
|
||||
float get_fade_from_play_pos() const;
|
||||
float get_fade_from_length() const;
|
||||
|
||||
float get_fading_time() const;
|
||||
float get_fading_pos() const;
|
||||
|
||||
void clear_path();
|
||||
void push_path(const StringName &p_state);
|
||||
|
||||
AnimationNodeStateMachinePlayback();
|
||||
};
|
||||
|
||||
#endif // ANIMATION_NODE_STATE_MACHINE_H
|
||||
86
engine/scene/animation/animation_player.compat.inc
Normal file
86
engine/scene/animation/animation_player.compat.inc
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/**************************************************************************/
|
||||
/* animation_player.compat.inc */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
||||
void AnimationPlayer::_set_process_callback_bind_compat_80813(AnimationPlayer::AnimationProcessCallback p_mode) {
|
||||
set_callback_mode_process(static_cast<AnimationMixer::AnimationCallbackModeProcess>(static_cast<int>(p_mode)));
|
||||
}
|
||||
|
||||
AnimationPlayer::AnimationProcessCallback AnimationPlayer::_get_process_callback_bind_compat_80813() const {
|
||||
return static_cast<AnimationProcessCallback>(static_cast<int>(get_callback_mode_process()));
|
||||
}
|
||||
|
||||
void AnimationPlayer::_set_method_call_mode_bind_compat_80813(AnimationPlayer::AnimationMethodCallMode p_mode) {
|
||||
set_callback_mode_method(static_cast<AnimationMixer::AnimationCallbackModeMethod>(static_cast<int>(p_mode)));
|
||||
}
|
||||
|
||||
AnimationPlayer::AnimationMethodCallMode AnimationPlayer::_get_method_call_mode_bind_compat_80813() const {
|
||||
return static_cast<AnimationMethodCallMode>(static_cast<int>(get_callback_mode_method()));
|
||||
}
|
||||
|
||||
void AnimationPlayer::_set_root_bind_compat_80813(const NodePath &p_root) {
|
||||
set_root_node(p_root);
|
||||
}
|
||||
|
||||
NodePath AnimationPlayer::_get_root_bind_compat_80813() const {
|
||||
return get_root_node();
|
||||
}
|
||||
|
||||
void AnimationPlayer::_seek_bind_compat_80813(double p_time, bool p_update) {
|
||||
seek(p_time, p_update, false);
|
||||
}
|
||||
|
||||
void AnimationPlayer::_play_compat_84906(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
|
||||
play(p_name, p_custom_blend, p_custom_scale, p_from_end);
|
||||
}
|
||||
|
||||
void AnimationPlayer::_play_backwards_compat_84906(const StringName &p_name, double p_custom_blend) {
|
||||
play_backwards(p_name, p_custom_blend);
|
||||
}
|
||||
|
||||
void AnimationPlayer::_bind_compatibility_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &AnimationPlayer::_set_process_callback_bind_compat_80813);
|
||||
ClassDB::bind_method(D_METHOD("get_process_callback"), &AnimationPlayer::_get_process_callback_bind_compat_80813);
|
||||
ClassDB::bind_method(D_METHOD("set_method_call_mode", "mode"), &AnimationPlayer::_set_method_call_mode_bind_compat_80813);
|
||||
ClassDB::bind_method(D_METHOD("get_method_call_mode"), &AnimationPlayer::_get_method_call_mode_bind_compat_80813);
|
||||
ClassDB::bind_method(D_METHOD("set_root", "path"), &AnimationPlayer::_set_root_bind_compat_80813);
|
||||
ClassDB::bind_method(D_METHOD("get_root"), &AnimationPlayer::_get_root_bind_compat_80813);
|
||||
ClassDB::bind_compatibility_method(D_METHOD("seek", "seconds", "update"), &AnimationPlayer::_seek_bind_compat_80813, DEFVAL(false));
|
||||
ClassDB::bind_compatibility_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::_play_compat_84906, DEFVAL(""), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
|
||||
ClassDB::bind_compatibility_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::_play_backwards_compat_84906, DEFVAL(""), DEFVAL(-1));
|
||||
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS);
|
||||
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE);
|
||||
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL);
|
||||
BIND_ENUM_CONSTANT(ANIMATION_METHOD_CALL_DEFERRED);
|
||||
BIND_ENUM_CONSTANT(ANIMATION_METHOD_CALL_IMMEDIATE);
|
||||
}
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
||||
922
engine/scene/animation/animation_player.cpp
Normal file
922
engine/scene/animation/animation_player.cpp
Normal file
|
|
@ -0,0 +1,922 @@
|
|||
/**************************************************************************/
|
||||
/* animation_player.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_player.h"
|
||||
#include "animation_player.compat.inc"
|
||||
|
||||
#include "core/config/engine.h"
|
||||
|
||||
bool AnimationPlayer::_set(const StringName &p_name, const Variant &p_value) {
|
||||
String name = p_name;
|
||||
if (name.begins_with("playback/play")) { // For backward compatibility.
|
||||
set_current_animation(p_value);
|
||||
} else if (name.begins_with("next/")) {
|
||||
String which = name.get_slicec('/', 1);
|
||||
animation_set_next(which, p_value);
|
||||
} else if (p_name == SceneStringName(blend_times)) {
|
||||
Array array = p_value;
|
||||
int len = array.size();
|
||||
ERR_FAIL_COND_V(len % 3, false);
|
||||
|
||||
for (int i = 0; i < len / 3; i++) {
|
||||
StringName from = array[i * 3 + 0];
|
||||
StringName to = array[i * 3 + 1];
|
||||
float time = array[i * 3 + 2];
|
||||
set_blend_time(from, to, time);
|
||||
}
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
} else if (p_name == "method_call_mode") {
|
||||
set_callback_mode_method(static_cast<AnimationCallbackModeMethod>((int)p_value));
|
||||
} else if (p_name == "playback_process_mode") {
|
||||
set_callback_mode_process(static_cast<AnimationCallbackModeProcess>((int)p_value));
|
||||
} else if (p_name == "playback_active") {
|
||||
set_active(p_value);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AnimationPlayer::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
String name = p_name;
|
||||
|
||||
if (name == "playback/play") { // For backward compatibility.
|
||||
|
||||
r_ret = get_current_animation();
|
||||
|
||||
} else if (name.begins_with("next/")) {
|
||||
String which = name.get_slicec('/', 1);
|
||||
r_ret = animation_get_next(which);
|
||||
|
||||
} else if (p_name == SceneStringName(blend_times)) {
|
||||
Vector<BlendKey> keys;
|
||||
for (const KeyValue<BlendKey, double> &E : blend_times) {
|
||||
keys.ordered_insert(E.key);
|
||||
}
|
||||
|
||||
Array array;
|
||||
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.get(keys[i]));
|
||||
}
|
||||
|
||||
r_ret = array;
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
} else if (name == "method_call_mode") {
|
||||
r_ret = get_callback_mode_method();
|
||||
} else if (name == "playback_process_mode") {
|
||||
r_ret = get_callback_mode_process();
|
||||
} else if (name == "playback_active") {
|
||||
r_ret = is_active();
|
||||
#endif // DISABLE_DEPRECATED
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AnimationPlayer::_validate_property(PropertyInfo &p_property) const {
|
||||
AnimationMixer::_validate_property(p_property);
|
||||
|
||||
if (p_property.name == "current_animation") {
|
||||
List<String> names;
|
||||
|
||||
for (const KeyValue<StringName, AnimationData> &E : animation_set) {
|
||||
names.push_back(E.key);
|
||||
}
|
||||
names.push_front("[stop]");
|
||||
String hint;
|
||||
for (List<String>::Element *E = names.front(); E; E = E->next()) {
|
||||
if (E != names.front()) {
|
||||
hint += ",";
|
||||
}
|
||||
hint += E->get();
|
||||
}
|
||||
|
||||
p_property.hint_string = hint;
|
||||
} else if (!auto_capture && p_property.name.begins_with("playback_auto_capture_")) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
List<PropertyInfo> anim_names;
|
||||
|
||||
for (const KeyValue<StringName, AnimationData> &E : animation_set) {
|
||||
HashMap<StringName, StringName>::ConstIterator F = animation_next_set.find(E.key);
|
||||
if (F && F->value != StringName()) {
|
||||
anim_names.push_back(PropertyInfo(Variant::STRING, "next/" + String(E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
|
||||
}
|
||||
}
|
||||
|
||||
for (const PropertyInfo &E : anim_names) {
|
||||
p_list->push_back(E);
|
||||
}
|
||||
|
||||
p_list->push_back(PropertyInfo(Variant::ARRAY, "blend_times", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
|
||||
}
|
||||
|
||||
void AnimationPlayer::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_READY: {
|
||||
if (!Engine::get_singleton()->is_editor_hint() && animation_set.has(autoplay)) {
|
||||
set_active(active);
|
||||
play(autoplay);
|
||||
_check_immediately_after_start();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_internal_seeked, bool p_started, bool p_is_current) {
|
||||
double speed = speed_scale * cd.speed_scale;
|
||||
bool backwards = signbit(speed); // Negative zero means playing backwards too.
|
||||
double delta = p_started ? 0 : p_delta * speed;
|
||||
double next_pos = cd.pos + delta;
|
||||
|
||||
double len = cd.from->animation->get_length();
|
||||
Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
|
||||
|
||||
switch (cd.from->animation->get_loop_mode()) {
|
||||
case Animation::LOOP_NONE: {
|
||||
if (Animation::is_less_approx(next_pos, 0)) {
|
||||
next_pos = 0;
|
||||
} else if (Animation::is_greater_approx(next_pos, len)) {
|
||||
next_pos = len;
|
||||
}
|
||||
delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here).
|
||||
} break;
|
||||
|
||||
case Animation::LOOP_LINEAR: {
|
||||
if (Animation::is_less_approx(next_pos, 0) && Animation::is_greater_or_equal_approx(cd.pos, 0)) {
|
||||
looped_flag = Animation::LOOPED_FLAG_START;
|
||||
}
|
||||
if (Animation::is_greater_approx(next_pos, len) && Animation::is_less_or_equal_approx(cd.pos, len)) {
|
||||
looped_flag = Animation::LOOPED_FLAG_END;
|
||||
}
|
||||
next_pos = Math::fposmod(next_pos, (double)len);
|
||||
} break;
|
||||
|
||||
case Animation::LOOP_PINGPONG: {
|
||||
if (Animation::is_less_approx(next_pos, 0) && Animation::is_greater_or_equal_approx(cd.pos, 0)) {
|
||||
cd.speed_scale *= -1.0;
|
||||
looped_flag = Animation::LOOPED_FLAG_START;
|
||||
}
|
||||
if (Animation::is_greater_approx(next_pos, len) && Animation::is_less_or_equal_approx(cd.pos, len)) {
|
||||
cd.speed_scale *= -1.0;
|
||||
looped_flag = Animation::LOOPED_FLAG_END;
|
||||
}
|
||||
next_pos = Math::pingpong(next_pos, (double)len);
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
double prev_pos = cd.pos; // The animation may be changed during process, so it is safer that the state is changed before process.
|
||||
|
||||
// End detection.
|
||||
if (p_is_current) {
|
||||
if (cd.from->animation->get_loop_mode() == Animation::LOOP_NONE) {
|
||||
if (!backwards && Animation::is_less_or_equal_approx(prev_pos, len) && Math::is_equal_approx(next_pos, len)) {
|
||||
// Playback finished.
|
||||
next_pos = len; // Snap to the edge.
|
||||
end_reached = true;
|
||||
end_notify = Animation::is_less_approx(prev_pos, len); // Notify only if not already at the end.
|
||||
p_blend = 1.0;
|
||||
}
|
||||
if (backwards && Animation::is_greater_or_equal_approx(prev_pos, 0) && Math::is_equal_approx(next_pos, 0)) {
|
||||
// Playback finished.
|
||||
next_pos = 0; // Snap to the edge.
|
||||
end_reached = true;
|
||||
end_notify = Animation::is_greater_approx(prev_pos, 0); // Notify only if not already at the beginning.
|
||||
p_blend = 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cd.pos = next_pos;
|
||||
|
||||
PlaybackInfo pi;
|
||||
if (p_started) {
|
||||
pi.time = prev_pos;
|
||||
pi.delta = 0;
|
||||
pi.seeked = true;
|
||||
} else {
|
||||
pi.time = next_pos;
|
||||
pi.delta = delta;
|
||||
pi.seeked = p_seeked;
|
||||
}
|
||||
if (Math::is_zero_approx(pi.delta) && backwards) {
|
||||
pi.delta = -0.0; // Sign is needed to handle converted Continuous track from Discrete track correctly.
|
||||
}
|
||||
// Immediately after playback, discrete keys should be retrieved with EXACT mode since behind keys must be ignored at that time.
|
||||
pi.is_external_seeking = !p_internal_seeked && !p_started;
|
||||
pi.looped_flag = looped_flag;
|
||||
pi.weight = p_blend;
|
||||
make_animation_instance(cd.from->name, pi);
|
||||
}
|
||||
|
||||
float AnimationPlayer::get_current_blend_amount() {
|
||||
Playback &c = playback;
|
||||
float blend = 1.0;
|
||||
for (List<Blend>::Element *E = c.blend.front(); E; E = E->next()) {
|
||||
Blend &b = E->get();
|
||||
blend = blend - b.blend_left;
|
||||
}
|
||||
return MAX(0, blend);
|
||||
}
|
||||
|
||||
void AnimationPlayer::_blend_playback_data(double p_delta, bool p_started) {
|
||||
Playback &c = playback;
|
||||
|
||||
bool seeked = c.seeked; // The animation may be changed during process, so it is safer that the state is changed before process.
|
||||
bool internal_seeked = c.internal_seeked;
|
||||
|
||||
if (!Math::is_zero_approx(p_delta)) {
|
||||
c.seeked = false;
|
||||
c.internal_seeked = false;
|
||||
}
|
||||
|
||||
// Second, process current animation to check if the animation end reached.
|
||||
_process_playback_data(c.current, p_delta, get_current_blend_amount(), seeked, internal_seeked, p_started, true);
|
||||
|
||||
// Finally, if not end the animation, do blending.
|
||||
if (end_reached) {
|
||||
playback.blend.clear();
|
||||
return;
|
||||
}
|
||||
List<List<Blend>::Element *> to_erase;
|
||||
for (List<Blend>::Element *E = c.blend.front(); E; E = E->next()) {
|
||||
Blend &b = E->get();
|
||||
b.blend_left = MAX(0, b.blend_left - Math::absf(speed_scale * p_delta) / b.blend_time);
|
||||
if (Animation::is_less_or_equal_approx(b.blend_left, 0)) {
|
||||
to_erase.push_back(E);
|
||||
b.blend_left = CMP_EPSILON; // May want to play last frame.
|
||||
}
|
||||
// Note: There may be issues if an animation event triggers an animation change while this blend is active,
|
||||
// so it is best to use "deferred" calls instead of "immediate" for animation events that can trigger new animations.
|
||||
_process_playback_data(b.data, p_delta, b.blend_left, false, false, false);
|
||||
}
|
||||
for (List<Blend>::Element *&E : to_erase) {
|
||||
c.blend.erase(E);
|
||||
}
|
||||
}
|
||||
|
||||
bool AnimationPlayer::_blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) {
|
||||
if (!playback.current.from) {
|
||||
_set_process(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
tmp_from = playback.current.from->animation->get_instance_id();
|
||||
end_reached = false;
|
||||
end_notify = false;
|
||||
|
||||
bool started = playback.started; // The animation may be changed during process, so it is safer that the state is changed before process.
|
||||
if (playback.started) {
|
||||
playback.started = false;
|
||||
}
|
||||
|
||||
AnimationData *prev_from = playback.current.from;
|
||||
_blend_playback_data(p_delta, started);
|
||||
|
||||
if (prev_from != playback.current.from) {
|
||||
return false; // Animation has been changed in the process (may be caused by method track), abort process.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AnimationPlayer::_blend_capture(double p_delta) {
|
||||
blend_capture(p_delta * Math::abs(speed_scale));
|
||||
}
|
||||
|
||||
void AnimationPlayer::_blend_post_process() {
|
||||
if (end_reached) {
|
||||
// If the method track changes current animation, the animation is not finished.
|
||||
if (tmp_from == playback.current.from->animation->get_instance_id()) {
|
||||
if (playback_queue.size()) {
|
||||
String old = playback.assigned;
|
||||
play(playback_queue.front()->get());
|
||||
String new_name = playback.assigned;
|
||||
playback_queue.pop_front();
|
||||
if (end_notify) {
|
||||
emit_signal(SceneStringName(animation_changed), old, new_name);
|
||||
}
|
||||
} else {
|
||||
_clear_caches();
|
||||
playing = false;
|
||||
_set_process(false);
|
||||
if (end_notify) {
|
||||
emit_signal(SceneStringName(animation_finished), playback.assigned);
|
||||
if (movie_quit_on_finish && OS::get_singleton()->has_feature("movie")) {
|
||||
print_line(vformat("Movie Maker mode is enabled. Quitting on animation finish as requested by: %s", get_path()));
|
||||
get_tree()->quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end_reached = false;
|
||||
end_notify = false;
|
||||
}
|
||||
tmp_from = ObjectID();
|
||||
}
|
||||
|
||||
void AnimationPlayer::queue(const StringName &p_name) {
|
||||
if (!is_playing()) {
|
||||
play(p_name);
|
||||
} else {
|
||||
playback_queue.push_back(p_name);
|
||||
}
|
||||
}
|
||||
|
||||
Vector<String> AnimationPlayer::get_queue() {
|
||||
Vector<String> ret;
|
||||
for (const StringName &E : playback_queue) {
|
||||
ret.push_back(E);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AnimationPlayer::clear_queue() {
|
||||
playback_queue.clear();
|
||||
}
|
||||
|
||||
void AnimationPlayer::play_backwards(const StringName &p_name, double p_custom_blend) {
|
||||
play(p_name, p_custom_blend, -1, true);
|
||||
}
|
||||
|
||||
void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
|
||||
if (auto_capture) {
|
||||
play_with_capture(p_name, auto_capture_duration, p_custom_blend, p_custom_scale, p_from_end, auto_capture_transition_type, auto_capture_ease_type);
|
||||
} else {
|
||||
_play(p_name, p_custom_blend, p_custom_scale, p_from_end);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPlayer::_play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
|
||||
StringName name = p_name;
|
||||
|
||||
if (name == StringName()) {
|
||||
name = playback.assigned;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name));
|
||||
|
||||
Playback &c = playback;
|
||||
|
||||
if (c.current.from) {
|
||||
double blend_time = 0.0;
|
||||
// Find if it can blend.
|
||||
BlendKey bk;
|
||||
bk.from = c.current.from->name;
|
||||
bk.to = name;
|
||||
|
||||
if (Animation::is_greater_or_equal_approx(p_custom_blend, 0)) {
|
||||
blend_time = p_custom_blend;
|
||||
} else if (blend_times.has(bk)) {
|
||||
blend_time = blend_times[bk];
|
||||
} else {
|
||||
bk.from = "*";
|
||||
if (blend_times.has(bk)) {
|
||||
blend_time = blend_times[bk];
|
||||
} else {
|
||||
bk.from = c.current.from->name;
|
||||
bk.to = "*";
|
||||
|
||||
if (blend_times.has(bk)) {
|
||||
blend_time = blend_times[bk];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Animation::is_less_approx(p_custom_blend, 0) && Math::is_zero_approx(blend_time) && default_blend_time) {
|
||||
blend_time = default_blend_time;
|
||||
}
|
||||
if (Animation::is_greater_approx(blend_time, 0)) {
|
||||
Blend b;
|
||||
b.data = c.current;
|
||||
b.blend_left = get_current_blend_amount();
|
||||
b.blend_time = blend_time;
|
||||
c.blend.push_back(b);
|
||||
} else {
|
||||
c.blend.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (get_current_animation() != p_name) {
|
||||
_clear_playing_caches();
|
||||
}
|
||||
|
||||
c.current.from = &animation_set[name];
|
||||
c.current.speed_scale = p_custom_scale;
|
||||
|
||||
if (!end_reached) {
|
||||
playback_queue.clear();
|
||||
}
|
||||
|
||||
if (c.assigned != name) { // Reset.
|
||||
c.current.pos = p_from_end ? c.current.from->animation->get_length() : 0;
|
||||
c.assigned = name;
|
||||
emit_signal(SNAME("current_animation_changed"), c.assigned);
|
||||
} else {
|
||||
if (p_from_end && Math::is_zero_approx(c.current.pos)) {
|
||||
// Animation reset but played backwards, set position to the end.
|
||||
seek_internal(c.current.from->animation->get_length(), true, true, true);
|
||||
} else if (!p_from_end && Math::is_equal_approx(c.current.pos, (double)c.current.from->animation->get_length())) {
|
||||
// Animation resumed but already ended, set position to the beginning.
|
||||
seek_internal(0, true, true, true);
|
||||
} else if (playing) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
c.seeked = false;
|
||||
c.started = true;
|
||||
|
||||
_set_process(true); // Always process when starting an animation.
|
||||
playing = true;
|
||||
|
||||
emit_signal(SceneStringName(animation_started), c.assigned);
|
||||
|
||||
if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) {
|
||||
return; // No next in this case.
|
||||
}
|
||||
|
||||
StringName next = animation_get_next(p_name);
|
||||
if (next != StringName() && animation_set.has(next)) {
|
||||
queue(next);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPlayer::_capture(const StringName &p_name, bool p_from_end, double p_duration, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) {
|
||||
StringName name = p_name;
|
||||
if (name == StringName()) {
|
||||
name = playback.assigned;
|
||||
}
|
||||
|
||||
Ref<Animation> anim = get_animation(name);
|
||||
if (anim.is_null() || !anim->is_capture_included()) {
|
||||
return;
|
||||
}
|
||||
if (signbit(p_duration)) {
|
||||
double max_dur = 0;
|
||||
double current_pos = playback.current.pos;
|
||||
if (playback.assigned != name) {
|
||||
current_pos = p_from_end ? anim->get_length() : 0;
|
||||
}
|
||||
for (int i = 0; i < anim->get_track_count(); i++) {
|
||||
if (anim->track_get_type(i) != Animation::TYPE_VALUE) {
|
||||
continue;
|
||||
}
|
||||
if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) {
|
||||
continue;
|
||||
}
|
||||
if (anim->track_get_key_count(i) == 0) {
|
||||
continue;
|
||||
}
|
||||
max_dur = MAX(max_dur, p_from_end ? current_pos - anim->track_get_key_time(i, anim->track_get_key_count(i) - 1) : anim->track_get_key_time(i, 0) - current_pos);
|
||||
}
|
||||
p_duration = max_dur;
|
||||
}
|
||||
if (Math::is_zero_approx(p_duration)) {
|
||||
return;
|
||||
}
|
||||
capture(name, p_duration, p_trans_type, p_ease_type);
|
||||
}
|
||||
|
||||
void AnimationPlayer::play_with_capture(const StringName &p_name, double p_duration, double p_custom_blend, float p_custom_scale, bool p_from_end, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) {
|
||||
_capture(p_name, p_from_end, p_duration, p_trans_type, p_ease_type);
|
||||
_play(p_name, p_custom_blend, p_custom_scale, p_from_end);
|
||||
}
|
||||
|
||||
bool AnimationPlayer::is_playing() const {
|
||||
return playing;
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_current_animation(const String &p_animation) {
|
||||
if (p_animation == "[stop]" || p_animation.is_empty()) {
|
||||
stop();
|
||||
} else if (!is_playing()) {
|
||||
play(p_animation);
|
||||
} else if (playback.assigned != p_animation) {
|
||||
float speed = playback.current.speed_scale;
|
||||
play(p_animation, -1.0, speed, signbit(speed));
|
||||
} else {
|
||||
// Same animation, do not replay from start.
|
||||
}
|
||||
}
|
||||
|
||||
String AnimationPlayer::get_current_animation() const {
|
||||
return (is_playing() ? playback.assigned : "");
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_assigned_animation(const String &p_animation) {
|
||||
if (is_playing()) {
|
||||
float speed = playback.current.speed_scale;
|
||||
play(p_animation, -1.0, speed, signbit(speed));
|
||||
} else {
|
||||
ERR_FAIL_COND_MSG(!animation_set.has(p_animation), vformat("Animation not found: %s.", p_animation));
|
||||
playback.current.pos = 0;
|
||||
playback.current.from = &animation_set[p_animation];
|
||||
playback.assigned = p_animation;
|
||||
emit_signal(SNAME("current_animation_changed"), playback.assigned);
|
||||
}
|
||||
}
|
||||
|
||||
String AnimationPlayer::get_assigned_animation() const {
|
||||
return playback.assigned;
|
||||
}
|
||||
|
||||
void AnimationPlayer::pause() {
|
||||
_stop_internal(false, false);
|
||||
}
|
||||
|
||||
void AnimationPlayer::stop(bool p_keep_state) {
|
||||
_stop_internal(true, p_keep_state);
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_speed_scale(float p_speed) {
|
||||
speed_scale = p_speed;
|
||||
}
|
||||
|
||||
float AnimationPlayer::get_speed_scale() const {
|
||||
return speed_scale;
|
||||
}
|
||||
|
||||
float AnimationPlayer::get_playing_speed() const {
|
||||
if (!playing) {
|
||||
return 0;
|
||||
}
|
||||
return speed_scale * playback.current.speed_scale;
|
||||
}
|
||||
|
||||
void AnimationPlayer::seek_internal(double p_time, bool p_update, bool p_update_only, bool p_is_internal_seek) {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_backward = Animation::is_less_approx(p_time, playback.current.pos);
|
||||
|
||||
_check_immediately_after_start();
|
||||
|
||||
playback.current.pos = p_time;
|
||||
if (!playback.current.from) {
|
||||
if (playback.assigned) {
|
||||
ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned));
|
||||
playback.current.from = &animation_set[playback.assigned];
|
||||
}
|
||||
if (!playback.current.from) {
|
||||
return; // There is no animation.
|
||||
}
|
||||
}
|
||||
|
||||
playback.seeked = true;
|
||||
playback.internal_seeked = p_is_internal_seek;
|
||||
|
||||
if (p_update) {
|
||||
_process_animation(is_backward ? -0.0 : 0.0, p_update_only);
|
||||
playback.seeked = false; // If animation was proceeded here, no more seek in internal process.
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPlayer::seek(double p_time, bool p_update, bool p_update_only) {
|
||||
seek_internal(p_time, p_update, p_update_only);
|
||||
}
|
||||
|
||||
void AnimationPlayer::advance(double p_time) {
|
||||
_check_immediately_after_start();
|
||||
AnimationMixer::advance(p_time);
|
||||
}
|
||||
|
||||
void AnimationPlayer::_check_immediately_after_start() {
|
||||
if (playback.started) {
|
||||
_process_animation(0); // Force process current key for Discrete/Method/Audio/AnimationPlayback. Then, started flag is cleared.
|
||||
}
|
||||
}
|
||||
|
||||
bool AnimationPlayer::is_valid() const {
|
||||
return (playback.current.from);
|
||||
}
|
||||
|
||||
double AnimationPlayer::get_current_animation_position() const {
|
||||
ERR_FAIL_NULL_V_MSG(playback.current.from, 0, "AnimationPlayer has no current animation.");
|
||||
return playback.current.pos;
|
||||
}
|
||||
|
||||
double AnimationPlayer::get_current_animation_length() const {
|
||||
ERR_FAIL_NULL_V_MSG(playback.current.from, 0, "AnimationPlayer has no current animation.");
|
||||
return playback.current.from->animation->get_length();
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_autoplay(const String &p_name) {
|
||||
if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) {
|
||||
WARN_PRINT("Setting autoplay after the node has been added to the scene has no effect.");
|
||||
}
|
||||
|
||||
autoplay = p_name;
|
||||
}
|
||||
|
||||
String AnimationPlayer::get_autoplay() const {
|
||||
return autoplay;
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_movie_quit_on_finish_enabled(bool p_enabled) {
|
||||
movie_quit_on_finish = p_enabled;
|
||||
}
|
||||
|
||||
bool AnimationPlayer::is_movie_quit_on_finish_enabled() const {
|
||||
return movie_quit_on_finish;
|
||||
}
|
||||
|
||||
void AnimationPlayer::_stop_internal(bool p_reset, bool p_keep_state) {
|
||||
_clear_caches();
|
||||
Playback &c = playback;
|
||||
// c.blend.clear();
|
||||
if (p_reset) {
|
||||
c.blend.clear();
|
||||
if (p_keep_state) {
|
||||
c.current.pos = 0;
|
||||
} else {
|
||||
is_stopping = true;
|
||||
seek_internal(0, true, true, true);
|
||||
is_stopping = false;
|
||||
}
|
||||
c.current.from = nullptr;
|
||||
c.current.speed_scale = 1;
|
||||
emit_signal(SNAME("current_animation_changed"), "");
|
||||
}
|
||||
_set_process(false);
|
||||
playback_queue.clear();
|
||||
playing = false;
|
||||
}
|
||||
|
||||
void AnimationPlayer::animation_set_next(const StringName &p_animation, const StringName &p_next) {
|
||||
ERR_FAIL_COND_MSG(!animation_set.has(p_animation), vformat("Animation not found: %s.", p_animation));
|
||||
animation_next_set[p_animation] = p_next;
|
||||
}
|
||||
|
||||
StringName AnimationPlayer::animation_get_next(const StringName &p_animation) const {
|
||||
if (!animation_next_set.has(p_animation)) {
|
||||
return StringName();
|
||||
}
|
||||
return animation_next_set[p_animation];
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_default_blend_time(double p_default) {
|
||||
default_blend_time = p_default;
|
||||
}
|
||||
|
||||
double AnimationPlayer::get_default_blend_time() const {
|
||||
return default_blend_time;
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_blend_time(const StringName &p_animation1, const StringName &p_animation2, double p_time) {
|
||||
ERR_FAIL_COND_MSG(!animation_set.has(p_animation1), vformat("Animation not found: %s.", p_animation1));
|
||||
ERR_FAIL_COND_MSG(!animation_set.has(p_animation2), vformat("Animation not found: %s.", p_animation2));
|
||||
ERR_FAIL_COND_MSG(p_time < 0, "Blend time cannot be smaller than 0.");
|
||||
|
||||
BlendKey bk;
|
||||
bk.from = p_animation1;
|
||||
bk.to = p_animation2;
|
||||
if (Math::is_zero_approx(p_time)) {
|
||||
blend_times.erase(bk);
|
||||
} else {
|
||||
blend_times[bk] = p_time;
|
||||
}
|
||||
}
|
||||
|
||||
double AnimationPlayer::get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const {
|
||||
BlendKey bk;
|
||||
bk.from = p_animation1;
|
||||
bk.to = p_animation2;
|
||||
|
||||
if (blend_times.has(bk)) {
|
||||
return blend_times[bk];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_auto_capture(bool p_auto_capture) {
|
||||
auto_capture = p_auto_capture;
|
||||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
bool AnimationPlayer::is_auto_capture() const {
|
||||
return auto_capture;
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_auto_capture_duration(double p_auto_capture_duration) {
|
||||
auto_capture_duration = p_auto_capture_duration;
|
||||
}
|
||||
|
||||
double AnimationPlayer::get_auto_capture_duration() const {
|
||||
return auto_capture_duration;
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_auto_capture_transition_type(Tween::TransitionType p_auto_capture_transition_type) {
|
||||
auto_capture_transition_type = p_auto_capture_transition_type;
|
||||
}
|
||||
|
||||
Tween::TransitionType AnimationPlayer::get_auto_capture_transition_type() const {
|
||||
return auto_capture_transition_type;
|
||||
}
|
||||
|
||||
void AnimationPlayer::set_auto_capture_ease_type(Tween::EaseType p_auto_capture_ease_type) {
|
||||
auto_capture_ease_type = p_auto_capture_ease_type;
|
||||
}
|
||||
|
||||
Tween::EaseType AnimationPlayer::get_auto_capture_ease_type() const {
|
||||
return auto_capture_ease_type;
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
|
||||
const String pf = p_function;
|
||||
if (p_idx == 0 && (pf == "play" || pf == "play_backwards" || pf == "has_animation" || pf == "queue")) {
|
||||
List<StringName> al;
|
||||
get_animation_list(&al);
|
||||
for (const StringName &name : al) {
|
||||
r_options->push_back(String(name).quote());
|
||||
}
|
||||
}
|
||||
AnimationMixer::get_argument_options(p_function, p_idx, r_options);
|
||||
}
|
||||
#endif
|
||||
|
||||
void AnimationPlayer::_animation_removed(const StringName &p_name, const StringName &p_library) {
|
||||
AnimationMixer::_animation_removed(p_name, p_library);
|
||||
|
||||
StringName name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name));
|
||||
|
||||
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, double> &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();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPlayer::_rename_animation(const StringName &p_from_name, const StringName &p_to_name) {
|
||||
AnimationMixer::_rename_animation(p_from_name, p_to_name);
|
||||
|
||||
// Rename autoplay or blends if needed.
|
||||
List<BlendKey> to_erase;
|
||||
HashMap<BlendKey, double, BlendKey> to_insert;
|
||||
for (const KeyValue<BlendKey, double> &E : blend_times) {
|
||||
BlendKey bk = E.key;
|
||||
BlendKey new_bk = bk;
|
||||
bool erase = false;
|
||||
if (bk.from == p_from_name) {
|
||||
new_bk.from = p_to_name;
|
||||
erase = true;
|
||||
}
|
||||
if (bk.to == p_from_name) {
|
||||
new_bk.to = p_to_name;
|
||||
erase = true;
|
||||
}
|
||||
|
||||
if (erase) {
|
||||
to_erase.push_back(bk);
|
||||
to_insert[new_bk] = E.value;
|
||||
}
|
||||
}
|
||||
|
||||
while (to_erase.size()) {
|
||||
blend_times.erase(to_erase.front()->get());
|
||||
to_erase.pop_front();
|
||||
}
|
||||
|
||||
while (to_insert.size()) {
|
||||
blend_times[to_insert.begin()->key] = to_insert.begin()->value;
|
||||
to_insert.remove(to_insert.begin());
|
||||
}
|
||||
|
||||
if (autoplay == p_from_name) {
|
||||
autoplay = p_to_name;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPlayer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("animation_set_next", "animation_from", "animation_to"), &AnimationPlayer::animation_set_next);
|
||||
ClassDB::bind_method(D_METHOD("animation_get_next", "animation_from"), &AnimationPlayer::animation_get_next);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_blend_time", "animation_from", "animation_to", "sec"), &AnimationPlayer::set_blend_time);
|
||||
ClassDB::bind_method(D_METHOD("get_blend_time", "animation_from", "animation_to"), &AnimationPlayer::get_blend_time);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_default_blend_time", "sec"), &AnimationPlayer::set_default_blend_time);
|
||||
ClassDB::bind_method(D_METHOD("get_default_blend_time"), &AnimationPlayer::get_default_blend_time);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_auto_capture", "auto_capture"), &AnimationPlayer::set_auto_capture);
|
||||
ClassDB::bind_method(D_METHOD("is_auto_capture"), &AnimationPlayer::is_auto_capture);
|
||||
ClassDB::bind_method(D_METHOD("set_auto_capture_duration", "auto_capture_duration"), &AnimationPlayer::set_auto_capture_duration);
|
||||
ClassDB::bind_method(D_METHOD("get_auto_capture_duration"), &AnimationPlayer::get_auto_capture_duration);
|
||||
ClassDB::bind_method(D_METHOD("set_auto_capture_transition_type", "auto_capture_transition_type"), &AnimationPlayer::set_auto_capture_transition_type);
|
||||
ClassDB::bind_method(D_METHOD("get_auto_capture_transition_type"), &AnimationPlayer::get_auto_capture_transition_type);
|
||||
ClassDB::bind_method(D_METHOD("set_auto_capture_ease_type", "auto_capture_ease_type"), &AnimationPlayer::set_auto_capture_ease_type);
|
||||
ClassDB::bind_method(D_METHOD("get_auto_capture_ease_type"), &AnimationPlayer::get_auto_capture_ease_type);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play, DEFVAL(StringName()), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::play_backwards, DEFVAL(StringName()), DEFVAL(-1));
|
||||
ClassDB::bind_method(D_METHOD("play_with_capture", "name", "duration", "custom_blend", "custom_speed", "from_end", "trans_type", "ease_type"), &AnimationPlayer::play_with_capture, DEFVAL(StringName()), DEFVAL(-1.0), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false), DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN));
|
||||
ClassDB::bind_method(D_METHOD("pause"), &AnimationPlayer::pause);
|
||||
ClassDB::bind_method(D_METHOD("stop", "keep_state"), &AnimationPlayer::stop, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("is_playing"), &AnimationPlayer::is_playing);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_current_animation", "animation"), &AnimationPlayer::set_current_animation);
|
||||
ClassDB::bind_method(D_METHOD("get_current_animation"), &AnimationPlayer::get_current_animation);
|
||||
ClassDB::bind_method(D_METHOD("set_assigned_animation", "animation"), &AnimationPlayer::set_assigned_animation);
|
||||
ClassDB::bind_method(D_METHOD("get_assigned_animation"), &AnimationPlayer::get_assigned_animation);
|
||||
ClassDB::bind_method(D_METHOD("queue", "name"), &AnimationPlayer::queue);
|
||||
ClassDB::bind_method(D_METHOD("get_queue"), &AnimationPlayer::get_queue);
|
||||
ClassDB::bind_method(D_METHOD("clear_queue"), &AnimationPlayer::clear_queue);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_speed_scale", "speed"), &AnimationPlayer::set_speed_scale);
|
||||
ClassDB::bind_method(D_METHOD("get_speed_scale"), &AnimationPlayer::get_speed_scale);
|
||||
ClassDB::bind_method(D_METHOD("get_playing_speed"), &AnimationPlayer::get_playing_speed);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_autoplay", "name"), &AnimationPlayer::set_autoplay);
|
||||
ClassDB::bind_method(D_METHOD("get_autoplay"), &AnimationPlayer::get_autoplay);
|
||||
|
||||
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("set_movie_quit_on_finish_enabled", "enabled"), &AnimationPlayer::set_movie_quit_on_finish_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_movie_quit_on_finish_enabled"), &AnimationPlayer::is_movie_quit_on_finish_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_current_animation_position"), &AnimationPlayer::get_current_animation_position);
|
||||
ClassDB::bind_method(D_METHOD("get_current_animation_length"), &AnimationPlayer::get_current_animation_length);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("seek", "seconds", "update", "update_only"), &AnimationPlayer::seek, DEFVAL(false), DEFVAL(false));
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "current_animation", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_EDITOR), "set_current_animation", "get_current_animation");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "assigned_animation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_assigned_animation", "get_assigned_animation");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "autoplay", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_autoplay", "get_autoplay");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "current_animation_length", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_current_animation_length");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "current_animation_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_current_animation_position");
|
||||
|
||||
ADD_GROUP("Playback Options", "playback_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_auto_capture"), "set_auto_capture", "is_auto_capture");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_auto_capture_duration", PROPERTY_HINT_NONE, "suffix:s"), "set_auto_capture_duration", "get_auto_capture_duration");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_auto_capture_transition_type", PROPERTY_HINT_ENUM, "Linear,Sine,Quint,Quart,Expo,Elastic,Cubic,Circ,Bounce,Back,Spring"), "set_auto_capture_transition_type", "get_auto_capture_transition_type");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_auto_capture_ease_type", PROPERTY_HINT_ENUM, "In,Out,InOut,OutIn"), "set_auto_capture_ease_type", "get_auto_capture_ease_type");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_default_blend_time", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:s"), "set_default_blend_time", "get_default_blend_time");
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-4,4,0.001,or_less,or_greater"), "set_speed_scale", "get_speed_scale");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "movie_quit_on_finish"), "set_movie_quit_on_finish_enabled", "is_movie_quit_on_finish_enabled");
|
||||
|
||||
ADD_SIGNAL(MethodInfo(SNAME("current_animation_changed"), PropertyInfo(Variant::STRING, "name")));
|
||||
ADD_SIGNAL(MethodInfo(SNAME("animation_changed"), PropertyInfo(Variant::STRING_NAME, "old_name"), PropertyInfo(Variant::STRING_NAME, "new_name")));
|
||||
}
|
||||
|
||||
AnimationPlayer::AnimationPlayer() {
|
||||
}
|
||||
|
||||
AnimationPlayer::~AnimationPlayer() {
|
||||
}
|
||||
225
engine/scene/animation/animation_player.h
Normal file
225
engine/scene/animation/animation_player.h
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
/**************************************************************************/
|
||||
/* animation_player.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANIMATION_PLAYER_H
|
||||
#define ANIMATION_PLAYER_H
|
||||
|
||||
#include "animation_mixer.h"
|
||||
#include "scene/2d/node_2d.h"
|
||||
#include "scene/resources/animation.h"
|
||||
|
||||
class AnimationPlayer : public AnimationMixer {
|
||||
GDCLASS(AnimationPlayer, AnimationMixer);
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
public:
|
||||
enum AnimationProcessCallback {
|
||||
ANIMATION_PROCESS_PHYSICS,
|
||||
ANIMATION_PROCESS_IDLE,
|
||||
ANIMATION_PROCESS_MANUAL,
|
||||
};
|
||||
enum AnimationMethodCallMode {
|
||||
ANIMATION_METHOD_CALL_DEFERRED,
|
||||
ANIMATION_METHOD_CALL_IMMEDIATE,
|
||||
};
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
private:
|
||||
HashMap<StringName, StringName> animation_next_set; // For auto advance.
|
||||
|
||||
float speed_scale = 1.0;
|
||||
double default_blend_time = 0.0;
|
||||
|
||||
bool auto_capture = true;
|
||||
double auto_capture_duration = -1.0;
|
||||
Tween::TransitionType auto_capture_transition_type = Tween::TRANS_LINEAR;
|
||||
Tween::EaseType auto_capture_ease_type = Tween::EASE_IN;
|
||||
|
||||
bool is_stopping = false;
|
||||
|
||||
struct PlaybackData {
|
||||
AnimationData *from = nullptr;
|
||||
double pos = 0.0;
|
||||
float speed_scale = 1.0;
|
||||
};
|
||||
|
||||
struct Blend {
|
||||
PlaybackData data;
|
||||
double blend_time = 0.0;
|
||||
double blend_left = 0.0;
|
||||
};
|
||||
|
||||
struct Playback {
|
||||
PlaybackData current;
|
||||
StringName assigned;
|
||||
bool seeked = false;
|
||||
bool internal_seeked = false;
|
||||
bool started = false;
|
||||
List<Blend> blend;
|
||||
} playback;
|
||||
|
||||
struct BlendKey {
|
||||
StringName from;
|
||||
StringName to;
|
||||
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 StringName::AlphCompare()(to, bk.to);
|
||||
} else {
|
||||
return StringName::AlphCompare()(from, bk.from);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
HashMap<BlendKey, double, BlendKey> blend_times;
|
||||
|
||||
List<StringName> playback_queue;
|
||||
ObjectID tmp_from;
|
||||
bool end_reached = false;
|
||||
bool end_notify = false;
|
||||
|
||||
StringName autoplay;
|
||||
|
||||
bool reset_on_save = true;
|
||||
bool movie_quit_on_finish = false;
|
||||
|
||||
void _play(const StringName &p_name, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false);
|
||||
void _capture(const StringName &p_name, bool p_from_end = false, double p_duration = -1.0, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN);
|
||||
void _process_playback_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_internal_seeked, bool p_started, bool p_is_current = false);
|
||||
void _blend_playback_data(double p_delta, bool p_started);
|
||||
void _stop_internal(bool p_reset, bool p_keep_state);
|
||||
void _check_immediately_after_start();
|
||||
|
||||
float get_current_blend_amount();
|
||||
|
||||
bool playing = false;
|
||||
|
||||
protected:
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
virtual void _validate_property(PropertyInfo &p_property) const override;
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
void _notification(int p_what);
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
// Make animation instances.
|
||||
virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) override;
|
||||
virtual void _blend_capture(double p_delta) override;
|
||||
virtual void _blend_post_process() override;
|
||||
|
||||
virtual void _animation_removed(const StringName &p_name, const StringName &p_library) override;
|
||||
virtual void _rename_animation(const StringName &p_from_name, const StringName &p_to_name) override;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void _set_process_callback_bind_compat_80813(AnimationProcessCallback p_mode);
|
||||
AnimationProcessCallback _get_process_callback_bind_compat_80813() const;
|
||||
void _set_method_call_mode_bind_compat_80813(AnimationMethodCallMode p_mode);
|
||||
AnimationMethodCallMode _get_method_call_mode_bind_compat_80813() const;
|
||||
void _set_root_bind_compat_80813(const NodePath &p_root);
|
||||
NodePath _get_root_bind_compat_80813() const;
|
||||
void _seek_bind_compat_80813(double p_time, bool p_update = false);
|
||||
void _play_compat_84906(const StringName &p_name = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false);
|
||||
void _play_backwards_compat_84906(const StringName &p_name = StringName(), double p_custom_blend = -1);
|
||||
|
||||
static void _bind_compatibility_methods();
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
public:
|
||||
void animation_set_next(const StringName &p_animation, const StringName &p_next);
|
||||
StringName animation_get_next(const StringName &p_animation) const;
|
||||
|
||||
void set_blend_time(const StringName &p_animation1, const StringName &p_animation2, double p_time);
|
||||
double get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const;
|
||||
|
||||
void set_default_blend_time(double p_default);
|
||||
double get_default_blend_time() const;
|
||||
|
||||
void set_auto_capture(bool p_auto_capture);
|
||||
bool is_auto_capture() const;
|
||||
void set_auto_capture_duration(double p_auto_capture_duration);
|
||||
double get_auto_capture_duration() const;
|
||||
void set_auto_capture_transition_type(Tween::TransitionType p_auto_capture_transition_type);
|
||||
Tween::TransitionType get_auto_capture_transition_type() const;
|
||||
void set_auto_capture_ease_type(Tween::EaseType p_auto_capture_ease_type);
|
||||
Tween::EaseType get_auto_capture_ease_type() const;
|
||||
|
||||
void play(const StringName &p_name = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false);
|
||||
void play_backwards(const StringName &p_name = StringName(), double p_custom_blend = -1);
|
||||
void play_with_capture(const StringName &p_name = StringName(), double p_duration = -1.0, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN);
|
||||
void queue(const StringName &p_name);
|
||||
Vector<String> get_queue();
|
||||
void clear_queue();
|
||||
void pause();
|
||||
void stop(bool p_keep_state = false);
|
||||
bool is_playing() const;
|
||||
String get_current_animation() const;
|
||||
void set_current_animation(const String &p_animation);
|
||||
String get_assigned_animation() const;
|
||||
void set_assigned_animation(const String &p_animation);
|
||||
bool is_valid() const;
|
||||
|
||||
void set_speed_scale(float p_speed);
|
||||
float get_speed_scale() const;
|
||||
float get_playing_speed() const;
|
||||
|
||||
void set_autoplay(const String &p_name);
|
||||
String get_autoplay() const;
|
||||
|
||||
void set_movie_quit_on_finish_enabled(bool p_enabled);
|
||||
bool is_movie_quit_on_finish_enabled() const;
|
||||
|
||||
void seek_internal(double p_time, bool p_update = false, bool p_update_only = false, bool p_is_internal_seek = false);
|
||||
void seek(double p_time, bool p_update = false, bool p_update_only = false);
|
||||
|
||||
double get_current_animation_position() const;
|
||||
double get_current_animation_length() const;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
|
||||
#endif
|
||||
|
||||
virtual void advance(double p_time) override;
|
||||
|
||||
AnimationPlayer();
|
||||
~AnimationPlayer();
|
||||
};
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
VARIANT_ENUM_CAST(AnimationPlayer::AnimationProcessCallback);
|
||||
VARIANT_ENUM_CAST(AnimationPlayer::AnimationMethodCallMode);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
#endif // ANIMATION_PLAYER_H
|
||||
64
engine/scene/animation/animation_tree.compat.inc
Normal file
64
engine/scene/animation/animation_tree.compat.inc
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/**************************************************************************/
|
||||
/* animation_tree.compat.inc */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
||||
void AnimationTree::_set_process_callback_bind_compat_80813(AnimationTree::AnimationProcessCallback p_mode) {
|
||||
set_callback_mode_process(static_cast<AnimationMixer::AnimationCallbackModeProcess>(static_cast<int>(p_mode)));
|
||||
}
|
||||
|
||||
AnimationTree::AnimationProcessCallback AnimationTree::_get_process_callback_bind_compat_80813() const {
|
||||
return static_cast<AnimationProcessCallback>(static_cast<int>(get_callback_mode_process()));
|
||||
}
|
||||
|
||||
void AnimationTree::_set_tree_root_bind_compat_80813(const Ref<AnimationNode> &p_root) {
|
||||
const Ref<AnimationRootNode> rn = Ref<AnimationRootNode>(p_root.ptr());
|
||||
if (rn.is_null()) {
|
||||
return;
|
||||
}
|
||||
return (set_root_animation_node(rn));
|
||||
}
|
||||
|
||||
Ref<AnimationNode> AnimationTree::_get_tree_root_bind_compat_80813() const {
|
||||
const Ref<AnimationRootNode> rn = Ref<AnimationNode>(get_root_animation_node().ptr());
|
||||
return rn;
|
||||
}
|
||||
|
||||
void AnimationTree::_bind_compatibility_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &AnimationTree::_set_process_callback_bind_compat_80813);
|
||||
ClassDB::bind_method(D_METHOD("get_process_callback"), &AnimationTree::_get_process_callback_bind_compat_80813);
|
||||
ClassDB::bind_compatibility_method(D_METHOD("set_tree_root", "root"), &AnimationTree::_set_tree_root_bind_compat_80813);
|
||||
ClassDB::bind_compatibility_method(D_METHOD("get_tree_root"), &AnimationTree::_get_tree_root_bind_compat_80813);
|
||||
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS);
|
||||
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE);
|
||||
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL);
|
||||
}
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
||||
972
engine/scene/animation/animation_tree.cpp
Normal file
972
engine/scene/animation/animation_tree.cpp
Normal file
|
|
@ -0,0 +1,972 @@
|
|||
/**************************************************************************/
|
||||
/* 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"
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
r_list->push_back(PropertyInfo(Variant::FLOAT, current_length, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_READ_ONLY));
|
||||
r_list->push_back(PropertyInfo(Variant::FLOAT, current_position, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_READ_ONLY));
|
||||
r_list->push_back(PropertyInfo(Variant::FLOAT, current_delta, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_READ_ONLY));
|
||||
}
|
||||
|
||||
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;
|
||||
if (GDVIRTUAL_CALL(_is_parameter_read_only, p_parameter, ret) && ret) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_parameter == current_length || p_parameter == current_position || p_parameter == current_delta) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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::set_node_time_info(const NodeTimeInfo &p_node_time_info) {
|
||||
set_parameter(current_length, p_node_time_info.length);
|
||||
set_parameter(current_position, p_node_time_info.position);
|
||||
set_parameter(current_delta, p_node_time_info.delta);
|
||||
}
|
||||
|
||||
AnimationNode::NodeTimeInfo AnimationNode::get_node_time_info() const {
|
||||
NodeTimeInfo nti;
|
||||
nti.length = get_parameter(current_length);
|
||||
nti.position = get_parameter(current_position);
|
||||
nti.delta = get_parameter(current_delta);
|
||||
return nti;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
AnimationNode::NodeTimeInfo AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
||||
process_state = p_process_state;
|
||||
NodeTimeInfo nti = process(p_playback_info, p_test_only);
|
||||
process_state = nullptr;
|
||||
return nti;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
AnimationNode::NodeTimeInfo 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(), NodeTimeInfo());
|
||||
|
||||
AnimationNodeBlendTree *blend_tree = Object::cast_to<AnimationNodeBlendTree>(node_state.parent);
|
||||
ERR_FAIL_NULL_V(blend_tree, NodeTimeInfo());
|
||||
|
||||
// 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 NodeTimeInfo();
|
||||
}
|
||||
|
||||
Ref<AnimationNode> node = blend_tree->get_node(node_name);
|
||||
ERR_FAIL_COND_V(node.is_null(), NodeTimeInfo());
|
||||
|
||||
real_t activity = 0.0;
|
||||
Vector<AnimationTree::Activity> *activity_ptr = process_state->tree->input_activity_map.getptr(node_state.base_path);
|
||||
NodeTimeInfo nti = _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 nti;
|
||||
}
|
||||
|
||||
AnimationNode::NodeTimeInfo 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) {
|
||||
ERR_FAIL_COND_V(p_node.is_null(), NodeTimeInfo());
|
||||
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);
|
||||
}
|
||||
|
||||
AnimationNode::NodeTimeInfo 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_NULL_V(process_state, NodeTimeInfo());
|
||||
|
||||
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, NodeTimeInfo());
|
||||
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.delta = 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;
|
||||
}
|
||||
|
||||
AnimationNode::NodeTimeInfo AnimationNode::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
||||
process_state->is_testing = p_test_only;
|
||||
|
||||
AnimationMixer::PlaybackInfo pi = p_playback_info;
|
||||
if (p_playback_info.seeked) {
|
||||
pi.delta = get_node_time_info().position - p_playback_info.time;
|
||||
} else {
|
||||
pi.time = get_node_time_info().position + p_playback_info.delta;
|
||||
}
|
||||
|
||||
NodeTimeInfo nti = _process(pi, p_test_only);
|
||||
|
||||
if (!p_test_only) {
|
||||
set_node_time_info(nti);
|
||||
}
|
||||
|
||||
return nti;
|
||||
}
|
||||
|
||||
AnimationNode::NodeTimeInfo AnimationNode::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
|
||||
double r_ret = 0.0;
|
||||
GDVIRTUAL_CALL(_process, p_playback_info.time, p_playback_info.seeked, p_playback_info.is_external_seeking, p_test_only, r_ret);
|
||||
NodeTimeInfo nti;
|
||||
nti.delta = r_ret;
|
||||
return nti;
|
||||
}
|
||||
|
||||
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_deletable(bool p_closable) {
|
||||
closable = p_closable;
|
||||
}
|
||||
|
||||
bool AnimationNode::is_deletable() 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;
|
||||
NodeTimeInfo nti = blend_node(p_node, p_sub_path, info, p_filter, p_sync, p_test_only);
|
||||
return nti.length - nti.position;
|
||||
}
|
||||
|
||||
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;
|
||||
NodeTimeInfo nti = blend_input(p_input, info, p_filter, p_sync, p_test_only);
|
||||
return nti.length - nti.position;
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void AnimationNode::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
|
||||
const String pf = p_function;
|
||||
if (p_idx == 0) {
|
||||
if (pf == "find_input") {
|
||||
for (const AnimationNode::Input &E : inputs) {
|
||||
r_options->push_back(E.name.quote());
|
||||
}
|
||||
} else if (pf == "get_parameter" || pf == "set_parameter") {
|
||||
bool is_setter = pf == "set_parameter";
|
||||
List<PropertyInfo> parameters;
|
||||
get_parameter_list(¶meters);
|
||||
for (const PropertyInfo &E : parameters) {
|
||||
if (is_setter && is_parameter_read_only(E.name)) {
|
||||
continue;
|
||||
}
|
||||
r_options->push_back(E.name.quote());
|
||||
}
|
||||
} else if (pf == "set_filter_path" || pf == "is_path_filtered") {
|
||||
for (const KeyValue<NodePath, bool> &E : filter) {
|
||||
r_options->push_back(String(E.key).quote());
|
||||
}
|
||||
}
|
||||
}
|
||||
Resource::get_argument_options(p_function, p_idx, r_options);
|
||||
}
|
||||
#endif
|
||||
|
||||
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 = SNAME(Animation::PARAMETERS_BASE_PATH.ascii().get_data());
|
||||
root_animation_node->node_state.parent = nullptr;
|
||||
}
|
||||
|
||||
// Process.
|
||||
{
|
||||
PlaybackInfo pi;
|
||||
|
||||
if (started) {
|
||||
// If started, seek.
|
||||
pi.seeked = true;
|
||||
pi.delta = p_delta;
|
||||
root_animation_node->_pre_process(&process_state, pi, false);
|
||||
started = false;
|
||||
} else {
|
||||
pi.seeked = false;
|
||||
pi.delta = 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;
|
||||
}
|
||||
|
||||
PackedStringArray AnimationTree::get_configuration_warnings() const {
|
||||
PackedStringArray 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(Animation::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(SceneStringName(path_pp));
|
||||
while (animation_libraries.size()) {
|
||||
remove_animation_library(animation_libraries[0].name);
|
||||
}
|
||||
}
|
||||
emit_signal(SNAME("animation_player_changed")); // Needs to unpin AnimationPlayerEditor.
|
||||
_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 (const StringName &E : list) {
|
||||
Ref<AnimationLibrary> lib = player->get_animation_library(E);
|
||||
if (lib.is_valid()) {
|
||||
add_animation_library(E, 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");
|
||||
|
||||
ADD_SIGNAL(MethodInfo(SNAME("animation_player_changed")));
|
||||
}
|
||||
|
||||
AnimationTree::AnimationTree() {
|
||||
deterministic = true;
|
||||
callback_mode_discrete = ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS;
|
||||
}
|
||||
|
||||
AnimationTree::~AnimationTree() {
|
||||
}
|
||||
327
engine/scene/animation/animation_tree.h
Normal file
327
engine/scene/animation/animation_tree.h
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
/**************************************************************************/
|
||||
/* animation_tree.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANIMATION_TREE_H
|
||||
#define ANIMATION_TREE_H
|
||||
|
||||
#include "animation_mixer.h"
|
||||
#include "scene/resources/animation.h"
|
||||
|
||||
#define HUGE_LENGTH 31540000 // 31540000 seconds mean 1 year... is it too long? It must be longer than any Animation length and Transition xfade time to prevent time inversion for AnimationNodeStateMachine.
|
||||
|
||||
class AnimationNodeBlendTree;
|
||||
class AnimationNodeStartState;
|
||||
class AnimationNodeEndState;
|
||||
class AnimationTree;
|
||||
|
||||
class AnimationNode : public Resource {
|
||||
GDCLASS(AnimationNode, Resource);
|
||||
|
||||
public:
|
||||
friend class AnimationTree;
|
||||
|
||||
enum FilterAction {
|
||||
FILTER_IGNORE,
|
||||
FILTER_PASS,
|
||||
FILTER_STOP,
|
||||
FILTER_BLEND
|
||||
};
|
||||
|
||||
struct Input {
|
||||
String name;
|
||||
};
|
||||
|
||||
bool closable = false;
|
||||
Vector<Input> inputs;
|
||||
HashMap<NodePath, bool> filter;
|
||||
bool filter_enabled = false;
|
||||
|
||||
// To propagate information from upstream for use in estimation of playback progress.
|
||||
// These values must be taken from the result of blend_node() or blend_input() and must be essentially read-only.
|
||||
// For example, if you want to change the position, you need to change the pi.time value of PlaybackInfo passed to blend_input(pi) and get the result.
|
||||
struct NodeTimeInfo {
|
||||
// Retain the previous frame values. These are stored into the AnimationTree's Map and exposing them as read-only values.
|
||||
double length = 0.0;
|
||||
double position = 0.0;
|
||||
double delta = 0.0;
|
||||
|
||||
// Needs internally to estimate remain time, the previous frame values are not retained.
|
||||
Animation::LoopMode loop_mode = Animation::LOOP_NONE;
|
||||
bool will_end = false; // For breaking loop, it is true when just looped.
|
||||
bool is_infinity = false; // For unpredictable state machine's end.
|
||||
|
||||
bool is_looping() {
|
||||
return loop_mode != Animation::LOOP_NONE;
|
||||
}
|
||||
double get_remain(bool p_break_loop = false) {
|
||||
if ((is_looping() && !p_break_loop) || is_infinity) {
|
||||
return HUGE_LENGTH;
|
||||
}
|
||||
if (is_looping() && p_break_loop && will_end) {
|
||||
return 0;
|
||||
}
|
||||
double remain = length - position;
|
||||
if (Math::is_zero_approx(remain)) {
|
||||
return 0;
|
||||
}
|
||||
return length - position;
|
||||
}
|
||||
};
|
||||
|
||||
// Temporary state for blending process which needs to be stored in each AnimationNodes.
|
||||
struct NodeState {
|
||||
StringName base_path;
|
||||
AnimationNode *parent = nullptr;
|
||||
Vector<StringName> connections;
|
||||
Vector<real_t> track_weights;
|
||||
} node_state;
|
||||
|
||||
// Temporary state for blending process which needs to be started in the AnimationTree, pass through the AnimationNodes, and then return to the AnimationTree.
|
||||
struct ProcessState {
|
||||
AnimationTree *tree = nullptr;
|
||||
HashMap<NodePath, int> track_map; // TODO: Is there a better way to manage filter/tracks?
|
||||
bool is_testing = false;
|
||||
bool valid = false;
|
||||
String invalid_reasons;
|
||||
uint64_t last_pass = 0;
|
||||
} *process_state = nullptr;
|
||||
|
||||
Array _get_filters() const;
|
||||
void _set_filters(const Array &p_filters);
|
||||
friend class AnimationNodeBlendTree;
|
||||
|
||||
// The time information is passed from upstream to downstream by AnimationMixer::PlaybackInfo::p_playback_info until AnimationNodeAnimation processes it.
|
||||
// Conversely, AnimationNodeAnimation returns the processed result as NodeTimeInfo from downstream to upstream.
|
||||
NodeTimeInfo _blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr);
|
||||
NodeTimeInfo _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false);
|
||||
|
||||
protected:
|
||||
StringName current_length = "current_length";
|
||||
StringName current_position = "current_position";
|
||||
StringName current_delta = "current_delta";
|
||||
|
||||
virtual NodeTimeInfo process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); // To organize time information. Virtualizing for especially AnimationNodeAnimation needs to take "backward" into account.
|
||||
virtual NodeTimeInfo _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); // Main process.
|
||||
|
||||
void blend_animation(const StringName &p_animation, AnimationMixer::PlaybackInfo p_playback_info);
|
||||
NodeTimeInfo blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
|
||||
NodeTimeInfo blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
|
||||
|
||||
// Bind-able methods to expose for compatibility, moreover AnimationMixer::PlaybackInfo is not exposed.
|
||||
void 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 = Animation::LOOPED_FLAG_NONE);
|
||||
double 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 = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
|
||||
double blend_input_ex(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
|
||||
|
||||
void make_invalid(const String &p_reason);
|
||||
AnimationTree *get_animation_tree() const;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
||||
GDVIRTUAL0RC(Dictionary, _get_child_nodes)
|
||||
GDVIRTUAL0RC(Array, _get_parameter_list)
|
||||
GDVIRTUAL1RC(Ref<AnimationNode>, _get_child_by_name, StringName)
|
||||
GDVIRTUAL1RC(Variant, _get_parameter_default_value, StringName)
|
||||
GDVIRTUAL1RC(bool, _is_parameter_read_only, StringName)
|
||||
GDVIRTUAL4RC(double, _process, double, bool, bool, bool)
|
||||
GDVIRTUAL0RC(String, _get_caption)
|
||||
GDVIRTUAL0RC(bool, _has_filter)
|
||||
|
||||
public:
|
||||
virtual void get_parameter_list(List<PropertyInfo> *r_list) const;
|
||||
virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
|
||||
virtual bool is_parameter_read_only(const StringName &p_parameter) const;
|
||||
|
||||
void set_parameter(const StringName &p_name, const Variant &p_value);
|
||||
Variant get_parameter(const StringName &p_name) const;
|
||||
|
||||
void set_node_time_info(const NodeTimeInfo &p_node_time_info); // Wrapper of set_parameter().
|
||||
virtual NodeTimeInfo get_node_time_info() const; // Wrapper of get_parameter().
|
||||
|
||||
struct ChildNode {
|
||||
StringName name;
|
||||
Ref<AnimationNode> node;
|
||||
};
|
||||
|
||||
virtual void get_child_nodes(List<ChildNode> *r_child_nodes);
|
||||
|
||||
virtual String get_caption() const;
|
||||
|
||||
virtual bool add_input(const String &p_name);
|
||||
virtual void remove_input(int p_index);
|
||||
virtual bool set_input_name(int p_input, const String &p_name);
|
||||
virtual String get_input_name(int p_input) const;
|
||||
int get_input_count() const;
|
||||
int find_input(const String &p_name) const;
|
||||
|
||||
void set_filter_path(const NodePath &p_path, bool p_enable);
|
||||
bool is_path_filtered(const NodePath &p_path) const;
|
||||
|
||||
void set_filter_enabled(bool p_enable);
|
||||
bool is_filter_enabled() const;
|
||||
|
||||
void set_deletable(bool p_closable);
|
||||
bool is_deletable() const;
|
||||
|
||||
virtual bool has_filter() const;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
|
||||
#endif
|
||||
|
||||
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const;
|
||||
Ref<AnimationNode> find_node_by_path(const String &p_name) const;
|
||||
|
||||
AnimationNode();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AnimationNode::FilterAction)
|
||||
|
||||
// Root node does not allow inputs.
|
||||
class AnimationRootNode : public AnimationNode {
|
||||
GDCLASS(AnimationRootNode, AnimationNode);
|
||||
|
||||
protected:
|
||||
virtual void _tree_changed();
|
||||
virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name);
|
||||
virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node);
|
||||
|
||||
public:
|
||||
AnimationRootNode() {}
|
||||
};
|
||||
|
||||
class AnimationNodeStartState : public AnimationRootNode {
|
||||
GDCLASS(AnimationNodeStartState, AnimationRootNode);
|
||||
};
|
||||
|
||||
class AnimationNodeEndState : public AnimationRootNode {
|
||||
GDCLASS(AnimationNodeEndState, AnimationRootNode);
|
||||
};
|
||||
|
||||
class AnimationTree : public AnimationMixer {
|
||||
GDCLASS(AnimationTree, AnimationMixer);
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
public:
|
||||
enum AnimationProcessCallback {
|
||||
ANIMATION_PROCESS_PHYSICS,
|
||||
ANIMATION_PROCESS_IDLE,
|
||||
ANIMATION_PROCESS_MANUAL,
|
||||
};
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
private:
|
||||
Ref<AnimationRootNode> root_animation_node;
|
||||
NodePath advance_expression_base_node = NodePath(String("."));
|
||||
|
||||
AnimationNode::ProcessState process_state;
|
||||
uint64_t process_pass = 1;
|
||||
|
||||
bool started = true;
|
||||
|
||||
friend class AnimationNode;
|
||||
|
||||
List<PropertyInfo> properties;
|
||||
HashMap<StringName, HashMap<StringName, StringName>> property_parent_map;
|
||||
HashMap<ObjectID, StringName> property_reference_map;
|
||||
HashMap<StringName, Pair<Variant, bool>> property_map; // Property value and read-only flag.
|
||||
|
||||
bool properties_dirty = true;
|
||||
|
||||
void _update_properties();
|
||||
void _update_properties_for_node(const String &p_base_path, Ref<AnimationNode> p_node);
|
||||
|
||||
void _tree_changed();
|
||||
void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name);
|
||||
void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node);
|
||||
|
||||
struct Activity {
|
||||
uint64_t last_pass = 0;
|
||||
real_t activity = 0.0;
|
||||
};
|
||||
HashMap<StringName, Vector<Activity>> input_activity_map;
|
||||
HashMap<StringName, Vector<Activity> *> input_activity_map_get;
|
||||
|
||||
NodePath animation_player;
|
||||
|
||||
void _setup_animation_player();
|
||||
void _animation_player_changed();
|
||||
|
||||
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;
|
||||
virtual void _validate_property(PropertyInfo &p_property) const override;
|
||||
void _notification(int p_what);
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
virtual void _set_active(bool p_active) override;
|
||||
|
||||
// Make animation instances.
|
||||
virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) override;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void _set_process_callback_bind_compat_80813(AnimationProcessCallback p_mode);
|
||||
AnimationProcessCallback _get_process_callback_bind_compat_80813() const;
|
||||
void _set_tree_root_bind_compat_80813(const Ref<AnimationNode> &p_root);
|
||||
Ref<AnimationNode> _get_tree_root_bind_compat_80813() const;
|
||||
|
||||
static void _bind_compatibility_methods();
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
public:
|
||||
void set_animation_player(const NodePath &p_path);
|
||||
NodePath get_animation_player() const;
|
||||
|
||||
void set_root_animation_node(const Ref<AnimationRootNode> &p_animation_node);
|
||||
Ref<AnimationRootNode> get_root_animation_node() const;
|
||||
|
||||
void set_advance_expression_base_node(const NodePath &p_path);
|
||||
NodePath get_advance_expression_base_node() const;
|
||||
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
bool is_state_invalid() const;
|
||||
String get_invalid_state_reason() const;
|
||||
|
||||
real_t get_connection_activity(const StringName &p_path, int p_connection) const;
|
||||
|
||||
uint64_t get_last_process_pass() const;
|
||||
|
||||
AnimationTree();
|
||||
~AnimationTree();
|
||||
};
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
VARIANT_ENUM_CAST(AnimationTree::AnimationProcessCallback);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
#endif // ANIMATION_TREE_H
|
||||
445
engine/scene/animation/easing_equations.h
Normal file
445
engine/scene/animation/easing_equations.h
Normal file
|
|
@ -0,0 +1,445 @@
|
|||
/**************************************************************************/
|
||||
/* easing_equations.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef EASING_EQUATIONS_H
|
||||
#define EASING_EQUATIONS_H
|
||||
|
||||
/*
|
||||
* Derived from Robert Penner's easing equations: http://robertpenner.com/easing/
|
||||
*
|
||||
* Copyright (c) 2001 Robert Penner
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
namespace linear {
|
||||
static real_t in(real_t t, real_t b, real_t c, real_t d) {
|
||||
return c * t / d + b;
|
||||
}
|
||||
}; // namespace linear
|
||||
|
||||
namespace sine {
|
||||
static real_t in(real_t t, real_t b, real_t c, real_t d) {
|
||||
return -c * cos(t / d * (Math_PI / 2)) + c + b;
|
||||
}
|
||||
|
||||
static real_t out(real_t t, real_t b, real_t c, real_t d) {
|
||||
return c * sin(t / d * (Math_PI / 2)) + b;
|
||||
}
|
||||
|
||||
static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
|
||||
return -c / 2 * (cos(Math_PI * t / d) - 1) + b;
|
||||
}
|
||||
|
||||
static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t < d / 2) {
|
||||
return out(t * 2, b, c / 2, d);
|
||||
}
|
||||
real_t h = c / 2;
|
||||
return in(t * 2 - d, b + h, h, d);
|
||||
}
|
||||
}; // namespace sine
|
||||
|
||||
namespace quint {
|
||||
static real_t in(real_t t, real_t b, real_t c, real_t d) {
|
||||
return c * pow(t / d, 5) + b;
|
||||
}
|
||||
|
||||
static real_t out(real_t t, real_t b, real_t c, real_t d) {
|
||||
return c * (pow(t / d - 1, 5) + 1) + b;
|
||||
}
|
||||
|
||||
static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
|
||||
t = t / d * 2;
|
||||
|
||||
if (t < 1) {
|
||||
return c / 2 * pow(t, 5) + b;
|
||||
}
|
||||
return c / 2 * (pow(t - 2, 5) + 2) + b;
|
||||
}
|
||||
|
||||
static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t < d / 2) {
|
||||
return out(t * 2, b, c / 2, d);
|
||||
}
|
||||
real_t h = c / 2;
|
||||
return in(t * 2 - d, b + h, h, d);
|
||||
}
|
||||
}; // namespace quint
|
||||
|
||||
namespace quart {
|
||||
static real_t in(real_t t, real_t b, real_t c, real_t d) {
|
||||
return c * pow(t / d, 4) + b;
|
||||
}
|
||||
|
||||
static real_t out(real_t t, real_t b, real_t c, real_t d) {
|
||||
return -c * (pow(t / d - 1, 4) - 1) + b;
|
||||
}
|
||||
|
||||
static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
|
||||
t = t / d * 2;
|
||||
|
||||
if (t < 1) {
|
||||
return c / 2 * pow(t, 4) + b;
|
||||
}
|
||||
return -c / 2 * (pow(t - 2, 4) - 2) + b;
|
||||
}
|
||||
|
||||
static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t < d / 2) {
|
||||
return out(t * 2, b, c / 2, d);
|
||||
}
|
||||
real_t h = c / 2;
|
||||
return in(t * 2 - d, b + h, h, d);
|
||||
}
|
||||
}; // namespace quart
|
||||
|
||||
namespace quad {
|
||||
static real_t in(real_t t, real_t b, real_t c, real_t d) {
|
||||
return c * pow(t / d, 2) + b;
|
||||
}
|
||||
|
||||
static real_t out(real_t t, real_t b, real_t c, real_t d) {
|
||||
t /= d;
|
||||
return -c * t * (t - 2) + b;
|
||||
}
|
||||
|
||||
static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
|
||||
t = t / d * 2;
|
||||
|
||||
if (t < 1) {
|
||||
return c / 2 * pow(t, 2) + b;
|
||||
}
|
||||
return -c / 2 * ((t - 1) * (t - 3) - 1) + b;
|
||||
}
|
||||
|
||||
static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t < d / 2) {
|
||||
return out(t * 2, b, c / 2, d);
|
||||
}
|
||||
real_t h = c / 2;
|
||||
return in(t * 2 - d, b + h, h, d);
|
||||
}
|
||||
}; // namespace quad
|
||||
|
||||
namespace expo {
|
||||
static real_t in(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t == 0) {
|
||||
return b;
|
||||
}
|
||||
return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001;
|
||||
}
|
||||
|
||||
static real_t out(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t == d) {
|
||||
return b + c;
|
||||
}
|
||||
return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b;
|
||||
}
|
||||
|
||||
static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t == 0) {
|
||||
return b;
|
||||
}
|
||||
|
||||
if (t == d) {
|
||||
return b + c;
|
||||
}
|
||||
|
||||
t = t / d * 2;
|
||||
|
||||
if (t < 1) {
|
||||
return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005;
|
||||
}
|
||||
return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b;
|
||||
}
|
||||
|
||||
static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t < d / 2) {
|
||||
return out(t * 2, b, c / 2, d);
|
||||
}
|
||||
real_t h = c / 2;
|
||||
return in(t * 2 - d, b + h, h, d);
|
||||
}
|
||||
}; // namespace expo
|
||||
|
||||
namespace elastic {
|
||||
static real_t in(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t == 0) {
|
||||
return b;
|
||||
}
|
||||
|
||||
t /= d;
|
||||
if (t == 1) {
|
||||
return b + c;
|
||||
}
|
||||
|
||||
t -= 1;
|
||||
float p = d * 0.3f;
|
||||
float a = c * pow(2, 10 * t);
|
||||
float s = p / 4;
|
||||
|
||||
return -(a * sin((t * d - s) * (2 * Math_PI) / p)) + b;
|
||||
}
|
||||
|
||||
static real_t out(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t == 0) {
|
||||
return b;
|
||||
}
|
||||
|
||||
t /= d;
|
||||
if (t == 1) {
|
||||
return b + c;
|
||||
}
|
||||
|
||||
float p = d * 0.3f;
|
||||
float s = p / 4;
|
||||
|
||||
return (c * pow(2, -10 * t) * sin((t * d - s) * (2 * Math_PI) / p) + c + b);
|
||||
}
|
||||
|
||||
static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t == 0) {
|
||||
return b;
|
||||
}
|
||||
|
||||
if ((t /= d / 2) == 2) {
|
||||
return b + c;
|
||||
}
|
||||
|
||||
float p = d * (0.3f * 1.5f);
|
||||
float a = c;
|
||||
float s = p / 4;
|
||||
|
||||
if (t < 1) {
|
||||
t -= 1;
|
||||
a *= pow(2, 10 * t);
|
||||
return -0.5f * (a * sin((t * d - s) * (2 * Math_PI) / p)) + b;
|
||||
}
|
||||
|
||||
t -= 1;
|
||||
a *= pow(2, -10 * t);
|
||||
return a * sin((t * d - s) * (2 * Math_PI) / p) * 0.5f + c + b;
|
||||
}
|
||||
|
||||
static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t < d / 2) {
|
||||
return out(t * 2, b, c / 2, d);
|
||||
}
|
||||
real_t h = c / 2;
|
||||
return in(t * 2 - d, b + h, h, d);
|
||||
}
|
||||
}; // namespace elastic
|
||||
|
||||
namespace cubic {
|
||||
static real_t in(real_t t, real_t b, real_t c, real_t d) {
|
||||
t /= d;
|
||||
return c * t * t * t + b;
|
||||
}
|
||||
|
||||
static real_t out(real_t t, real_t b, real_t c, real_t d) {
|
||||
t = t / d - 1;
|
||||
return c * (t * t * t + 1) + b;
|
||||
}
|
||||
|
||||
static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
|
||||
t /= d / 2;
|
||||
if (t < 1) {
|
||||
return c / 2 * t * t * t + b;
|
||||
}
|
||||
|
||||
t -= 2;
|
||||
return c / 2 * (t * t * t + 2) + b;
|
||||
}
|
||||
|
||||
static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t < d / 2) {
|
||||
return out(t * 2, b, c / 2, d);
|
||||
}
|
||||
real_t h = c / 2;
|
||||
return in(t * 2 - d, b + h, h, d);
|
||||
}
|
||||
}; // namespace cubic
|
||||
|
||||
namespace circ {
|
||||
static real_t in(real_t t, real_t b, real_t c, real_t d) {
|
||||
t /= d;
|
||||
return -c * (sqrt(1 - t * t) - 1) + b;
|
||||
}
|
||||
|
||||
static real_t out(real_t t, real_t b, real_t c, real_t d) {
|
||||
t = t / d - 1;
|
||||
return c * sqrt(1 - t * t) + b;
|
||||
}
|
||||
|
||||
static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
|
||||
t /= d / 2;
|
||||
if (t < 1) {
|
||||
return -c / 2 * (sqrt(1 - t * t) - 1) + b;
|
||||
}
|
||||
|
||||
t -= 2;
|
||||
return c / 2 * (sqrt(1 - t * t) + 1) + b;
|
||||
}
|
||||
|
||||
static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t < d / 2) {
|
||||
return out(t * 2, b, c / 2, d);
|
||||
}
|
||||
real_t h = c / 2;
|
||||
return in(t * 2 - d, b + h, h, d);
|
||||
}
|
||||
}; // namespace circ
|
||||
|
||||
namespace bounce {
|
||||
static real_t out(real_t t, real_t b, real_t c, real_t d) {
|
||||
t /= d;
|
||||
|
||||
if (t < (1 / 2.75f)) {
|
||||
return c * (7.5625f * t * t) + b;
|
||||
}
|
||||
|
||||
if (t < (2 / 2.75f)) {
|
||||
t -= 1.5f / 2.75f;
|
||||
return c * (7.5625f * t * t + 0.75f) + b;
|
||||
}
|
||||
|
||||
if (t < (2.5 / 2.75)) {
|
||||
t -= 2.25f / 2.75f;
|
||||
return c * (7.5625f * t * t + 0.9375f) + b;
|
||||
}
|
||||
|
||||
t -= 2.625f / 2.75f;
|
||||
return c * (7.5625f * t * t + 0.984375f) + b;
|
||||
}
|
||||
|
||||
static real_t in(real_t t, real_t b, real_t c, real_t d) {
|
||||
return c - out(d - t, 0, c, d) + b;
|
||||
}
|
||||
|
||||
static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t < d / 2) {
|
||||
return in(t * 2, b, c / 2, d);
|
||||
}
|
||||
real_t h = c / 2;
|
||||
return out(t * 2 - d, b + h, h, d);
|
||||
}
|
||||
|
||||
static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t < d / 2) {
|
||||
return out(t * 2, b, c / 2, d);
|
||||
}
|
||||
real_t h = c / 2;
|
||||
return in(t * 2 - d, b + h, h, d);
|
||||
}
|
||||
}; // namespace bounce
|
||||
|
||||
namespace back {
|
||||
static real_t in(real_t t, real_t b, real_t c, real_t d) {
|
||||
float s = 1.70158f;
|
||||
t /= d;
|
||||
|
||||
return c * t * t * ((s + 1) * t - s) + b;
|
||||
}
|
||||
|
||||
static real_t out(real_t t, real_t b, real_t c, real_t d) {
|
||||
float s = 1.70158f;
|
||||
t = t / d - 1;
|
||||
|
||||
return c * (t * t * ((s + 1) * t + s) + 1) + b;
|
||||
}
|
||||
|
||||
static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
|
||||
float s = 1.70158f * 1.525f;
|
||||
t /= d / 2;
|
||||
|
||||
if (t < 1) {
|
||||
return c / 2 * (t * t * ((s + 1) * t - s)) + b;
|
||||
}
|
||||
|
||||
t -= 2;
|
||||
return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b;
|
||||
}
|
||||
|
||||
static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t < d / 2) {
|
||||
return out(t * 2, b, c / 2, d);
|
||||
}
|
||||
real_t h = c / 2;
|
||||
return in(t * 2 - d, b + h, h, d);
|
||||
}
|
||||
}; // namespace back
|
||||
|
||||
namespace spring {
|
||||
static real_t out(real_t t, real_t b, real_t c, real_t d) {
|
||||
t /= d;
|
||||
real_t s = 1.0 - t;
|
||||
t = (sin(t * Math_PI * (0.2 + 2.5 * t * t * t)) * pow(s, 2.2) + t) * (1.0 + (1.2 * s));
|
||||
return c * t + b;
|
||||
}
|
||||
|
||||
static real_t in(real_t t, real_t b, real_t c, real_t d) {
|
||||
return c - out(d - t, 0, c, d) + b;
|
||||
}
|
||||
|
||||
static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t < d / 2) {
|
||||
return in(t * 2, b, c / 2, d);
|
||||
}
|
||||
real_t h = c / 2;
|
||||
return out(t * 2 - d, b + h, h, d);
|
||||
}
|
||||
|
||||
static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
|
||||
if (t < d / 2) {
|
||||
return out(t * 2, b, c / 2, d);
|
||||
}
|
||||
real_t h = c / 2;
|
||||
return in(t * 2 - d, b + h, h, d);
|
||||
}
|
||||
}; // namespace spring
|
||||
|
||||
#endif // EASING_EQUATIONS_H
|
||||
209
engine/scene/animation/root_motion_view.cpp
Normal file
209
engine/scene/animation/root_motion_view.cpp
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
/**************************************************************************/
|
||||
/* root_motion_view.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
|
||||
#include "root_motion_view.h"
|
||||
|
||||
#include "scene/animation/animation_tree.h"
|
||||
#include "scene/resources/material.h"
|
||||
|
||||
void RootMotionView::set_animation_mixer(const NodePath &p_path) {
|
||||
path = p_path;
|
||||
first = true;
|
||||
}
|
||||
|
||||
NodePath RootMotionView::get_animation_mixer() const {
|
||||
return path;
|
||||
}
|
||||
|
||||
void RootMotionView::set_color(const Color &p_color) {
|
||||
color = p_color;
|
||||
first = true;
|
||||
}
|
||||
|
||||
Color RootMotionView::get_color() const {
|
||||
return color;
|
||||
}
|
||||
|
||||
void RootMotionView::set_cell_size(float p_size) {
|
||||
cell_size = p_size;
|
||||
first = true;
|
||||
}
|
||||
|
||||
float RootMotionView::get_cell_size() const {
|
||||
return cell_size;
|
||||
}
|
||||
|
||||
void RootMotionView::set_radius(float p_radius) {
|
||||
radius = p_radius;
|
||||
first = true;
|
||||
}
|
||||
|
||||
float RootMotionView::get_radius() const {
|
||||
return radius;
|
||||
}
|
||||
|
||||
void RootMotionView::set_zero_y(bool p_zero_y) {
|
||||
zero_y = p_zero_y;
|
||||
}
|
||||
|
||||
bool RootMotionView::get_zero_y() const {
|
||||
return zero_y;
|
||||
}
|
||||
|
||||
void RootMotionView::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
immediate_material = StandardMaterial3D::get_material_for_2d(false, BaseMaterial3D::TRANSPARENCY_ALPHA, false);
|
||||
|
||||
first = true;
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_INTERNAL_PROCESS:
|
||||
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
|
||||
Transform3D transform;
|
||||
Basis diff;
|
||||
|
||||
if (has_node(path)) {
|
||||
Node *node = get_node(path);
|
||||
|
||||
AnimationMixer *mixer = Object::cast_to<AnimationMixer>(node);
|
||||
if (mixer && mixer->is_active() && mixer->get_root_motion_track() != NodePath()) {
|
||||
if (is_processing_internal() && mixer->get_callback_mode_process() == AnimationMixer::ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS) {
|
||||
set_process_internal(false);
|
||||
set_physics_process_internal(true);
|
||||
}
|
||||
|
||||
if (is_physics_processing_internal() && mixer->get_callback_mode_process() == AnimationMixer::ANIMATION_CALLBACK_MODE_PROCESS_IDLE) {
|
||||
set_process_internal(true);
|
||||
set_physics_process_internal(false);
|
||||
}
|
||||
transform.origin = mixer->get_root_motion_position();
|
||||
transform.basis = mixer->get_root_motion_rotation(); // Scale is meaningless.
|
||||
diff = mixer->get_root_motion_rotation_accumulator();
|
||||
}
|
||||
}
|
||||
|
||||
if (!first && transform == Transform3D()) {
|
||||
return;
|
||||
}
|
||||
|
||||
first = false;
|
||||
|
||||
accumulated.basis *= transform.basis;
|
||||
transform.origin = (diff.inverse() * accumulated.basis).xform(transform.origin);
|
||||
accumulated.origin += transform.origin;
|
||||
|
||||
accumulated.origin.x = Math::fposmod(accumulated.origin.x, cell_size);
|
||||
if (zero_y) {
|
||||
accumulated.origin.y = 0;
|
||||
}
|
||||
accumulated.origin.z = Math::fposmod(accumulated.origin.z, cell_size);
|
||||
|
||||
immediate->clear_surfaces();
|
||||
|
||||
int cells_in_radius = int((radius / cell_size) + 1.0);
|
||||
|
||||
immediate->surface_begin(Mesh::PRIMITIVE_LINES, immediate_material);
|
||||
|
||||
for (int i = -cells_in_radius; i < cells_in_radius; i++) {
|
||||
for (int j = -cells_in_radius; j < cells_in_radius; j++) {
|
||||
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_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);
|
||||
c_i.a *= MAX(0, 1.0 - from_i.length() / radius);
|
||||
c_j.a *= MAX(0, 1.0 - from_j.length() / radius);
|
||||
|
||||
immediate->surface_set_color(c);
|
||||
immediate->surface_add_vertex(from);
|
||||
|
||||
immediate->surface_set_color(c_i);
|
||||
immediate->surface_add_vertex(from_i);
|
||||
|
||||
immediate->surface_set_color(c);
|
||||
immediate->surface_add_vertex(from);
|
||||
|
||||
immediate->surface_set_color(c_j);
|
||||
immediate->surface_add_vertex(from_j);
|
||||
}
|
||||
}
|
||||
|
||||
immediate->surface_end();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
AABB RootMotionView::get_aabb() const {
|
||||
return AABB(Vector3(-radius, 0, -radius), Vector3(radius * 2, 0.001, radius * 2));
|
||||
}
|
||||
|
||||
void RootMotionView::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_animation_path", "path"), &RootMotionView::set_animation_mixer);
|
||||
ClassDB::bind_method(D_METHOD("get_animation_path"), &RootMotionView::get_animation_mixer);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_color", "color"), &RootMotionView::set_color);
|
||||
ClassDB::bind_method(D_METHOD("get_color"), &RootMotionView::get_color);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_cell_size", "size"), &RootMotionView::set_cell_size);
|
||||
ClassDB::bind_method(D_METHOD("get_cell_size"), &RootMotionView::get_cell_size);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_radius", "size"), &RootMotionView::set_radius);
|
||||
ClassDB::bind_method(D_METHOD("get_radius"), &RootMotionView::get_radius);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_zero_y", "enable"), &RootMotionView::set_zero_y);
|
||||
ClassDB::bind_method(D_METHOD("get_zero_y"), &RootMotionView::get_zero_y);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "animation_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationMixer"), "set_animation_path", "get_animation_path");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell_size", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater,suffix:m"), "set_cell_size", "get_cell_size");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater,suffix:m"), "set_radius", "get_radius");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "zero_y"), "set_zero_y", "get_zero_y");
|
||||
}
|
||||
|
||||
RootMotionView::RootMotionView() {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
set_process_internal(true);
|
||||
}
|
||||
immediate.instantiate();
|
||||
set_base(immediate->get_rid());
|
||||
}
|
||||
|
||||
RootMotionView::~RootMotionView() {
|
||||
set_base(RID());
|
||||
}
|
||||
|
||||
#endif // _3D_DISABLED
|
||||
79
engine/scene/animation/root_motion_view.h
Normal file
79
engine/scene/animation/root_motion_view.h
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/**************************************************************************/
|
||||
/* root_motion_view.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ROOT_MOTION_VIEW_H
|
||||
#define ROOT_MOTION_VIEW_H
|
||||
|
||||
#include "scene/3d/visual_instance_3d.h"
|
||||
#include "scene/resources/immediate_mesh.h"
|
||||
class RootMotionView : public VisualInstance3D {
|
||||
GDCLASS(RootMotionView, VisualInstance3D);
|
||||
|
||||
public:
|
||||
Ref<ImmediateMesh> immediate;
|
||||
NodePath path;
|
||||
real_t cell_size = 1.0;
|
||||
real_t radius = 10.0;
|
||||
bool use_in_game = false;
|
||||
Color color = Color(0.5, 0.5, 1.0);
|
||||
bool first = true;
|
||||
bool zero_y = true;
|
||||
|
||||
Ref<Material> immediate_material;
|
||||
|
||||
Transform3D accumulated;
|
||||
|
||||
private:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void set_animation_mixer(const NodePath &p_path);
|
||||
NodePath get_animation_mixer() const;
|
||||
|
||||
void set_color(const Color &p_color);
|
||||
Color get_color() const;
|
||||
|
||||
void set_cell_size(float p_size);
|
||||
float get_cell_size() const;
|
||||
|
||||
void set_radius(float p_radius);
|
||||
float get_radius() const;
|
||||
|
||||
void set_zero_y(bool p_zero_y);
|
||||
bool get_zero_y() const;
|
||||
|
||||
virtual AABB get_aabb() const override;
|
||||
|
||||
RootMotionView();
|
||||
~RootMotionView();
|
||||
};
|
||||
|
||||
#endif // ROOT_MOTION_VIEW_H
|
||||
842
engine/scene/animation/tween.cpp
Normal file
842
engine/scene/animation/tween.cpp
Normal file
|
|
@ -0,0 +1,842 @@
|
|||
/**************************************************************************/
|
||||
/* tween.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 "tween.h"
|
||||
|
||||
#include "scene/animation/easing_equations.h"
|
||||
#include "scene/main/node.h"
|
||||
#include "scene/resources/animation.h"
|
||||
|
||||
#define CHECK_VALID() \
|
||||
ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); \
|
||||
ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first.");
|
||||
|
||||
Tween::interpolater Tween::interpolaters[Tween::TRANS_MAX][Tween::EASE_MAX] = {
|
||||
{ &linear::in, &linear::in, &linear::in, &linear::in }, // Linear is the same for each easing.
|
||||
{ &sine::in, &sine::out, &sine::in_out, &sine::out_in },
|
||||
{ &quint::in, &quint::out, &quint::in_out, &quint::out_in },
|
||||
{ &quart::in, &quart::out, &quart::in_out, &quart::out_in },
|
||||
{ &quad::in, &quad::out, &quad::in_out, &quad::out_in },
|
||||
{ &expo::in, &expo::out, &expo::in_out, &expo::out_in },
|
||||
{ &elastic::in, &elastic::out, &elastic::in_out, &elastic::out_in },
|
||||
{ &cubic::in, &cubic::out, &cubic::in_out, &cubic::out_in },
|
||||
{ &circ::in, &circ::out, &circ::in_out, &circ::out_in },
|
||||
{ &bounce::in, &bounce::out, &bounce::in_out, &bounce::out_in },
|
||||
{ &back::in, &back::out, &back::in_out, &back::out_in },
|
||||
{ &spring::in, &spring::out, &spring::in_out, &spring::out_in },
|
||||
};
|
||||
|
||||
void Tweener::set_tween(const Ref<Tween> &p_tween) {
|
||||
tween_id = p_tween->get_instance_id();
|
||||
}
|
||||
|
||||
Ref<Tween> Tweener::_get_tween() {
|
||||
return Ref<Tween>(ObjectDB::get_instance(tween_id));
|
||||
}
|
||||
|
||||
void Tweener::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("finished"));
|
||||
}
|
||||
|
||||
bool Tween::_validate_type_match(const Variant &p_from, Variant &r_to) {
|
||||
if (p_from.get_type() != r_to.get_type()) {
|
||||
// Cast r_to between double and int to avoid minor annoyances.
|
||||
if (p_from.get_type() == Variant::FLOAT && r_to.get_type() == Variant::INT) {
|
||||
r_to = double(r_to);
|
||||
} else if (p_from.get_type() == Variant::INT && r_to.get_type() == Variant::FLOAT) {
|
||||
r_to = int(r_to);
|
||||
} else {
|
||||
ERR_FAIL_V_MSG(false, "Type mismatch between initial and final value: " + Variant::get_type_name(p_from.get_type()) + " and " + Variant::get_type_name(r_to.get_type()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Tween::_start_tweeners() {
|
||||
if (tweeners.is_empty()) {
|
||||
dead = true;
|
||||
ERR_FAIL_MSG("Tween without commands, aborting.");
|
||||
}
|
||||
|
||||
for (Ref<Tweener> &tweener : tweeners.write[current_step]) {
|
||||
tweener->start();
|
||||
}
|
||||
}
|
||||
|
||||
void Tween::_stop_internal(bool p_reset) {
|
||||
running = false;
|
||||
if (p_reset) {
|
||||
started = false;
|
||||
dead = false;
|
||||
total_time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Ref<PropertyTweener> Tween::tween_property(const Object *p_target, const NodePath &p_property, Variant p_to, double p_duration) {
|
||||
ERR_FAIL_NULL_V(p_target, nullptr);
|
||||
CHECK_VALID();
|
||||
|
||||
Vector<StringName> property_subnames = p_property.get_as_property_path().get_subnames();
|
||||
#ifdef DEBUG_ENABLED
|
||||
bool prop_valid;
|
||||
const Variant &prop_value = p_target->get_indexed(property_subnames, &prop_valid);
|
||||
ERR_FAIL_COND_V_MSG(!prop_valid, nullptr, vformat("The tweened property \"%s\" does not exist in object \"%s\".", p_property, p_target));
|
||||
#else
|
||||
const Variant &prop_value = p_target->get_indexed(property_subnames);
|
||||
#endif
|
||||
|
||||
if (!_validate_type_match(prop_value, p_to)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Ref<PropertyTweener> tweener = memnew(PropertyTweener(p_target, property_subnames, p_to, p_duration));
|
||||
append(tweener);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
Ref<IntervalTweener> Tween::tween_interval(double p_time) {
|
||||
CHECK_VALID();
|
||||
|
||||
Ref<IntervalTweener> tweener = memnew(IntervalTweener(p_time));
|
||||
append(tweener);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
Ref<CallbackTweener> Tween::tween_callback(const Callable &p_callback) {
|
||||
CHECK_VALID();
|
||||
|
||||
Ref<CallbackTweener> tweener = memnew(CallbackTweener(p_callback));
|
||||
append(tweener);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
Ref<MethodTweener> Tween::tween_method(const Callable &p_callback, const Variant p_from, Variant p_to, double p_duration) {
|
||||
CHECK_VALID();
|
||||
|
||||
if (!_validate_type_match(p_from, p_to)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Ref<MethodTweener> tweener = memnew(MethodTweener(p_callback, p_from, p_to, p_duration));
|
||||
append(tweener);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
void Tween::append(Ref<Tweener> p_tweener) {
|
||||
p_tweener->set_tween(this);
|
||||
|
||||
if (parallel_enabled) {
|
||||
current_step = MAX(current_step, 0);
|
||||
} else {
|
||||
current_step++;
|
||||
}
|
||||
parallel_enabled = default_parallel;
|
||||
|
||||
tweeners.resize(current_step + 1);
|
||||
tweeners.write[current_step].push_back(p_tweener);
|
||||
}
|
||||
|
||||
void Tween::stop() {
|
||||
_stop_internal(true);
|
||||
}
|
||||
|
||||
void Tween::pause() {
|
||||
_stop_internal(false);
|
||||
}
|
||||
|
||||
void Tween::play() {
|
||||
ERR_FAIL_COND_MSG(!valid, "Tween invalid. Either finished or created outside scene tree.");
|
||||
ERR_FAIL_COND_MSG(dead, "Can't play finished Tween, use stop() first to reset its state.");
|
||||
running = true;
|
||||
}
|
||||
|
||||
void Tween::kill() {
|
||||
running = false; // For the sake of is_running().
|
||||
dead = true;
|
||||
}
|
||||
|
||||
bool Tween::is_running() {
|
||||
return running;
|
||||
}
|
||||
|
||||
bool Tween::is_valid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
void Tween::clear() {
|
||||
valid = false;
|
||||
tweeners.clear();
|
||||
}
|
||||
|
||||
Ref<Tween> Tween::bind_node(const Node *p_node) {
|
||||
ERR_FAIL_NULL_V(p_node, this);
|
||||
|
||||
bound_node = p_node->get_instance_id();
|
||||
is_bound = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
Ref<Tween> Tween::set_process_mode(TweenProcessMode p_mode) {
|
||||
process_mode = p_mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
Tween::TweenProcessMode Tween::get_process_mode() {
|
||||
return process_mode;
|
||||
}
|
||||
|
||||
Ref<Tween> Tween::set_pause_mode(TweenPauseMode p_mode) {
|
||||
pause_mode = p_mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
Tween::TweenPauseMode Tween::get_pause_mode() {
|
||||
return pause_mode;
|
||||
}
|
||||
|
||||
Ref<Tween> Tween::set_parallel(bool p_parallel) {
|
||||
default_parallel = p_parallel;
|
||||
parallel_enabled = p_parallel;
|
||||
return this;
|
||||
}
|
||||
|
||||
Ref<Tween> Tween::set_loops(int p_loops) {
|
||||
loops = p_loops;
|
||||
return this;
|
||||
}
|
||||
|
||||
int Tween::get_loops_left() const {
|
||||
if (loops <= 0) {
|
||||
return -1; // Infinite loop.
|
||||
} else {
|
||||
return loops - loops_done;
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Tween> Tween::set_speed_scale(float p_speed) {
|
||||
speed_scale = p_speed;
|
||||
return this;
|
||||
}
|
||||
|
||||
Ref<Tween> Tween::set_trans(TransitionType p_trans) {
|
||||
default_transition = p_trans;
|
||||
return this;
|
||||
}
|
||||
|
||||
Tween::TransitionType Tween::get_trans() {
|
||||
return default_transition;
|
||||
}
|
||||
|
||||
Ref<Tween> Tween::set_ease(EaseType p_ease) {
|
||||
default_ease = p_ease;
|
||||
return this;
|
||||
}
|
||||
|
||||
Tween::EaseType Tween::get_ease() {
|
||||
return default_ease;
|
||||
}
|
||||
|
||||
Ref<Tween> Tween::parallel() {
|
||||
parallel_enabled = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
Ref<Tween> Tween::chain() {
|
||||
parallel_enabled = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
bool Tween::custom_step(double p_delta) {
|
||||
bool r = running;
|
||||
running = true;
|
||||
bool ret = step(p_delta);
|
||||
running = running && r; // Running might turn false when Tween finished.
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Tween::step(double p_delta) {
|
||||
if (dead) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_bound) {
|
||||
Node *node = get_bound_node();
|
||||
if (node) {
|
||||
if (!node->is_inside_tree()) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!running) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!started) {
|
||||
if (tweeners.is_empty()) {
|
||||
String tween_id;
|
||||
Node *node = get_bound_node();
|
||||
if (node) {
|
||||
tween_id = vformat("Tween (bound to %s)", node->is_inside_tree() ? (String)node->get_path() : (String)node->get_name());
|
||||
} else {
|
||||
tween_id = to_string();
|
||||
}
|
||||
ERR_FAIL_V_MSG(false, tween_id + ": started with no Tweeners.");
|
||||
}
|
||||
current_step = 0;
|
||||
loops_done = 0;
|
||||
total_time = 0;
|
||||
_start_tweeners();
|
||||
started = true;
|
||||
}
|
||||
|
||||
double rem_delta = p_delta * speed_scale;
|
||||
bool step_active = false;
|
||||
total_time += rem_delta;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
double initial_delta = rem_delta;
|
||||
bool potential_infinite = false;
|
||||
#endif
|
||||
|
||||
while (rem_delta > 0 && running) {
|
||||
double step_delta = rem_delta;
|
||||
step_active = false;
|
||||
|
||||
for (Ref<Tweener> &tweener : tweeners.write[current_step]) {
|
||||
// Modified inside Tweener.step().
|
||||
double temp_delta = rem_delta;
|
||||
// Turns to true if any Tweener returns true (i.e. is still not finished).
|
||||
step_active = tweener->step(temp_delta) || step_active;
|
||||
step_delta = MIN(temp_delta, step_delta);
|
||||
}
|
||||
|
||||
rem_delta = step_delta;
|
||||
|
||||
if (!step_active) {
|
||||
emit_signal(SNAME("step_finished"), current_step);
|
||||
current_step++;
|
||||
|
||||
if (current_step == tweeners.size()) {
|
||||
loops_done++;
|
||||
if (loops_done == loops) {
|
||||
running = false;
|
||||
dead = true;
|
||||
emit_signal(SceneStringName(finished));
|
||||
break;
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Tween::can_process(bool p_tree_paused) const {
|
||||
if (is_bound && pause_mode == TWEEN_PAUSE_BOUND) {
|
||||
Node *node = get_bound_node();
|
||||
if (node) {
|
||||
return node->is_inside_tree() && node->can_process();
|
||||
}
|
||||
}
|
||||
|
||||
return !p_tree_paused || pause_mode == TWEEN_PAUSE_PROCESS;
|
||||
}
|
||||
|
||||
Node *Tween::get_bound_node() const {
|
||||
if (is_bound) {
|
||||
return Object::cast_to<Node>(ObjectDB::get_instance(bound_node));
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
double 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.
|
||||
return p_initial + p_delta;
|
||||
}
|
||||
|
||||
interpolater func = interpolaters[p_trans_type][p_ease_type];
|
||||
return func(p_time, p_initial, p_delta, p_duration);
|
||||
}
|
||||
|
||||
Variant Tween::interpolate_variant(const Variant &p_initial_val, const Variant &p_delta_val, double p_time, double p_duration, TransitionType p_trans, EaseType p_ease) {
|
||||
ERR_FAIL_INDEX_V(p_trans, TransitionType::TRANS_MAX, Variant());
|
||||
ERR_FAIL_INDEX_V(p_ease, EaseType::EASE_MAX, Variant());
|
||||
|
||||
Variant ret = Animation::add_variant(p_initial_val, p_delta_val);
|
||||
ret = Animation::interpolate_variant(p_initial_val, ret, run_equation(p_trans, p_ease, p_time, 0.0, 1.0, p_duration), p_initial_val.is_string());
|
||||
return ret;
|
||||
}
|
||||
|
||||
String Tween::to_string() {
|
||||
String ret = Object::to_string();
|
||||
Node *node = get_bound_node();
|
||||
if (node) {
|
||||
ret += vformat(" (bound to %s)", node->get_name());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Tween::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("tween_property", "object", "property", "final_val", "duration"), &Tween::tween_property);
|
||||
ClassDB::bind_method(D_METHOD("tween_interval", "time"), &Tween::tween_interval);
|
||||
ClassDB::bind_method(D_METHOD("tween_callback", "callback"), &Tween::tween_callback);
|
||||
ClassDB::bind_method(D_METHOD("tween_method", "method", "from", "to", "duration"), &Tween::tween_method);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("custom_step", "delta"), &Tween::custom_step);
|
||||
ClassDB::bind_method(D_METHOD("stop"), &Tween::stop);
|
||||
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);
|
||||
ClassDB::bind_method(D_METHOD("bind_node", "node"), &Tween::bind_node);
|
||||
ClassDB::bind_method(D_METHOD("set_process_mode", "mode"), &Tween::set_process_mode);
|
||||
ClassDB::bind_method(D_METHOD("set_pause_mode", "mode"), &Tween::set_pause_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_parallel", "parallel"), &Tween::set_parallel, DEFVAL(true));
|
||||
ClassDB::bind_method(D_METHOD("set_loops", "loops"), &Tween::set_loops, DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("get_loops_left"), &Tween::get_loops_left);
|
||||
ClassDB::bind_method(D_METHOD("set_speed_scale", "speed"), &Tween::set_speed_scale);
|
||||
ClassDB::bind_method(D_METHOD("set_trans", "trans"), &Tween::set_trans);
|
||||
ClassDB::bind_method(D_METHOD("set_ease", "ease"), &Tween::set_ease);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("parallel"), &Tween::parallel);
|
||||
ClassDB::bind_method(D_METHOD("chain"), &Tween::chain);
|
||||
|
||||
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")));
|
||||
ADD_SIGNAL(MethodInfo("finished"));
|
||||
|
||||
BIND_ENUM_CONSTANT(TWEEN_PROCESS_PHYSICS);
|
||||
BIND_ENUM_CONSTANT(TWEEN_PROCESS_IDLE);
|
||||
|
||||
BIND_ENUM_CONSTANT(TWEEN_PAUSE_BOUND);
|
||||
BIND_ENUM_CONSTANT(TWEEN_PAUSE_STOP);
|
||||
BIND_ENUM_CONSTANT(TWEEN_PAUSE_PROCESS);
|
||||
|
||||
BIND_ENUM_CONSTANT(TRANS_LINEAR);
|
||||
BIND_ENUM_CONSTANT(TRANS_SINE);
|
||||
BIND_ENUM_CONSTANT(TRANS_QUINT);
|
||||
BIND_ENUM_CONSTANT(TRANS_QUART);
|
||||
BIND_ENUM_CONSTANT(TRANS_QUAD);
|
||||
BIND_ENUM_CONSTANT(TRANS_EXPO);
|
||||
BIND_ENUM_CONSTANT(TRANS_ELASTIC);
|
||||
BIND_ENUM_CONSTANT(TRANS_CUBIC);
|
||||
BIND_ENUM_CONSTANT(TRANS_CIRC);
|
||||
BIND_ENUM_CONSTANT(TRANS_BOUNCE);
|
||||
BIND_ENUM_CONSTANT(TRANS_BACK);
|
||||
BIND_ENUM_CONSTANT(TRANS_SPRING);
|
||||
|
||||
BIND_ENUM_CONSTANT(EASE_IN);
|
||||
BIND_ENUM_CONSTANT(EASE_OUT);
|
||||
BIND_ENUM_CONSTANT(EASE_IN_OUT);
|
||||
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(const Variant &p_value) {
|
||||
Ref<Tween> tween = _get_tween();
|
||||
ERR_FAIL_COND_V(tween.is_null(), nullptr);
|
||||
|
||||
Variant from_value = p_value;
|
||||
if (!tween->_validate_type_match(final_val, from_value)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
initial_val = from_value;
|
||||
do_continue = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
Ref<PropertyTweener> PropertyTweener::from_current() {
|
||||
do_continue = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
Ref<PropertyTweener> PropertyTweener::as_relative() {
|
||||
relative = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
Ref<PropertyTweener> PropertyTweener::set_trans(Tween::TransitionType p_trans) {
|
||||
trans_type = p_trans;
|
||||
return this;
|
||||
}
|
||||
|
||||
Ref<PropertyTweener> PropertyTweener::set_ease(Tween::EaseType p_ease) {
|
||||
ease_type = p_ease;
|
||||
return this;
|
||||
}
|
||||
|
||||
Ref<PropertyTweener> PropertyTweener::set_custom_interpolator(const Callable &p_method) {
|
||||
custom_method = p_method;
|
||||
return this;
|
||||
}
|
||||
|
||||
Ref<PropertyTweener> PropertyTweener::set_delay(double p_delay) {
|
||||
delay = p_delay;
|
||||
return this;
|
||||
}
|
||||
|
||||
void PropertyTweener::start() {
|
||||
elapsed_time = 0;
|
||||
finished = false;
|
||||
|
||||
Object *target_instance = ObjectDB::get_instance(target);
|
||||
if (!target_instance) {
|
||||
WARN_PRINT("Target object freed before starting, aborting Tweener.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (do_continue) {
|
||||
if (Math::is_zero_approx(delay)) {
|
||||
initial_val = target_instance->get_indexed(property);
|
||||
} else {
|
||||
do_continue_delayed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (relative) {
|
||||
final_val = Animation::add_variant(initial_val, base_final_val);
|
||||
}
|
||||
|
||||
delta_val = Animation::subtract_variant(final_val, initial_val);
|
||||
}
|
||||
|
||||
bool PropertyTweener::step(double &r_delta) {
|
||||
if (finished) {
|
||||
// This is needed in case there's a parallel Tweener with longer duration.
|
||||
return false;
|
||||
}
|
||||
|
||||
Object *target_instance = ObjectDB::get_instance(target);
|
||||
if (!target_instance) {
|
||||
return false;
|
||||
}
|
||||
elapsed_time += r_delta;
|
||||
|
||||
if (elapsed_time < delay) {
|
||||
r_delta = 0;
|
||||
return true;
|
||||
} else if (do_continue_delayed && !Math::is_zero_approx(delay)) {
|
||||
initial_val = target_instance->get_indexed(property);
|
||||
delta_val = Animation::subtract_variant(final_val, initial_val);
|
||||
do_continue_delayed = false;
|
||||
}
|
||||
|
||||
Ref<Tween> tween = _get_tween();
|
||||
|
||||
double time = MIN(elapsed_time - delay, duration);
|
||||
if (time < duration) {
|
||||
if (custom_method.is_valid()) {
|
||||
const Variant t = tween->interpolate_variant(0.0, 1.0, time, duration, trans_type, ease_type);
|
||||
const Variant *argptr = &t;
|
||||
|
||||
Variant result;
|
||||
Callable::CallError ce;
|
||||
custom_method.callp(&argptr, 1, result, ce);
|
||||
if (ce.error != Callable::CallError::CALL_OK) {
|
||||
ERR_FAIL_V_MSG(false, "Error calling custom method from PropertyTweener: " + Variant::get_callable_error_text(custom_method, &argptr, 1, ce) + ".");
|
||||
} else if (result.get_type() != Variant::FLOAT) {
|
||||
ERR_FAIL_V_MSG(false, vformat("Wrong return type in PropertyTweener custom method. Expected float, got %s.", Variant::get_type_name(result.get_type())));
|
||||
}
|
||||
|
||||
target_instance->set_indexed(property, Animation::interpolate_variant(initial_val, final_val, result));
|
||||
} else {
|
||||
target_instance->set_indexed(property, tween->interpolate_variant(initial_val, delta_val, time, duration, trans_type, ease_type));
|
||||
}
|
||||
r_delta = 0;
|
||||
return true;
|
||||
} else {
|
||||
target_instance->set_indexed(property, final_val);
|
||||
finished = true;
|
||||
r_delta = elapsed_time - delay - duration;
|
||||
emit_signal(SceneStringName(finished));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyTweener::set_tween(const Ref<Tween> &p_tween) {
|
||||
Tweener::set_tween(p_tween);
|
||||
if (trans_type == Tween::TRANS_MAX) {
|
||||
trans_type = p_tween->get_trans();
|
||||
}
|
||||
if (ease_type == Tween::EASE_MAX) {
|
||||
ease_type = p_tween->get_ease();
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyTweener::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("from", "value"), &PropertyTweener::from);
|
||||
ClassDB::bind_method(D_METHOD("from_current"), &PropertyTweener::from_current);
|
||||
ClassDB::bind_method(D_METHOD("as_relative"), &PropertyTweener::as_relative);
|
||||
ClassDB::bind_method(D_METHOD("set_trans", "trans"), &PropertyTweener::set_trans);
|
||||
ClassDB::bind_method(D_METHOD("set_ease", "ease"), &PropertyTweener::set_ease);
|
||||
ClassDB::bind_method(D_METHOD("set_custom_interpolator", "interpolator_method"), &PropertyTweener::set_custom_interpolator);
|
||||
ClassDB::bind_method(D_METHOD("set_delay", "delay"), &PropertyTweener::set_delay);
|
||||
}
|
||||
|
||||
PropertyTweener::PropertyTweener(const Object *p_target, const Vector<StringName> &p_property, const Variant &p_to, double p_duration) {
|
||||
target = p_target->get_instance_id();
|
||||
property = p_property;
|
||||
initial_val = p_target->get_indexed(property);
|
||||
base_final_val = p_to;
|
||||
final_val = base_final_val;
|
||||
duration = p_duration;
|
||||
|
||||
if (p_target->is_ref_counted()) {
|
||||
ref_copy = p_target;
|
||||
}
|
||||
}
|
||||
|
||||
PropertyTweener::PropertyTweener() {
|
||||
ERR_FAIL_MSG("PropertyTweener can't be created directly. Use the tween_property() method in Tween.");
|
||||
}
|
||||
|
||||
void IntervalTweener::start() {
|
||||
elapsed_time = 0;
|
||||
finished = false;
|
||||
}
|
||||
|
||||
bool IntervalTweener::step(double &r_delta) {
|
||||
if (finished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
elapsed_time += r_delta;
|
||||
|
||||
if (elapsed_time < duration) {
|
||||
r_delta = 0;
|
||||
return true;
|
||||
} else {
|
||||
finished = true;
|
||||
r_delta = elapsed_time - duration;
|
||||
emit_signal(SceneStringName(finished));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
IntervalTweener::IntervalTweener(double p_time) {
|
||||
duration = p_time;
|
||||
}
|
||||
|
||||
IntervalTweener::IntervalTweener() {
|
||||
ERR_FAIL_MSG("IntervalTweener can't be created directly. Use the tween_interval() method in Tween.");
|
||||
}
|
||||
|
||||
Ref<CallbackTweener> CallbackTweener::set_delay(double p_delay) {
|
||||
delay = p_delay;
|
||||
return this;
|
||||
}
|
||||
|
||||
void CallbackTweener::start() {
|
||||
elapsed_time = 0;
|
||||
finished = false;
|
||||
}
|
||||
|
||||
bool CallbackTweener::step(double &r_delta) {
|
||||
if (finished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!callback.is_valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
elapsed_time += r_delta;
|
||||
if (elapsed_time >= delay) {
|
||||
Variant result;
|
||||
Callable::CallError ce;
|
||||
callback.callp(nullptr, 0, result, ce);
|
||||
if (ce.error != Callable::CallError::CALL_OK) {
|
||||
ERR_FAIL_V_MSG(false, "Error calling method from CallbackTweener: " + Variant::get_callable_error_text(callback, nullptr, 0, ce) + ".");
|
||||
}
|
||||
|
||||
finished = true;
|
||||
r_delta = elapsed_time - delay;
|
||||
emit_signal(SceneStringName(finished));
|
||||
return false;
|
||||
}
|
||||
|
||||
r_delta = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CallbackTweener::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_delay", "delay"), &CallbackTweener::set_delay);
|
||||
}
|
||||
|
||||
CallbackTweener::CallbackTweener(const Callable &p_callback) {
|
||||
callback = p_callback;
|
||||
|
||||
Object *callback_instance = p_callback.get_object();
|
||||
if (callback_instance && callback_instance->is_ref_counted()) {
|
||||
ref_copy = callback_instance;
|
||||
}
|
||||
}
|
||||
|
||||
CallbackTweener::CallbackTweener() {
|
||||
ERR_FAIL_MSG("CallbackTweener can't be created directly. Use the tween_callback() method in Tween.");
|
||||
}
|
||||
|
||||
Ref<MethodTweener> MethodTweener::set_delay(double p_delay) {
|
||||
delay = p_delay;
|
||||
return this;
|
||||
}
|
||||
|
||||
Ref<MethodTweener> MethodTweener::set_trans(Tween::TransitionType p_trans) {
|
||||
trans_type = p_trans;
|
||||
return this;
|
||||
}
|
||||
|
||||
Ref<MethodTweener> MethodTweener::set_ease(Tween::EaseType p_ease) {
|
||||
ease_type = p_ease;
|
||||
return this;
|
||||
}
|
||||
|
||||
void MethodTweener::start() {
|
||||
elapsed_time = 0;
|
||||
finished = false;
|
||||
}
|
||||
|
||||
bool MethodTweener::step(double &r_delta) {
|
||||
if (finished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!callback.is_valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
elapsed_time += r_delta;
|
||||
|
||||
if (elapsed_time < delay) {
|
||||
r_delta = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
Ref<Tween> tween = _get_tween();
|
||||
|
||||
Variant current_val;
|
||||
double time = MIN(elapsed_time - delay, duration);
|
||||
if (time < duration) {
|
||||
current_val = tween->interpolate_variant(initial_val, delta_val, time, duration, trans_type, ease_type);
|
||||
} else {
|
||||
current_val = final_val;
|
||||
}
|
||||
const Variant **argptr = (const Variant **)alloca(sizeof(Variant *));
|
||||
argptr[0] = ¤t_val;
|
||||
|
||||
Variant result;
|
||||
Callable::CallError ce;
|
||||
callback.callp(argptr, 1, result, ce);
|
||||
if (ce.error != Callable::CallError::CALL_OK) {
|
||||
ERR_FAIL_V_MSG(false, "Error calling method from MethodTweener: " + Variant::get_callable_error_text(callback, argptr, 1, ce) + ".");
|
||||
}
|
||||
|
||||
if (time < duration) {
|
||||
r_delta = 0;
|
||||
return true;
|
||||
} else {
|
||||
finished = true;
|
||||
r_delta = elapsed_time - delay - duration;
|
||||
emit_signal(SceneStringName(finished));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void MethodTweener::set_tween(const Ref<Tween> &p_tween) {
|
||||
Tweener::set_tween(p_tween);
|
||||
if (trans_type == Tween::TRANS_MAX) {
|
||||
trans_type = p_tween->get_trans();
|
||||
}
|
||||
if (ease_type == Tween::EASE_MAX) {
|
||||
ease_type = p_tween->get_ease();
|
||||
}
|
||||
}
|
||||
|
||||
void MethodTweener::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_delay", "delay"), &MethodTweener::set_delay);
|
||||
ClassDB::bind_method(D_METHOD("set_trans", "trans"), &MethodTweener::set_trans);
|
||||
ClassDB::bind_method(D_METHOD("set_ease", "ease"), &MethodTweener::set_ease);
|
||||
}
|
||||
|
||||
MethodTweener::MethodTweener(const Callable &p_callback, const Variant &p_from, const Variant &p_to, double p_duration) {
|
||||
callback = p_callback;
|
||||
initial_val = p_from;
|
||||
delta_val = Animation::subtract_variant(p_to, p_from);
|
||||
final_val = p_to;
|
||||
duration = p_duration;
|
||||
|
||||
Object *callback_instance = p_callback.get_object();
|
||||
if (callback_instance && callback_instance->is_ref_counted()) {
|
||||
ref_copy = callback_instance;
|
||||
}
|
||||
}
|
||||
|
||||
MethodTweener::MethodTweener() {
|
||||
ERR_FAIL_MSG("MethodTweener can't be created directly. Use the tween_method() method in Tween.");
|
||||
}
|
||||
304
engine/scene/animation/tween.h
Normal file
304
engine/scene/animation/tween.h
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
/**************************************************************************/
|
||||
/* tween.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TWEEN_H
|
||||
#define TWEEN_H
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class Tween;
|
||||
class Node;
|
||||
|
||||
class Tweener : public RefCounted {
|
||||
GDCLASS(Tweener, RefCounted);
|
||||
|
||||
ObjectID tween_id;
|
||||
|
||||
public:
|
||||
virtual void set_tween(const Ref<Tween> &p_tween);
|
||||
virtual void start() = 0;
|
||||
virtual bool step(double &r_delta) = 0;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
Ref<Tween> _get_tween();
|
||||
|
||||
double elapsed_time = 0;
|
||||
bool finished = false;
|
||||
};
|
||||
|
||||
class PropertyTweener;
|
||||
class IntervalTweener;
|
||||
class CallbackTweener;
|
||||
class MethodTweener;
|
||||
|
||||
class Tween : public RefCounted {
|
||||
GDCLASS(Tween, RefCounted);
|
||||
|
||||
friend class PropertyTweener;
|
||||
|
||||
public:
|
||||
enum TweenProcessMode {
|
||||
TWEEN_PROCESS_PHYSICS,
|
||||
TWEEN_PROCESS_IDLE,
|
||||
};
|
||||
|
||||
enum TweenPauseMode {
|
||||
TWEEN_PAUSE_BOUND,
|
||||
TWEEN_PAUSE_STOP,
|
||||
TWEEN_PAUSE_PROCESS,
|
||||
};
|
||||
|
||||
enum TransitionType {
|
||||
TRANS_LINEAR,
|
||||
TRANS_SINE,
|
||||
TRANS_QUINT,
|
||||
TRANS_QUART,
|
||||
TRANS_QUAD,
|
||||
TRANS_EXPO,
|
||||
TRANS_ELASTIC,
|
||||
TRANS_CUBIC,
|
||||
TRANS_CIRC,
|
||||
TRANS_BOUNCE,
|
||||
TRANS_BACK,
|
||||
TRANS_SPRING,
|
||||
TRANS_MAX
|
||||
};
|
||||
|
||||
enum EaseType {
|
||||
EASE_IN,
|
||||
EASE_OUT,
|
||||
EASE_IN_OUT,
|
||||
EASE_OUT_IN,
|
||||
EASE_MAX
|
||||
};
|
||||
|
||||
private:
|
||||
TweenProcessMode process_mode = TweenProcessMode::TWEEN_PROCESS_IDLE;
|
||||
TweenPauseMode pause_mode = TweenPauseMode::TWEEN_PAUSE_BOUND;
|
||||
TransitionType default_transition = TransitionType::TRANS_LINEAR;
|
||||
EaseType default_ease = EaseType::EASE_IN_OUT;
|
||||
ObjectID bound_node;
|
||||
|
||||
Vector<List<Ref<Tweener>>> tweeners;
|
||||
double total_time = 0;
|
||||
int current_step = -1;
|
||||
int loops = 1;
|
||||
int loops_done = 0;
|
||||
float speed_scale = 1;
|
||||
|
||||
bool is_bound = false;
|
||||
bool started = false;
|
||||
bool running = true;
|
||||
bool dead = false;
|
||||
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];
|
||||
|
||||
void _start_tweeners();
|
||||
void _stop_internal(bool p_reset);
|
||||
bool _validate_type_match(const Variant &p_from, Variant &r_to);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual String to_string() override;
|
||||
|
||||
Ref<PropertyTweener> tween_property(const Object *p_target, const NodePath &p_property, Variant p_to, double p_duration);
|
||||
Ref<IntervalTweener> tween_interval(double p_time);
|
||||
Ref<CallbackTweener> tween_callback(const Callable &p_callback);
|
||||
Ref<MethodTweener> tween_method(const Callable &p_callback, const Variant p_from, Variant p_to, double p_duration);
|
||||
void append(Ref<Tweener> p_tweener);
|
||||
|
||||
bool custom_step(double p_delta);
|
||||
void stop();
|
||||
void pause();
|
||||
void play();
|
||||
void kill();
|
||||
|
||||
bool is_running();
|
||||
bool is_valid();
|
||||
void clear();
|
||||
|
||||
Ref<Tween> bind_node(const Node *p_node);
|
||||
Ref<Tween> set_process_mode(TweenProcessMode p_mode);
|
||||
TweenProcessMode get_process_mode();
|
||||
Ref<Tween> set_pause_mode(TweenPauseMode p_mode);
|
||||
TweenPauseMode get_pause_mode();
|
||||
|
||||
Ref<Tween> set_parallel(bool p_parallel);
|
||||
Ref<Tween> set_loops(int p_loops);
|
||||
int get_loops_left() const;
|
||||
Ref<Tween> set_speed_scale(float p_speed);
|
||||
Ref<Tween> set_trans(TransitionType p_trans);
|
||||
TransitionType get_trans();
|
||||
Ref<Tween> set_ease(EaseType p_ease);
|
||||
EaseType get_ease();
|
||||
|
||||
Ref<Tween> parallel();
|
||||
Ref<Tween> chain();
|
||||
|
||||
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(const Variant &p_initial_val, const Variant &p_delta_val, double p_time, double p_duration, Tween::TransitionType p_trans, Tween::EaseType p_ease);
|
||||
|
||||
bool step(double p_delta);
|
||||
bool can_process(bool p_tree_paused) const;
|
||||
Node *get_bound_node() const;
|
||||
double get_total_time() const;
|
||||
|
||||
Tween();
|
||||
Tween(bool p_valid);
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(Tween::TweenPauseMode);
|
||||
VARIANT_ENUM_CAST(Tween::TweenProcessMode);
|
||||
VARIANT_ENUM_CAST(Tween::TransitionType);
|
||||
VARIANT_ENUM_CAST(Tween::EaseType);
|
||||
|
||||
class PropertyTweener : public Tweener {
|
||||
GDCLASS(PropertyTweener, Tweener);
|
||||
|
||||
public:
|
||||
Ref<PropertyTweener> from(const Variant &p_value);
|
||||
Ref<PropertyTweener> from_current();
|
||||
Ref<PropertyTweener> as_relative();
|
||||
Ref<PropertyTweener> set_trans(Tween::TransitionType p_trans);
|
||||
Ref<PropertyTweener> set_ease(Tween::EaseType p_ease);
|
||||
Ref<PropertyTweener> set_custom_interpolator(const Callable &p_method);
|
||||
Ref<PropertyTweener> set_delay(double p_delay);
|
||||
|
||||
void set_tween(const Ref<Tween> &p_tween) override;
|
||||
void start() override;
|
||||
bool step(double &r_delta) override;
|
||||
|
||||
PropertyTweener(const Object *p_target, const Vector<StringName> &p_property, const Variant &p_to, double p_duration);
|
||||
PropertyTweener();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
ObjectID target;
|
||||
Vector<StringName> property;
|
||||
Variant initial_val;
|
||||
Variant base_final_val;
|
||||
Variant final_val;
|
||||
Variant delta_val;
|
||||
|
||||
Ref<RefCounted> ref_copy; // Makes sure that RefCounted objects are not freed too early.
|
||||
|
||||
double duration = 0;
|
||||
Tween::TransitionType trans_type = Tween::TRANS_MAX; // This is set inside set_tween();
|
||||
Tween::EaseType ease_type = Tween::EASE_MAX;
|
||||
Callable custom_method;
|
||||
|
||||
double delay = 0;
|
||||
bool do_continue = true;
|
||||
bool do_continue_delayed = false;
|
||||
bool relative = false;
|
||||
};
|
||||
|
||||
class IntervalTweener : public Tweener {
|
||||
GDCLASS(IntervalTweener, Tweener);
|
||||
|
||||
public:
|
||||
void start() override;
|
||||
bool step(double &r_delta) override;
|
||||
|
||||
IntervalTweener(double p_time);
|
||||
IntervalTweener();
|
||||
|
||||
private:
|
||||
double duration = 0;
|
||||
};
|
||||
|
||||
class CallbackTweener : public Tweener {
|
||||
GDCLASS(CallbackTweener, Tweener);
|
||||
|
||||
public:
|
||||
Ref<CallbackTweener> set_delay(double p_delay);
|
||||
|
||||
void start() override;
|
||||
bool step(double &r_delta) override;
|
||||
|
||||
CallbackTweener(const Callable &p_callback);
|
||||
CallbackTweener();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
Callable callback;
|
||||
double delay = 0;
|
||||
|
||||
Ref<RefCounted> ref_copy;
|
||||
};
|
||||
|
||||
class MethodTweener : public Tweener {
|
||||
GDCLASS(MethodTweener, Tweener);
|
||||
|
||||
public:
|
||||
Ref<MethodTweener> set_trans(Tween::TransitionType p_trans);
|
||||
Ref<MethodTweener> set_ease(Tween::EaseType p_ease);
|
||||
Ref<MethodTweener> set_delay(double p_delay);
|
||||
|
||||
void set_tween(const Ref<Tween> &p_tween) override;
|
||||
void start() override;
|
||||
bool step(double &r_delta) override;
|
||||
|
||||
MethodTweener(const Callable &p_callback, const Variant &p_from, const Variant &p_to, double p_duration);
|
||||
MethodTweener();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
double duration = 0;
|
||||
double delay = 0;
|
||||
Tween::TransitionType trans_type = Tween::TRANS_MAX;
|
||||
Tween::EaseType ease_type = Tween::EASE_MAX;
|
||||
|
||||
Variant initial_val;
|
||||
Variant delta_val;
|
||||
Variant final_val;
|
||||
Callable callback;
|
||||
|
||||
Ref<RefCounted> ref_copy;
|
||||
};
|
||||
|
||||
#endif // TWEEN_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue