feat: godot-engine-source-4.3-stable

This commit is contained in:
Jan van der Weide 2025-01-17 16:36:38 +01:00
parent c59a7dcade
commit 7125d019b5
11149 changed files with 5070401 additions and 0 deletions

View 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)

View 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() {
}

View 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

View 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() {
}

View 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

File diff suppressed because it is too large Load diff

View 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

View 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

File diff suppressed because it is too large Load diff

View 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

File diff suppressed because it is too large Load diff

View 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

View 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

View 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() {
}

View 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

View 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

View 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(&parameters);
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() {
}

View 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

View 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

View 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

View 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

View 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] = &current_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.");
}

View 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