summaryrefslogtreecommitdiff
path: root/scene/animation
diff options
context:
space:
mode:
Diffstat (limited to 'scene/animation')
-rw-r--r--scene/animation/animation_blend_space_1d.cpp300
-rw-r--r--scene/animation/animation_blend_space_1d.h75
-rw-r--r--scene/animation/animation_blend_space_2d.cpp573
-rw-r--r--scene/animation/animation_blend_space_2d.h101
-rw-r--r--scene/animation/animation_blend_tree.cpp1220
-rw-r--r--scene/animation/animation_blend_tree.h378
-rw-r--r--scene/animation/animation_cache.cpp12
-rw-r--r--scene/animation/animation_node_state_machine.cpp943
-rw-r--r--scene/animation/animation_node_state_machine.h189
-rw-r--r--scene/animation/animation_player.cpp374
-rw-r--r--scene/animation/animation_player.h40
-rw-r--r--scene/animation/animation_tree.cpp1504
-rw-r--r--scene/animation/animation_tree.h314
-rw-r--r--scene/animation/animation_tree_player.cpp30
-rw-r--r--scene/animation/animation_tree_player.h1
-rw-r--r--scene/animation/root_motion_view.cpp178
-rw-r--r--scene/animation/root_motion_view.h47
-rw-r--r--scene/animation/skeleton_ik.cpp555
-rw-r--r--scene/animation/skeleton_ik.h216
-rw-r--r--scene/animation/tween.cpp130
-rw-r--r--scene/animation/tween.h8
21 files changed, 7064 insertions, 124 deletions
diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp
new file mode 100644
index 0000000000..289cf7a3a7
--- /dev/null
+++ b/scene/animation/animation_blend_space_1d.cpp
@@ -0,0 +1,300 @@
+#include "animation_blend_space_1d.h"
+
+void AnimationNodeBlendSpace1D::get_parameter_list(List<PropertyInfo> *r_list) const {
+ r_list->push_back(PropertyInfo(Variant::REAL, blend_position));
+}
+Variant AnimationNodeBlendSpace1D::get_parameter_default_value(const StringName &p_parameter) const {
+ return 0;
+}
+
+Ref<AnimationNode> AnimationNodeBlendSpace1D::get_child_by_name(const StringName &p_name) {
+ return get_blend_point_node(p_name.operator String().to_int());
+}
+
+void AnimationNodeBlendSpace1D::_validate_property(PropertyInfo &property) const {
+ if (property.name.begins_with("blend_point_")) {
+ String left = property.name.get_slicec('/', 0);
+ int idx = left.get_slicec('_', 2).to_int();
+ if (idx >= blend_points_used) {
+ property.usage = 0;
+ }
+ }
+ AnimationRootNode::_validate_property(property);
+}
+
+void AnimationNodeBlendSpace1D::_tree_changed() {
+ emit_signal("tree_changed");
+}
+
+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("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace1D::_add_blend_point);
+
+ ClassDB::bind_method(D_METHOD("_tree_changed"), &AnimationNodeBlendSpace1D::_tree_changed);
+
+ 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_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_add_blend_point", "get_blend_point_node", i);
+ ADD_PROPERTYI(PropertyInfo(Variant::REAL, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i);
+ }
+
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_min_space", "get_min_space");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_space", "get_max_space");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_snap", "get_snap");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_value_label", "get_value_label");
+}
+
+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", this, "_tree_changed", varray(), CONNECT_REFERENCE_COUNTED);
+
+ blend_points_used++;
+ emit_signal("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", this, "_tree_changed");
+ }
+
+ blend_points[p_point].node = p_node;
+ blend_points[p_point].node->connect("tree_changed", this, "_tree_changed", varray(), CONNECT_REFERENCE_COUNTED);
+
+ emit_signal("tree_changed");
+}
+
+float AnimationNodeBlendSpace1D::get_blend_point_position(int p_point) const {
+ ERR_FAIL_INDEX_V(p_point, blend_points_used, 0);
+ return blend_points[p_point].position;
+}
+
+Ref<AnimationRootNode> AnimationNodeBlendSpace1D::get_blend_point_node(int p_point) const {
+ ERR_FAIL_INDEX_V(p_point, blend_points_used, Ref<AnimationRootNode>());
+ return blend_points[p_point].node;
+}
+
+void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) {
+ ERR_FAIL_INDEX(p_point, blend_points_used);
+
+ blend_points[p_point].node->disconnect("tree_changed", this, "_tree_changed");
+
+ for (int i = p_point; i < blend_points_used - 1; i++) {
+ blend_points[i] = blend_points[i + 1];
+ }
+
+ blend_points_used--;
+ emit_signal("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::_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);
+ }
+}
+
+float AnimationNodeBlendSpace1D::process(float p_time, bool p_seek) {
+
+ if (blend_points_used == 0) {
+ return 0.0;
+ }
+
+ if (blend_points_used == 1) {
+ // only one point available, just play that animation
+ return blend_node(blend_points[0].name, blend_points[0].node, p_time, p_seek, 1.0, FILTER_IGNORE, false);
+ }
+
+ float blend_pos = get_parameter(blend_position);
+
+ float weights[MAX_BLEND_POINTS] = {};
+
+ 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) {
+ point_lower = i;
+ pos_lower = pos;
+ } else if ((blend_pos - pos) < (blend_pos - pos_lower)) {
+ point_lower = i;
+ pos_lower = pos;
+ }
+ } else {
+ if (point_higher == -1) {
+ point_higher = i;
+ pos_higher = pos;
+ } else if ((pos - blend_pos) < (pos_higher - blend_pos)) {
+ point_higher = i;
+ pos_higher = pos;
+ }
+ }
+ }
+
+ // fill in weights
+
+ if (point_lower == -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
+
+ float max_time_remaining = 0.0;
+
+ for (int i = 0; i < blend_points_used; i++) {
+ float remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, weights[i], FILTER_IGNORE, false);
+
+ max_time_remaining = MAX(max_time_remaining, remaining);
+ }
+
+ return max_time_remaining;
+}
+
+String AnimationNodeBlendSpace1D::get_caption() const {
+ return "BlendSpace1D";
+}
+
+AnimationNodeBlendSpace1D::AnimationNodeBlendSpace1D() {
+
+ for (int i = 0; i < MAX_BLEND_POINTS; i++) {
+ blend_points[i].name = itos(i);
+ }
+ blend_points_used = 0;
+ max_space = 1;
+ min_space = -1;
+
+ snap = 0.1;
+ value_label = "value";
+
+ blend_position = "blend_position";
+}
+
+AnimationNodeBlendSpace1D::~AnimationNodeBlendSpace1D() {
+}
diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h
new file mode 100644
index 0000000000..f4e20f0d70
--- /dev/null
+++ b/scene/animation/animation_blend_space_1d.h
@@ -0,0 +1,75 @@
+#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)
+
+ enum {
+ MAX_BLEND_POINTS = 64
+ };
+
+ struct BlendPoint {
+ StringName name;
+ Ref<AnimationRootNode> node;
+ float position;
+ };
+
+ BlendPoint blend_points[MAX_BLEND_POINTS];
+ int blend_points_used;
+
+ float max_space;
+ float min_space;
+
+ float snap;
+
+ String value_label;
+
+ void _add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node);
+
+ void _tree_changed();
+
+ StringName blend_position;
+
+protected:
+ virtual void _validate_property(PropertyInfo &property) const;
+ static void _bind_methods();
+
+public:
+ virtual void get_parameter_list(List<PropertyInfo> *r_list) const;
+ virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
+
+ virtual void get_child_nodes(List<ChildNode> *r_child_nodes);
+
+ 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;
+
+ float process(float p_time, bool p_seek);
+ String get_caption() const;
+
+ Ref<AnimationNode> get_child_by_name(const StringName &p_name);
+
+ AnimationNodeBlendSpace1D();
+ ~AnimationNodeBlendSpace1D();
+};
+
+#endif // ANIMATION_BLEND_SPACE_1D_H
diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp
new file mode 100644
index 0000000000..3dc7f2a86f
--- /dev/null
+++ b/scene/animation/animation_blend_space_2d.cpp
@@ -0,0 +1,573 @@
+#include "animation_blend_space_2d.h"
+#include "math/delaunay.h"
+
+void AnimationNodeBlendSpace2D::get_parameter_list(List<PropertyInfo> *r_list) const {
+ r_list->push_back(PropertyInfo(Variant::VECTOR2, blend_position));
+}
+Variant AnimationNodeBlendSpace2D::get_parameter_default_value(const StringName &p_parameter) const {
+ 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", this, "_tree_changed", varray(), CONNECT_REFERENCE_COUNTED);
+ blend_points_used++;
+
+ if (auto_triangles) {
+ trianges_dirty = true;
+ }
+ emit_signal("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;
+ if (auto_triangles) {
+ trianges_dirty = true;
+ }
+}
+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", this, "_tree_changed");
+ }
+ blend_points[p_point].node = p_node;
+ blend_points[p_point].node->connect("tree_changed", this, "_tree_changed", varray(), CONNECT_REFERENCE_COUNTED);
+
+ emit_signal("tree_changed");
+}
+Vector2 AnimationNodeBlendSpace2D::get_blend_point_position(int p_point) const {
+ ERR_FAIL_INDEX_V(p_point, blend_points_used, Vector2());
+ return blend_points[p_point].position;
+}
+Ref<AnimationRootNode> AnimationNodeBlendSpace2D::get_blend_point_node(int p_point) const {
+ ERR_FAIL_INDEX_V(p_point, blend_points_used, Ref<AnimationRootNode>());
+ return blend_points[p_point].node;
+}
+void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) {
+ ERR_FAIL_INDEX(p_point, blend_points_used);
+
+ blend_points[p_point].node->disconnect("tree_changed", this, "_tree_changed");
+
+ 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(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("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(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::_update_triangles() {
+
+ if (!auto_triangles || !trianges_dirty)
+ return;
+
+ trianges_dirty = false;
+ triangles.clear();
+ if (blend_points_used < 3)
+ 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> triangles = Delaunay2D::triangulate(points);
+
+ for (int i = 0; i < triangles.size(); i++) {
+ add_triangle(triangles[i].points[0], triangles[i].points[1], triangles[i].points[2]);
+ }
+}
+
+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 (Geometry::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 = Geometry::get_closest_point_to_segment_2d(p_point, s);
+ if (first || closest.distance_to(p_point) < best_point.distance_to(p_point)) {
+ best_point = closest;
+ first = false;
+ }
+ }
+ }
+
+ return best_point;
+}
+
+void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights) {
+
+ if (p_pos.distance_squared_to(p_points[0]) < CMP_EPSILON2) {
+ r_weights[0] = 1;
+ r_weights[1] = 0;
+ r_weights[2] = 0;
+ return;
+ }
+ if (p_pos.distance_squared_to(p_points[1]) < CMP_EPSILON2) {
+ r_weights[0] = 0;
+ r_weights[1] = 1;
+ r_weights[2] = 0;
+ return;
+ }
+ if (p_pos.distance_squared_to(p_points[2]) < CMP_EPSILON2) {
+ 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;
+}
+
+float AnimationNodeBlendSpace2D::process(float p_time, bool p_seek) {
+
+ _update_triangles();
+
+ Vector2 blend_pos = get_parameter(blend_position);
+
+ if (triangles.size() == 0)
+ return 0;
+
+ 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 (Geometry::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 closest = Geometry::get_closest_point_to_segment_2d(blend_pos, s);
+ if (first || closest.distance_to(blend_pos) < best_point.distance_to(blend_pos)) {
+ best_point = closest;
+ 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(closest) / 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, 0); //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;
+ float mind;
+ 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
+ float t = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, blend_weights[j], FILTER_IGNORE, false);
+ if (first || t < mind) {
+ mind = t;
+ first = false;
+ }
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ //ignore
+ blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, 0, FILTER_IGNORE, false);
+ }
+ }
+ return mind;
+}
+
+String AnimationNodeBlendSpace2D::get_caption() const {
+ return "BlendSpace2D";
+}
+
+void AnimationNodeBlendSpace2D::_validate_property(PropertyInfo &property) const {
+ if (property.name.begins_with("blend_point_")) {
+ String left = property.name.get_slicec('/', 0);
+ int idx = left.get_slicec('_', 2).to_int();
+ if (idx >= blend_points_used) {
+ property.usage = 0;
+ }
+ }
+ AnimationRootNode::_validate_property(property);
+}
+
+void AnimationNodeBlendSpace2D::set_auto_triangles(bool p_enable) {
+ auto_triangles = p_enable;
+ if (auto_triangles) {
+ trianges_dirty = true;
+ }
+}
+
+bool AnimationNodeBlendSpace2D::get_auto_triangles() const {
+ return auto_triangles;
+}
+
+Ref<AnimationNode> AnimationNodeBlendSpace2D::get_child_by_name(const StringName &p_name) {
+ return get_blend_point_node(p_name.operator String().to_int());
+}
+
+void AnimationNodeBlendSpace2D::_tree_changed() {
+ emit_signal("tree_changed");
+}
+
+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("_tree_changed"), &AnimationNodeBlendSpace2D::_tree_changed);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "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_NOEDITOR | 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_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i);
+ }
+
+ ADD_PROPERTY(PropertyInfo(Variant::POOL_INT_ARRAY, "triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_triangles", "_get_triangles");
+
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_min_space", "get_min_space");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_space", "get_max_space");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_snap", "get_snap");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "x_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_x_label", "get_x_label");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "y_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_y_label", "get_y_label");
+}
+
+AnimationNodeBlendSpace2D::AnimationNodeBlendSpace2D() {
+
+ for (int i = 0; i < MAX_BLEND_POINTS; i++) {
+ blend_points[i].name = itos(i);
+ }
+ auto_triangles = true;
+ blend_points_used = 0;
+ max_space = Vector2(1, 1);
+ min_space = Vector2(-1, -1);
+ snap = Vector2(0.1, 0.1);
+ x_label = "x";
+ y_label = "y";
+ trianges_dirty = false;
+ blend_position = "blend_position";
+}
+
+AnimationNodeBlendSpace2D::~AnimationNodeBlendSpace2D() {
+}
diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h
new file mode 100644
index 0000000000..3e1c66c924
--- /dev/null
+++ b/scene/animation/animation_blend_space_2d.h
@@ -0,0 +1,101 @@
+#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)
+
+ enum {
+ MAX_BLEND_POINTS = 64
+ };
+
+ struct BlendPoint {
+ StringName name;
+ Ref<AnimationRootNode> node;
+ Vector2 position;
+ };
+
+ BlendPoint blend_points[MAX_BLEND_POINTS];
+ int blend_points_used;
+
+ struct BlendTriangle {
+ int points[3];
+ };
+
+ Vector<BlendTriangle> triangles;
+
+ StringName blend_position;
+ Vector2 max_space;
+ Vector2 min_space;
+ Vector2 snap;
+ String x_label;
+ String y_label;
+
+ 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;
+ bool trianges_dirty;
+
+ void _update_triangles();
+
+ void _tree_changed();
+
+protected:
+ virtual void _validate_property(PropertyInfo &property) const;
+ static void _bind_methods();
+
+public:
+ virtual void get_parameter_list(List<PropertyInfo> *r_list) const;
+ virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
+
+ virtual void get_child_nodes(List<ChildNode> *r_child_nodes);
+
+ 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 float process(float p_time, bool p_seek);
+ virtual String get_caption() const;
+
+ Vector2 get_closest_point(const Vector2 &p_point);
+
+ void set_auto_triangles(bool p_enable);
+ bool get_auto_triangles() const;
+
+ virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name);
+
+ AnimationNodeBlendSpace2D();
+ ~AnimationNodeBlendSpace2D();
+};
+
+#endif // ANIMATION_BLEND_SPACE_2D_H
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
new file mode 100644
index 0000000000..10bab3ce38
--- /dev/null
+++ b/scene/animation/animation_blend_tree.cpp
@@ -0,0 +1,1220 @@
+#include "animation_blend_tree.h"
+#include "scene/scene_string_names.h"
+
+void AnimationNodeAnimation::set_animation(const StringName &p_name) {
+ animation = p_name;
+ _change_notify("animation");
+}
+
+StringName AnimationNodeAnimation::get_animation() const {
+ return animation;
+}
+
+float AnimationNodeAnimation::get_playback_time() const {
+ return time;
+}
+
+Vector<String> (*AnimationNodeAnimation::get_editable_animation_list)() = NULL;
+
+void AnimationNodeAnimation::_validate_property(PropertyInfo &property) const {
+
+ if (property.name == "animation" && get_editable_animation_list) {
+ Vector<String> names = get_editable_animation_list();
+ String anims;
+ for (int i = 0; i < names.size(); i++) {
+
+ if (i > 0) {
+ anims += ",";
+ }
+ anims += String(names[i]);
+ }
+ if (anims != String()) {
+ property.hint = PROPERTY_HINT_ENUM;
+ property.hint_string = anims;
+ }
+ }
+}
+
+float AnimationNodeAnimation::process(float p_time, bool p_seek) {
+
+ AnimationPlayer *ap = state->player;
+ ERR_FAIL_COND_V(!ap, 0);
+
+ if (!ap->has_animation(animation)) {
+
+ AnimationNodeBlendTree *tree = Object::cast_to<AnimationNodeBlendTree>(parent);
+ if (tree) {
+ String name = tree->get_node_name(Ref<AnimationNodeAnimation>(this));
+ make_invalid(vformat(RTR("On BlendTree node '%s', animation not found: '%s'"), name, animation));
+
+ } else {
+ make_invalid(vformat(RTR("Animation not found: '%s'"), animation));
+ }
+
+ return 0;
+ }
+
+ Ref<Animation> anim = ap->get_animation(animation);
+
+ if (p_seek) {
+ time = p_time;
+ step = 0;
+ } else {
+ time = MAX(0, time + p_time);
+ step = p_time;
+ }
+
+ float anim_size = anim->get_length();
+
+ if (anim->has_loop()) {
+
+ if (anim_size) {
+ time = Math::fposmod(time, anim_size);
+ }
+
+ } else if (time > anim_size) {
+
+ time = anim_size;
+ }
+
+ blend_animation(animation, time, step, p_seek, 1.0);
+
+ return anim_size - time;
+}
+
+String AnimationNodeAnimation::get_caption() const {
+ return "Animation";
+}
+
+void AnimationNodeAnimation::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimationNodeAnimation::set_animation);
+ ClassDB::bind_method(D_METHOD("get_animation"), &AnimationNodeAnimation::get_animation);
+
+ ClassDB::bind_method(D_METHOD("get_playback_time"), &AnimationNodeAnimation::get_playback_time);
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "animation"), "set_animation", "get_animation");
+}
+
+AnimationNodeAnimation::AnimationNodeAnimation() {
+ last_version = 0;
+ skip = false;
+ time = 0;
+ step = 0;
+}
+
+////////////////////////////////////////////////////////
+
+void AnimationNodeOneShot::get_parameter_list(List<PropertyInfo> *r_list) const {
+ r_list->push_back(PropertyInfo(Variant::BOOL, active));
+ r_list->push_back(PropertyInfo(Variant::BOOL, prev_active, PROPERTY_HINT_NONE, "", 0));
+ r_list->push_back(PropertyInfo(Variant::REAL, time, PROPERTY_HINT_NONE, "", 0));
+ r_list->push_back(PropertyInfo(Variant::REAL, remaining, PROPERTY_HINT_NONE, "", 0));
+}
+Variant AnimationNodeOneShot::get_parameter_default_value(const StringName &p_parameter) const {
+ if (p_parameter == active || p_parameter == prev_active) {
+ return false;
+ } else {
+ return 0.0;
+ }
+}
+
+void AnimationNodeOneShot::set_fadein_time(float p_time) {
+
+ fade_in = p_time;
+}
+
+void AnimationNodeOneShot::set_fadeout_time(float p_time) {
+
+ fade_out = p_time;
+}
+
+float AnimationNodeOneShot::get_fadein_time() const {
+
+ return fade_in;
+}
+float AnimationNodeOneShot::get_fadeout_time() const {
+
+ return fade_out;
+}
+
+void AnimationNodeOneShot::set_autorestart(bool p_active) {
+
+ autorestart = p_active;
+}
+void AnimationNodeOneShot::set_autorestart_delay(float p_time) {
+
+ autorestart_delay = p_time;
+}
+void AnimationNodeOneShot::set_autorestart_random_delay(float p_time) {
+
+ autorestart_random_delay = p_time;
+}
+
+bool AnimationNodeOneShot::has_autorestart() const {
+
+ return autorestart;
+}
+float AnimationNodeOneShot::get_autorestart_delay() const {
+
+ return autorestart_delay;
+}
+float AnimationNodeOneShot::get_autorestart_random_delay() const {
+
+ return autorestart_random_delay;
+}
+
+void AnimationNodeOneShot::set_mix_mode(MixMode p_mix) {
+
+ mix = p_mix;
+}
+AnimationNodeOneShot::MixMode AnimationNodeOneShot::get_mix_mode() const {
+
+ return mix;
+}
+
+String AnimationNodeOneShot::get_caption() const {
+ return "OneShot";
+}
+
+bool AnimationNodeOneShot::has_filter() const {
+ return true;
+}
+
+float AnimationNodeOneShot::process(float p_time, bool p_seek) {
+
+ bool active = get_parameter(this->active);
+ bool prev_active = get_parameter(this->prev_active);
+ float time = get_parameter(this->time);
+ float remaining = get_parameter(this->remaining);
+
+ if (!active) {
+ //make it as if this node doesn't exist, pass input 0 by.
+ if (prev_active) {
+ set_parameter(this->prev_active, false);
+ }
+ return blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync);
+ }
+
+ bool os_seek = p_seek;
+
+ if (p_seek)
+ time = p_time;
+ bool do_start = !prev_active;
+
+ if (do_start) {
+ time = 0;
+ os_seek = true;
+ set_parameter(this->prev_active, true);
+ }
+
+ float blend;
+
+ if (time < fade_in) {
+
+ if (fade_in > 0)
+ blend = time / fade_in;
+ else
+ blend = 0; //wtf
+
+ } else if (!do_start && remaining < fade_out) {
+
+ if (fade_out)
+ blend = (remaining / fade_out);
+ else
+ blend = 1.0;
+ } else
+ blend = 1.0;
+
+ float main_rem;
+ if (mix == MIX_MODE_ADD) {
+ main_rem = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync);
+ } else {
+ main_rem = blend_input(0, p_time, p_seek, 1.0 - blend, FILTER_BLEND, !sync);
+ }
+
+ float os_rem = blend_input(1, os_seek ? time : p_time, os_seek, blend, FILTER_PASS, false);
+
+ if (do_start) {
+ remaining = os_rem;
+ do_start = false;
+ }
+
+ if (!p_seek) {
+ time += p_time;
+ remaining = os_rem;
+ if (remaining <= 0) {
+ set_parameter(this->active, false);
+ set_parameter(this->prev_active, false);
+ }
+ }
+
+ set_parameter(this->time, time);
+ set_parameter(this->remaining, remaining);
+
+ return MAX(main_rem, remaining);
+}
+void AnimationNodeOneShot::set_use_sync(bool p_sync) {
+
+ sync = p_sync;
+}
+
+bool AnimationNodeOneShot::is_using_sync() const {
+
+ return sync;
+}
+
+void AnimationNodeOneShot::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_fadein_time", "time"), &AnimationNodeOneShot::set_fadein_time);
+ ClassDB::bind_method(D_METHOD("get_fadein_time"), &AnimationNodeOneShot::get_fadein_time);
+
+ ClassDB::bind_method(D_METHOD("set_fadeout_time", "time"), &AnimationNodeOneShot::set_fadeout_time);
+ ClassDB::bind_method(D_METHOD("get_fadeout_time"), &AnimationNodeOneShot::get_fadeout_time);
+
+ ClassDB::bind_method(D_METHOD("set_autorestart", "enable"), &AnimationNodeOneShot::set_autorestart);
+ ClassDB::bind_method(D_METHOD("has_autorestart"), &AnimationNodeOneShot::has_autorestart);
+
+ ClassDB::bind_method(D_METHOD("set_autorestart_delay", "enable"), &AnimationNodeOneShot::set_autorestart_delay);
+ ClassDB::bind_method(D_METHOD("get_autorestart_delay"), &AnimationNodeOneShot::get_autorestart_delay);
+
+ ClassDB::bind_method(D_METHOD("set_autorestart_random_delay", "enable"), &AnimationNodeOneShot::set_autorestart_random_delay);
+ ClassDB::bind_method(D_METHOD("get_autorestart_random_delay"), &AnimationNodeOneShot::get_autorestart_random_delay);
+
+ ClassDB::bind_method(D_METHOD("set_mix_mode", "mode"), &AnimationNodeOneShot::set_mix_mode);
+ ClassDB::bind_method(D_METHOD("get_mix_mode"), &AnimationNodeOneShot::get_mix_mode);
+
+ ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeOneShot::set_use_sync);
+ ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeOneShot::is_using_sync);
+
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "fadein_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_fadein_time", "get_fadein_time");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "fadeout_time", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_fadeout_time", "get_fadeout_time");
+
+ ADD_GROUP("autorestart_", "Auto Restart");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autorestart"), "set_autorestart", "has_autorestart");
+
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "autorestart_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_autorestart_delay", "get_autorestart_delay");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "autorestart_random_delay", PROPERTY_HINT_RANGE, "0,60,0.01,or_greater"), "set_autorestart_random_delay", "get_autorestart_random_delay");
+
+ ADD_GROUP("", "");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
+
+ BIND_ENUM_CONSTANT(MIX_MODE_BLEND)
+ BIND_ENUM_CONSTANT(MIX_MODE_ADD)
+}
+
+AnimationNodeOneShot::AnimationNodeOneShot() {
+
+ add_input("in");
+ add_input("shot");
+
+ fade_in = 0.1;
+ fade_out = 0.1;
+ autorestart = false;
+ autorestart_delay = 1;
+
+ mix = MIX_MODE_BLEND;
+ sync = false;
+
+ active = "active";
+ prev_active = "prev_active";
+ time = "time";
+ remaining = "remaining";
+}
+
+////////////////////////////////////////////////
+
+void AnimationNodeAdd2::get_parameter_list(List<PropertyInfo> *r_list) const {
+ r_list->push_back(PropertyInfo(Variant::REAL, add_amount, PROPERTY_HINT_RANGE, "0,1,0.01"));
+}
+Variant AnimationNodeAdd2::get_parameter_default_value(const StringName &p_parameter) const {
+ return 0;
+}
+
+String AnimationNodeAdd2::get_caption() const {
+ return "Add2";
+}
+void AnimationNodeAdd2::set_use_sync(bool p_sync) {
+
+ sync = p_sync;
+}
+
+bool AnimationNodeAdd2::is_using_sync() const {
+
+ return sync;
+}
+
+bool AnimationNodeAdd2::has_filter() const {
+
+ return true;
+}
+
+float AnimationNodeAdd2::process(float p_time, bool p_seek) {
+
+ float amount = get_parameter(add_amount);
+ float rem0 = blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync);
+ blend_input(1, p_time, p_seek, amount, FILTER_PASS, !sync);
+
+ return rem0;
+}
+
+void AnimationNodeAdd2::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeAdd2::set_use_sync);
+ ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeAdd2::is_using_sync);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
+}
+
+AnimationNodeAdd2::AnimationNodeAdd2() {
+
+ add_amount = "add_amount";
+ add_input("in");
+ add_input("add");
+ sync = false;
+}
+
+////////////////////////////////////////////////
+
+void AnimationNodeAdd3::get_parameter_list(List<PropertyInfo> *r_list) const {
+ r_list->push_back(PropertyInfo(Variant::REAL, add_amount, PROPERTY_HINT_RANGE, "-1,1,0.01"));
+}
+Variant AnimationNodeAdd3::get_parameter_default_value(const StringName &p_parameter) const {
+ return 0;
+}
+
+String AnimationNodeAdd3::get_caption() const {
+ return "Add3";
+}
+void AnimationNodeAdd3::set_use_sync(bool p_sync) {
+
+ sync = p_sync;
+}
+
+bool AnimationNodeAdd3::is_using_sync() const {
+
+ return sync;
+}
+
+bool AnimationNodeAdd3::has_filter() const {
+
+ return true;
+}
+
+float AnimationNodeAdd3::process(float p_time, bool p_seek) {
+
+ float amount = get_parameter(add_amount);
+ blend_input(0, p_time, p_seek, MAX(0, -amount), FILTER_PASS, !sync);
+ float rem0 = blend_input(1, p_time, p_seek, 1.0, FILTER_IGNORE, !sync);
+ blend_input(2, p_time, p_seek, MAX(0, amount), FILTER_PASS, !sync);
+
+ return rem0;
+}
+
+void AnimationNodeAdd3::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeAdd3::set_use_sync);
+ ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeAdd3::is_using_sync);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
+}
+
+AnimationNodeAdd3::AnimationNodeAdd3() {
+
+ add_amount = "add_amount";
+ add_input("-add");
+ add_input("in");
+ add_input("+add");
+ sync = false;
+}
+/////////////////////////////////////////////
+
+void AnimationNodeBlend2::get_parameter_list(List<PropertyInfo> *r_list) const {
+ r_list->push_back(PropertyInfo(Variant::REAL, blend_amount, PROPERTY_HINT_RANGE, "0,1,0.01"));
+}
+Variant AnimationNodeBlend2::get_parameter_default_value(const StringName &p_parameter) const {
+ return 0; //for blend amount
+}
+
+String AnimationNodeBlend2::get_caption() const {
+ return "Blend2";
+}
+
+float AnimationNodeBlend2::process(float p_time, bool p_seek) {
+
+ float amount = get_parameter(blend_amount);
+
+ float rem0 = blend_input(0, p_time, p_seek, 1.0 - amount, FILTER_BLEND, !sync);
+ float rem1 = blend_input(1, p_time, p_seek, amount, FILTER_PASS, !sync);
+
+ return amount > 0.5 ? rem1 : rem0; //hacky but good enough
+}
+
+void AnimationNodeBlend2::set_use_sync(bool p_sync) {
+
+ sync = p_sync;
+}
+
+bool AnimationNodeBlend2::is_using_sync() const {
+
+ return sync;
+}
+
+bool AnimationNodeBlend2::has_filter() const {
+
+ return true;
+}
+void AnimationNodeBlend2::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlend2::set_use_sync);
+ ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlend2::is_using_sync);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
+}
+AnimationNodeBlend2::AnimationNodeBlend2() {
+ blend_amount = "blend_amount";
+ add_input("in");
+ add_input("blend");
+ sync = false;
+}
+
+//////////////////////////////////////
+
+void AnimationNodeBlend3::get_parameter_list(List<PropertyInfo> *r_list) const {
+ r_list->push_back(PropertyInfo(Variant::REAL, blend_amount, PROPERTY_HINT_RANGE, "-1,1,0.01"));
+}
+Variant AnimationNodeBlend3::get_parameter_default_value(const StringName &p_parameter) const {
+ return 0; //for blend amount
+}
+
+String AnimationNodeBlend3::get_caption() const {
+ return "Blend3";
+}
+
+void AnimationNodeBlend3::set_use_sync(bool p_sync) {
+
+ sync = p_sync;
+}
+
+bool AnimationNodeBlend3::is_using_sync() const {
+
+ return sync;
+}
+
+float AnimationNodeBlend3::process(float p_time, bool p_seek) {
+
+ float amount = get_parameter(blend_amount);
+ float rem0 = blend_input(0, p_time, p_seek, MAX(0, -amount), FILTER_IGNORE, !sync);
+ float rem1 = blend_input(1, p_time, p_seek, 1.0 - ABS(amount), FILTER_IGNORE, !sync);
+ float rem2 = blend_input(2, p_time, p_seek, MAX(0, amount), FILTER_IGNORE, !sync);
+
+ return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); //hacky but good enough
+}
+
+void AnimationNodeBlend3::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeBlend3::set_use_sync);
+ ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeBlend3::is_using_sync);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
+}
+AnimationNodeBlend3::AnimationNodeBlend3() {
+ blend_amount = "blend_amount";
+ add_input("-blend");
+ add_input("in");
+ add_input("+blend");
+ sync = false;
+}
+
+/////////////////////////////////
+
+void AnimationNodeTimeScale::get_parameter_list(List<PropertyInfo> *r_list) const {
+ r_list->push_back(PropertyInfo(Variant::REAL, scale, PROPERTY_HINT_RANGE, "0,32,0.01,or_greater"));
+}
+Variant AnimationNodeTimeScale::get_parameter_default_value(const StringName &p_parameter) const {
+ return 1.0; //initial timescale
+}
+
+String AnimationNodeTimeScale::get_caption() const {
+ return "TimeScale";
+}
+
+float AnimationNodeTimeScale::process(float p_time, bool p_seek) {
+
+ float scale = get_parameter(this->scale);
+ if (p_seek) {
+ return blend_input(0, p_time, true, 1.0, FILTER_IGNORE, false);
+ } else {
+ return blend_input(0, p_time * scale, false, 1.0, FILTER_IGNORE, false);
+ }
+}
+
+void AnimationNodeTimeScale::_bind_methods() {
+}
+AnimationNodeTimeScale::AnimationNodeTimeScale() {
+ scale = "scale";
+ add_input("in");
+}
+
+////////////////////////////////////
+
+void AnimationNodeTimeSeek::get_parameter_list(List<PropertyInfo> *r_list) const {
+ r_list->push_back(PropertyInfo(Variant::REAL, seek_pos, PROPERTY_HINT_RANGE, "-1,3600,0.01,or_greater"));
+}
+Variant AnimationNodeTimeSeek::get_parameter_default_value(const StringName &p_parameter) const {
+ return 1.0; //initial timescale
+}
+
+String AnimationNodeTimeSeek::get_caption() const {
+ return "Seek";
+}
+
+float AnimationNodeTimeSeek::process(float p_time, bool p_seek) {
+
+ float seek_pos = get_parameter(this->seek_pos);
+ if (p_seek) {
+ return blend_input(0, p_time, true, 1.0, FILTER_IGNORE, false);
+ } else if (seek_pos >= 0) {
+ float ret = blend_input(0, seek_pos, true, 1.0, FILTER_IGNORE, false);
+ set_parameter(this->seek_pos, -1.0); //reset
+ _change_notify("seek_pos");
+ return ret;
+ } else {
+ return blend_input(0, p_time, false, 1.0, FILTER_IGNORE, false);
+ }
+}
+
+void AnimationNodeTimeSeek::_bind_methods() {
+}
+
+AnimationNodeTimeSeek::AnimationNodeTimeSeek() {
+ add_input("in");
+ seek_pos = "seek_position";
+}
+
+/////////////////////////////////////////////////
+
+void AnimationNodeTransition::get_parameter_list(List<PropertyInfo> *r_list) const {
+
+ String anims;
+ for (int i = 0; i < enabled_inputs; i++) {
+ if (i > 0) {
+ anims += ",";
+ }
+ anims += inputs[i].name;
+ }
+
+ r_list->push_back(PropertyInfo(Variant::INT, current, PROPERTY_HINT_ENUM, anims));
+ r_list->push_back(PropertyInfo(Variant::INT, prev_current, PROPERTY_HINT_NONE, "", 0));
+ r_list->push_back(PropertyInfo(Variant::INT, prev, PROPERTY_HINT_NONE, "", 0));
+ r_list->push_back(PropertyInfo(Variant::REAL, time, PROPERTY_HINT_NONE, "", 0));
+ r_list->push_back(PropertyInfo(Variant::REAL, prev_xfading, PROPERTY_HINT_NONE, "", 0));
+}
+Variant AnimationNodeTransition::get_parameter_default_value(const StringName &p_parameter) const {
+ if (p_parameter == time || p_parameter == prev_xfading) {
+ return 0.0;
+ } else if (p_parameter == prev || p_parameter == prev_current) {
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+String AnimationNodeTransition::get_caption() const {
+ return "Transition";
+}
+
+void AnimationNodeTransition::_update_inputs() {
+ while (get_input_count() < enabled_inputs) {
+ add_input(inputs[get_input_count()].name);
+ }
+
+ while (get_input_count() > enabled_inputs) {
+ remove_input(get_input_count() - 1);
+ }
+}
+
+void AnimationNodeTransition::set_enabled_inputs(int p_inputs) {
+ ERR_FAIL_INDEX(p_inputs, MAX_INPUTS);
+ enabled_inputs = p_inputs;
+ _update_inputs();
+}
+
+int AnimationNodeTransition::get_enabled_inputs() {
+ return enabled_inputs;
+}
+
+void AnimationNodeTransition::set_input_as_auto_advance(int p_input, bool p_enable) {
+ ERR_FAIL_INDEX(p_input, MAX_INPUTS);
+ inputs[p_input].auto_advance = p_enable;
+}
+
+bool AnimationNodeTransition::is_input_set_as_auto_advance(int p_input) const {
+ ERR_FAIL_INDEX_V(p_input, MAX_INPUTS, false);
+ return inputs[p_input].auto_advance;
+}
+
+void AnimationNodeTransition::set_input_caption(int p_input, const String &p_name) {
+ ERR_FAIL_INDEX(p_input, MAX_INPUTS);
+ inputs[p_input].name = p_name;
+ set_input_name(p_input, p_name);
+}
+
+String AnimationNodeTransition::get_input_caption(int p_input) const {
+ ERR_FAIL_INDEX_V(p_input, MAX_INPUTS, String());
+ return inputs[p_input].name;
+}
+
+#if 0
+ Ref<AnimationNodeBlendTree> tree = get_parent();
+
+ if (tree.is_valid() && current >= 0) {
+ prev = current;
+ prev_xfading = xfade;
+ time = 0;
+ current = p_current;
+ switched = true;
+ _change_notify("current");
+ } else {
+ current = p_current;
+ }
+#endif
+
+void AnimationNodeTransition::set_cross_fade_time(float p_fade) {
+ xfade = p_fade;
+}
+
+float AnimationNodeTransition::get_cross_fade_time() const {
+ return xfade;
+}
+
+float AnimationNodeTransition::process(float p_time, bool p_seek) {
+
+ int current = get_parameter(this->current);
+ int prev = get_parameter(this->prev);
+ int prev_current = get_parameter(this->prev_current);
+
+ float time = get_parameter(this->time);
+ float prev_xfading = get_parameter(this->prev_xfading);
+
+ bool switched = current != prev_current;
+
+ if (switched) {
+ set_parameter(this->prev_current, current);
+ set_parameter(this->prev, prev_current);
+
+ prev = prev_current;
+ prev_xfading = xfade;
+ time = 0;
+ switched = true;
+ }
+
+ if (current < 0 || current >= enabled_inputs || prev >= enabled_inputs) {
+ return 0;
+ }
+
+ float rem = 0;
+
+ if (prev < 0) { // process current animation, check for transition
+
+ rem = blend_input(current, p_time, p_seek, 1.0, FILTER_IGNORE, false);
+
+ if (p_seek)
+ time = p_time;
+ else
+ time += p_time;
+
+ if (inputs[current].auto_advance && rem <= xfade) {
+
+ set_parameter(this->current, (current + 1) % enabled_inputs);
+ }
+
+ } else { // cross-fading from prev to current
+
+ float blend = xfade ? (prev_xfading / xfade) : 1;
+
+ if (!p_seek && switched) { //just switched, seek to start of current
+
+ rem = blend_input(current, 0, true, 1.0 - blend, FILTER_IGNORE, false);
+ } else {
+
+ rem = blend_input(current, p_time, p_seek, 1.0 - blend, FILTER_IGNORE, false);
+ }
+
+ switched = false;
+
+ if (p_seek) { // don't seek prev animation
+ blend_input(prev, 0, false, blend, FILTER_IGNORE, false);
+ time = p_time;
+ } else {
+ blend_input(prev, p_time, false, blend, FILTER_IGNORE, false);
+ time += p_time;
+ prev_xfading -= p_time;
+ if (prev_xfading < 0) {
+ set_parameter(this->prev, -1);
+ }
+ }
+ }
+
+ set_parameter(this->time, time);
+ set_parameter(this->prev_xfading, prev_xfading);
+
+ return rem;
+}
+
+void AnimationNodeTransition::_validate_property(PropertyInfo &property) const {
+
+ if (property.name.begins_with("input_")) {
+ String n = property.name.get_slicec('/', 0).get_slicec('_', 1);
+ if (n != "count") {
+ int idx = n.to_int();
+ if (idx >= enabled_inputs) {
+ property.usage = 0;
+ }
+ }
+ }
+
+ AnimationNode::_validate_property(property);
+}
+
+void AnimationNodeTransition::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_enabled_inputs", "amount"), &AnimationNodeTransition::set_enabled_inputs);
+ ClassDB::bind_method(D_METHOD("get_enabled_inputs"), &AnimationNodeTransition::get_enabled_inputs);
+
+ ClassDB::bind_method(D_METHOD("set_input_as_auto_advance", "input", "enable"), &AnimationNodeTransition::set_input_as_auto_advance);
+ ClassDB::bind_method(D_METHOD("is_input_set_as_auto_advance", "input"), &AnimationNodeTransition::is_input_set_as_auto_advance);
+
+ ClassDB::bind_method(D_METHOD("set_input_caption", "input", "caption"), &AnimationNodeTransition::set_input_caption);
+ ClassDB::bind_method(D_METHOD("get_input_caption", "input"), &AnimationNodeTransition::get_input_caption);
+
+ ClassDB::bind_method(D_METHOD("set_cross_fade_time", "time"), &AnimationNodeTransition::set_cross_fade_time);
+ ClassDB::bind_method(D_METHOD("get_cross_fade_time"), &AnimationNodeTransition::get_cross_fade_time);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "input_count", PROPERTY_HINT_RANGE, "0,64,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_enabled_inputs", "get_enabled_inputs");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "xfade_time", PROPERTY_HINT_RANGE, "0,120,0.01"), "set_cross_fade_time", "get_cross_fade_time");
+
+ for (int i = 0; i < MAX_INPUTS; i++) {
+ ADD_PROPERTYI(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name"), "set_input_caption", "get_input_caption", i);
+ ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/auto_advance"), "set_input_as_auto_advance", "is_input_set_as_auto_advance", i);
+ }
+}
+
+AnimationNodeTransition::AnimationNodeTransition() {
+
+ prev_xfading = "prev_xfading";
+ prev = "prev";
+ time = "time";
+ current = "current";
+ prev_current = "prev_current";
+ ;
+
+ enabled_inputs = 0;
+ for (int i = 0; i < MAX_INPUTS; i++) {
+ inputs[i].auto_advance = false;
+ inputs[i].name = itos(i + 1);
+ }
+}
+
+/////////////////////
+
+String AnimationNodeOutput::get_caption() const {
+ return "Output";
+}
+
+float AnimationNodeOutput::process(float p_time, bool p_seek) {
+ return blend_input(0, p_time, p_seek, 1.0);
+}
+
+AnimationNodeOutput::AnimationNodeOutput() {
+ add_input("output");
+}
+
+///////////////////////////////////////////////////////
+void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNode> p_node, const Vector2 &p_position) {
+
+ ERR_FAIL_COND(nodes.has(p_name));
+ ERR_FAIL_COND(p_node.is_null());
+ ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output);
+ ERR_FAIL_COND(String(p_name).find("/") != -1);
+
+ Node n;
+ n.node = p_node;
+ n.position = p_position;
+ n.connections.resize(n.node->get_input_count());
+ nodes[p_name] = n;
+
+ emit_changed();
+ emit_signal("tree_changed");
+
+ p_node->connect("tree_changed", this, "_tree_changed", varray(), CONNECT_REFERENCE_COUNTED);
+ p_node->connect("changed", this, "_node_changed", varray(p_name), CONNECT_REFERENCE_COUNTED);
+}
+
+Ref<AnimationNode> AnimationNodeBlendTree::get_node(const StringName &p_name) const {
+
+ ERR_FAIL_COND_V(!nodes.has(p_name), Ref<AnimationNode>());
+
+ return nodes[p_name].node;
+}
+
+StringName AnimationNodeBlendTree::get_node_name(const Ref<AnimationNode> &p_node) const {
+ for (Map<StringName, Node>::Element *E = nodes.front(); E; E = E->next()) {
+ if (E->get().node == p_node) {
+ return E->key();
+ }
+ }
+
+ ERR_FAIL_V(StringName());
+}
+
+void AnimationNodeBlendTree::set_node_position(const StringName &p_node, const Vector2 &p_position) {
+ ERR_FAIL_COND(!nodes.has(p_node));
+ nodes[p_node].position = p_position;
+}
+
+Vector2 AnimationNodeBlendTree::get_node_position(const StringName &p_node) const {
+ ERR_FAIL_COND_V(!nodes.has(p_node), Vector2());
+ return nodes[p_node].position;
+}
+
+void AnimationNodeBlendTree::get_child_nodes(List<ChildNode> *r_child_nodes) {
+ Vector<StringName> ns;
+
+ for (Map<StringName, Node>::Element *E = nodes.front(); E; E = E->next()) {
+ ns.push_back(E->key());
+ }
+
+ ns.sort_custom<StringName::AlphCompare>();
+
+ for (int i = 0; i < ns.size(); i++) {
+ ChildNode cn;
+ cn.name = ns[i];
+ cn.node = nodes[cn.name].node;
+ r_child_nodes->push_back(cn);
+ }
+}
+
+bool AnimationNodeBlendTree::has_node(const StringName &p_name) const {
+ return nodes.has(p_name);
+}
+Vector<StringName> AnimationNodeBlendTree::get_node_connection_array(const StringName &p_name) const {
+
+ ERR_FAIL_COND_V(!nodes.has(p_name), Vector<StringName>());
+ return nodes[p_name].connections;
+}
+void AnimationNodeBlendTree::remove_node(const StringName &p_name) {
+
+ ERR_FAIL_COND(!nodes.has(p_name));
+ ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output); //can't delete output
+
+ {
+ Ref<AnimationNode> node = nodes[p_name].node;
+ node->disconnect("tree_changed", this, "_tree_changed");
+ node->disconnect("changed", this, "_node_changed");
+ }
+
+ nodes.erase(p_name);
+
+ //erase connections to name
+ for (Map<StringName, Node>::Element *E = nodes.front(); E; E = E->next()) {
+ for (int i = 0; i < E->get().connections.size(); i++) {
+ if (E->get().connections[i] == p_name) {
+ E->get().connections.write[i] = StringName();
+ }
+ }
+ }
+
+ emit_changed();
+ emit_signal("tree_changed");
+}
+
+void AnimationNodeBlendTree::rename_node(const StringName &p_name, const StringName &p_new_name) {
+
+ ERR_FAIL_COND(!nodes.has(p_name));
+ ERR_FAIL_COND(nodes.has(p_new_name));
+ ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output);
+ ERR_FAIL_COND(p_new_name == SceneStringNames::get_singleton()->output);
+
+ nodes[p_name].node->disconnect("changed", this, "_node_changed");
+
+ nodes[p_new_name] = nodes[p_name];
+ nodes.erase(p_name);
+
+ //rename connections
+ for (Map<StringName, Node>::Element *E = nodes.front(); E; E = E->next()) {
+
+ for (int i = 0; i < E->get().connections.size(); i++) {
+ if (E->get().connections[i] == p_name) {
+ E->get().connections.write[i] = p_new_name;
+ }
+ }
+ }
+ //connection must be done with new name
+ nodes[p_new_name].node->connect("changed", this, "_node_changed", varray(p_new_name), CONNECT_REFERENCE_COUNTED);
+
+ emit_signal("tree_changed");
+}
+
+void AnimationNodeBlendTree::connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) {
+
+ ERR_FAIL_COND(!nodes.has(p_output_node));
+ ERR_FAIL_COND(!nodes.has(p_input_node));
+ ERR_FAIL_COND(p_output_node == SceneStringNames::get_singleton()->output);
+ ERR_FAIL_COND(p_input_node == p_output_node);
+
+ Ref<AnimationNode> input = nodes[p_input_node].node;
+ ERR_FAIL_INDEX(p_input_index, nodes[p_input_node].connections.size());
+
+ for (Map<StringName, Node>::Element *E = nodes.front(); E; E = E->next()) {
+ for (int i = 0; i < E->get().connections.size(); i++) {
+ StringName output = E->get().connections[i];
+ ERR_FAIL_COND(output == p_output_node);
+ }
+ }
+
+ nodes[p_input_node].connections.write[p_input_index] = p_output_node;
+
+ emit_changed();
+}
+
+void AnimationNodeBlendTree::disconnect_node(const StringName &p_node, int p_input_index) {
+
+ ERR_FAIL_COND(!nodes.has(p_node));
+
+ Ref<AnimationNode> input = nodes[p_node].node;
+ ERR_FAIL_INDEX(p_input_index, nodes[p_node].connections.size());
+
+ nodes[p_node].connections.write[p_input_index] = StringName();
+}
+
+AnimationNodeBlendTree::ConnectionError AnimationNodeBlendTree::can_connect_node(const StringName &p_input_node, int p_input_index, const StringName &p_output_node) const {
+
+ if (!nodes.has(p_output_node) || p_output_node == SceneStringNames::get_singleton()->output) {
+ return CONNECTION_ERROR_NO_OUTPUT;
+ }
+
+ if (!nodes.has(p_input_node)) {
+ return CONNECTION_ERROR_NO_INPUT;
+ }
+
+ if (!nodes.has(p_input_node)) {
+ return CONNECTION_ERROR_SAME_NODE;
+ }
+
+ Ref<AnimationNode> input = nodes[p_input_node].node;
+
+ if (p_input_index < 0 || p_input_index >= nodes[p_input_node].connections.size()) {
+ return CONNECTION_ERROR_NO_INPUT_INDEX;
+ }
+
+ if (nodes[p_input_node].connections[p_input_index] != StringName()) {
+ return CONNECTION_ERROR_CONNECTION_EXISTS;
+ }
+
+ for (Map<StringName, Node>::Element *E = nodes.front(); E; E = E->next()) {
+ for (int i = 0; i < E->get().connections.size(); i++) {
+ StringName output = E->get().connections[i];
+ if (output == p_output_node) {
+ return CONNECTION_ERROR_CONNECTION_EXISTS;
+ }
+ }
+ }
+ return CONNECTION_OK;
+}
+
+void AnimationNodeBlendTree::get_node_connections(List<NodeConnection> *r_connections) const {
+
+ for (Map<StringName, Node>::Element *E = nodes.front(); E; E = E->next()) {
+ for (int i = 0; i < E->get().connections.size(); i++) {
+ StringName output = E->get().connections[i];
+ if (output != StringName()) {
+ NodeConnection nc;
+ nc.input_node = E->key();
+ nc.input_index = i;
+ nc.output_node = output;
+ r_connections->push_back(nc);
+ }
+ }
+ }
+}
+
+String AnimationNodeBlendTree::get_caption() const {
+ return "BlendTree";
+}
+
+float AnimationNodeBlendTree::process(float p_time, bool p_seek) {
+
+ Ref<AnimationNodeOutput> output = nodes[SceneStringNames::get_singleton()->output].node;
+ return _blend_node("output", nodes[SceneStringNames::get_singleton()->output].connections, this, output, p_time, p_seek, 1.0);
+}
+
+void AnimationNodeBlendTree::get_node_list(List<StringName> *r_list) {
+
+ for (Map<StringName, Node>::Element *E = nodes.front(); E; E = E->next()) {
+ r_list->push_back(E->key());
+ }
+}
+
+void AnimationNodeBlendTree::set_graph_offset(const Vector2 &p_graph_offset) {
+
+ graph_offset = p_graph_offset;
+}
+
+Vector2 AnimationNodeBlendTree::get_graph_offset() const {
+
+ return graph_offset;
+}
+
+Ref<AnimationNode> AnimationNodeBlendTree::get_child_by_name(const StringName &p_name) {
+ return get_node(p_name);
+}
+
+bool AnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_value) {
+
+ String name = p_name;
+ if (name.begins_with("nodes/")) {
+
+ String node_name = name.get_slicec('/', 1);
+ String what = name.get_slicec('/', 2);
+
+ if (what == "node") {
+ Ref<AnimationNode> anode = p_value;
+ if (anode.is_valid()) {
+ add_node(node_name, p_value);
+ }
+ return true;
+ }
+
+ if (what == "position") {
+
+ if (nodes.has(node_name)) {
+ nodes[node_name].position = p_value;
+ }
+ return true;
+ }
+ } else if (name == "node_connections") {
+
+ Array conns = p_value;
+ ERR_FAIL_COND_V(conns.size() % 3 != 0, false);
+
+ for (int i = 0; i < conns.size(); i += 3) {
+ connect_node(conns[i], conns[i + 1], conns[i + 2]);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool AnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_ret) const {
+
+ String name = p_name;
+ if (name.begins_with("nodes/")) {
+ String node_name = name.get_slicec('/', 1);
+ String what = name.get_slicec('/', 2);
+
+ if (what == "node") {
+ if (nodes.has(node_name)) {
+ r_ret = nodes[node_name].node;
+ return true;
+ }
+ }
+
+ if (what == "position") {
+
+ if (nodes.has(node_name)) {
+ r_ret = nodes[node_name].position;
+ return true;
+ }
+ }
+ } else if (name == "node_connections") {
+ List<NodeConnection> nc;
+ get_node_connections(&nc);
+ Array conns;
+ conns.resize(nc.size() * 3);
+
+ int idx = 0;
+ for (List<NodeConnection>::Element *E = nc.front(); E; E = E->next()) {
+ conns[idx * 3 + 0] = E->get().input_node;
+ conns[idx * 3 + 1] = E->get().input_index;
+ conns[idx * 3 + 2] = E->get().output_node;
+ idx++;
+ }
+
+ r_ret = conns;
+ return true;
+ }
+
+ return false;
+}
+void AnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) const {
+
+ List<StringName> names;
+ for (Map<StringName, Node>::Element *E = nodes.front(); E; E = E->next()) {
+ names.push_back(E->key());
+ }
+ names.sort_custom<StringName::AlphCompare>();
+
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ String name = E->get();
+ if (name != "output") {
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR));
+ }
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ }
+
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "node_connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+}
+
+void AnimationNodeBlendTree::_tree_changed() {
+ emit_signal("tree_changed");
+}
+
+void AnimationNodeBlendTree::_node_changed(const StringName &p_node) {
+
+ ERR_FAIL_COND(!nodes.has(p_node));
+ nodes[p_node].connections.resize(nodes[p_node].node->get_input_count());
+}
+
+void AnimationNodeBlendTree::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("add_node", "name", "node", "position"), &AnimationNodeBlendTree::add_node, DEFVAL(Vector2()));
+ ClassDB::bind_method(D_METHOD("get_node", "name"), &AnimationNodeBlendTree::get_node);
+ ClassDB::bind_method(D_METHOD("remove_node", "name"), &AnimationNodeBlendTree::remove_node);
+ ClassDB::bind_method(D_METHOD("rename_node", "name", "new_name"), &AnimationNodeBlendTree::rename_node);
+ ClassDB::bind_method(D_METHOD("has_node", "name"), &AnimationNodeBlendTree::has_node);
+ ClassDB::bind_method(D_METHOD("connect_node", "input_node", "input_index", "output_node"), &AnimationNodeBlendTree::connect_node);
+ ClassDB::bind_method(D_METHOD("disconnect_node", "input_node", "input_index"), &AnimationNodeBlendTree::disconnect_node);
+
+ ClassDB::bind_method(D_METHOD("set_node_position", "name", "position"), &AnimationNodeBlendTree::set_node_position);
+ ClassDB::bind_method(D_METHOD("get_node_position", "name"), &AnimationNodeBlendTree::get_node_position);
+
+ ClassDB::bind_method(D_METHOD("set_graph_offset", "offset"), &AnimationNodeBlendTree::set_graph_offset);
+ ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeBlendTree::get_graph_offset);
+
+ ClassDB::bind_method(D_METHOD("_tree_changed"), &AnimationNodeBlendTree::_tree_changed);
+ ClassDB::bind_method(D_METHOD("_node_changed", "node"), &AnimationNodeBlendTree::_node_changed);
+
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_graph_offset", "get_graph_offset");
+
+ BIND_CONSTANT(CONNECTION_OK);
+ BIND_CONSTANT(CONNECTION_ERROR_NO_INPUT);
+ BIND_CONSTANT(CONNECTION_ERROR_NO_INPUT_INDEX);
+ BIND_CONSTANT(CONNECTION_ERROR_NO_OUTPUT);
+ BIND_CONSTANT(CONNECTION_ERROR_SAME_NODE);
+ BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_EXISTS);
+}
+
+AnimationNodeBlendTree::AnimationNodeBlendTree() {
+
+ Ref<AnimationNodeOutput> output;
+ output.instance();
+ Node n;
+ n.node = output;
+ n.position = Vector2(300, 150);
+ n.connections.resize(1);
+ nodes["output"] = n;
+}
+
+AnimationNodeBlendTree::~AnimationNodeBlendTree() {
+}
diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h
new file mode 100644
index 0000000000..7bf2917c1e
--- /dev/null
+++ b/scene/animation/animation_blend_tree.h
@@ -0,0 +1,378 @@
+#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;
+
+ uint64_t last_version;
+ float time;
+ float step;
+ bool skip;
+
+protected:
+ void _validate_property(PropertyInfo &property) const;
+
+ static void _bind_methods();
+
+public:
+ static Vector<String> (*get_editable_animation_list)();
+
+ virtual String get_caption() const;
+ virtual float process(float p_time, bool p_seek);
+
+ void set_animation(const StringName &p_name);
+ StringName get_animation() const;
+
+ float get_playback_time() const;
+
+ AnimationNodeAnimation();
+};
+
+class AnimationNodeOneShot : public AnimationNode {
+ GDCLASS(AnimationNodeOneShot, AnimationNode);
+
+public:
+ enum MixMode {
+ MIX_MODE_BLEND,
+ MIX_MODE_ADD
+ };
+
+private:
+ float fade_in;
+ float fade_out;
+
+ bool autorestart;
+ float autorestart_delay;
+ float autorestart_random_delay;
+ MixMode mix;
+
+ bool sync;
+
+ /* bool active;
+ bool do_start;
+ float time;
+ float remaining;*/
+
+ StringName active;
+ StringName prev_active;
+ StringName time;
+ StringName remaining;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual void get_parameter_list(List<PropertyInfo> *r_list) const;
+ virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
+
+ virtual String get_caption() const;
+
+ void set_fadein_time(float p_time);
+ void set_fadeout_time(float p_time);
+
+ float get_fadein_time() const;
+ float get_fadeout_time() const;
+
+ void set_autorestart(bool p_active);
+ void set_autorestart_delay(float p_time);
+ void set_autorestart_random_delay(float p_time);
+
+ bool has_autorestart() const;
+ float get_autorestart_delay() const;
+ float get_autorestart_random_delay() const;
+
+ void set_mix_mode(MixMode p_mix);
+ MixMode get_mix_mode() const;
+
+ void set_use_sync(bool p_sync);
+ bool is_using_sync() const;
+
+ virtual bool has_filter() const;
+ virtual float process(float p_time, bool p_seek);
+
+ AnimationNodeOneShot();
+};
+
+VARIANT_ENUM_CAST(AnimationNodeOneShot::MixMode)
+
+class AnimationNodeAdd2 : public AnimationNode {
+ GDCLASS(AnimationNodeAdd2, AnimationNode);
+
+ StringName add_amount;
+ bool sync;
+
+protected:
+ static void _bind_methods();
+
+public:
+ void get_parameter_list(List<PropertyInfo> *r_list) const;
+ virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
+
+ virtual String get_caption() const;
+
+ void set_use_sync(bool p_sync);
+ bool is_using_sync() const;
+
+ virtual bool has_filter() const;
+ virtual float process(float p_time, bool p_seek);
+
+ AnimationNodeAdd2();
+};
+
+class AnimationNodeAdd3 : public AnimationNode {
+ GDCLASS(AnimationNodeAdd3, AnimationNode);
+
+ StringName add_amount;
+ bool sync;
+
+protected:
+ static void _bind_methods();
+
+public:
+ void get_parameter_list(List<PropertyInfo> *r_list) const;
+ virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
+
+ virtual String get_caption() const;
+
+ void set_use_sync(bool p_sync);
+ bool is_using_sync() const;
+
+ virtual bool has_filter() const;
+ virtual float process(float p_time, bool p_seek);
+
+ AnimationNodeAdd3();
+};
+
+class AnimationNodeBlend2 : public AnimationNode {
+ GDCLASS(AnimationNodeBlend2, AnimationNode);
+
+ StringName blend_amount;
+ bool sync;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual void get_parameter_list(List<PropertyInfo> *r_list) const;
+ virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
+
+ virtual String get_caption() const;
+ virtual float process(float p_time, bool p_seek);
+
+ void set_use_sync(bool p_sync);
+ bool is_using_sync() const;
+
+ virtual bool has_filter() const;
+ AnimationNodeBlend2();
+};
+
+class AnimationNodeBlend3 : public AnimationNode {
+ GDCLASS(AnimationNodeBlend3, AnimationNode);
+
+ StringName blend_amount;
+ bool sync;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual void get_parameter_list(List<PropertyInfo> *r_list) const;
+ virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
+
+ virtual String get_caption() const;
+
+ void set_use_sync(bool p_sync);
+ bool is_using_sync() const;
+
+ float process(float p_time, bool p_seek);
+ AnimationNodeBlend3();
+};
+
+class AnimationNodeTimeScale : public AnimationNode {
+ GDCLASS(AnimationNodeTimeScale, AnimationNode);
+
+ StringName scale;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual void get_parameter_list(List<PropertyInfo> *r_list) const;
+ virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
+
+ virtual String get_caption() const;
+
+ float process(float p_time, bool p_seek);
+
+ AnimationNodeTimeScale();
+};
+
+class AnimationNodeTimeSeek : public AnimationNode {
+ GDCLASS(AnimationNodeTimeSeek, AnimationNode);
+
+ StringName seek_pos;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual void get_parameter_list(List<PropertyInfo> *r_list) const;
+ virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
+
+ virtual String get_caption() const;
+
+ float process(float p_time, bool p_seek);
+
+ AnimationNodeTimeSeek();
+};
+
+class AnimationNodeTransition : public AnimationNode {
+ GDCLASS(AnimationNodeTransition, AnimationNode);
+
+ enum {
+ MAX_INPUTS = 32
+ };
+ struct InputData {
+
+ String name;
+ bool auto_advance;
+ InputData() { auto_advance = false; }
+ };
+
+ InputData inputs[MAX_INPUTS];
+ int enabled_inputs;
+
+ /*
+ float prev_xfading;
+ int prev;
+ float time;
+ int current;
+ int prev_current; */
+
+ StringName prev_xfading;
+ StringName prev;
+ StringName time;
+ StringName current;
+ StringName prev_current;
+
+ float xfade;
+
+ void _update_inputs();
+
+protected:
+ static void _bind_methods();
+ void _validate_property(PropertyInfo &property) const;
+
+public:
+ virtual void get_parameter_list(List<PropertyInfo> *r_list) const;
+ virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
+
+ virtual String get_caption() const;
+
+ void set_enabled_inputs(int p_inputs);
+ int get_enabled_inputs();
+
+ 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_caption(int p_input, const String &p_name);
+ String get_input_caption(int p_input) const;
+
+ void set_cross_fade_time(float p_fade);
+ float get_cross_fade_time() const;
+
+ float process(float p_time, bool p_seek);
+
+ AnimationNodeTransition();
+};
+
+class AnimationNodeOutput : public AnimationNode {
+ GDCLASS(AnimationNodeOutput, AnimationNode)
+public:
+ virtual String get_caption() const;
+ virtual float process(float p_time, bool p_seek);
+ AnimationNodeOutput();
+};
+
+/////
+
+class AnimationNodeBlendTree : public AnimationRootNode {
+ GDCLASS(AnimationNodeBlendTree, AnimationRootNode)
+
+ struct Node {
+ Ref<AnimationNode> node;
+ Vector2 position;
+ Vector<StringName> connections;
+ };
+
+ Map<StringName, Node> nodes;
+
+ Vector2 graph_offset;
+
+ void _tree_changed();
+ void _node_changed(const StringName &p_node);
+
+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;
+
+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);
+
+ 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;
+ 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;
+ virtual float process(float p_time, bool p_seek);
+
+ 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);
+
+ AnimationNodeBlendTree();
+ ~AnimationNodeBlendTree();
+};
+
+VARIANT_ENUM_CAST(AnimationNodeBlendTree::ConnectionError)
+
+#endif // ANIMATION_BLEND_TREE_H
diff --git a/scene/animation/animation_cache.cpp b/scene/animation/animation_cache.cpp
index 949a0be3bc..756907c41c 100644
--- a/scene/animation/animation_cache.cpp
+++ b/scene/animation/animation_cache.cpp
@@ -43,7 +43,7 @@ void AnimationCache::_node_exit_tree(Node *p_node) {
if (path_cache[i].node != p_node)
continue;
- path_cache[i].valid = false; //invalidate path cache
+ path_cache.write[i].valid = false; //invalidate path cache
}
}
@@ -196,7 +196,7 @@ void AnimationCache::set_track_transform(int p_idx, const Transform &p_transform
ERR_FAIL_COND(!cache_valid);
ERR_FAIL_INDEX(p_idx, path_cache.size());
- Path &p = path_cache[p_idx];
+ Path &p = path_cache.write[p_idx];
if (!p.valid)
return;
@@ -217,7 +217,7 @@ void AnimationCache::set_track_value(int p_idx, const Variant &p_value) {
ERR_FAIL_COND(!cache_valid);
ERR_FAIL_INDEX(p_idx, path_cache.size());
- Path &p = path_cache[p_idx];
+ Path &p = path_cache.write[p_idx];
if (!p.valid)
return;
@@ -232,7 +232,7 @@ void AnimationCache::call_track(int p_idx, const StringName &p_method, const Var
ERR_FAIL_COND(!cache_valid);
ERR_FAIL_INDEX(p_idx, path_cache.size());
- Path &p = path_cache[p_idx];
+ Path &p = path_cache.write[p_idx];
if (!p.valid)
return;
@@ -297,11 +297,11 @@ void AnimationCache::set_all(float p_time, float p_delta) {
call_track(i, name, NULL, 0, err);
} else {
- Vector<Variant *> argptrs;
+ Vector<const Variant *> argptrs;
argptrs.resize(args.size());
for (int j = 0; j < args.size(); j++) {
- argptrs[j] = &args[j];
+ argptrs.write[j] = &args.write[j];
}
call_track(i, name, (const Variant **)&argptrs[0], args.size(), err);
diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp
new file mode 100644
index 0000000000..09c36eb081
--- /dev/null
+++ b/scene/animation/animation_node_state_machine.cpp
@@ -0,0 +1,943 @@
+#include "animation_node_state_machine.h"
+
+/////////////////////////////////////////////////
+
+void AnimationNodeStateMachineTransition::set_switch_mode(SwitchMode p_mode) {
+
+ switch_mode = p_mode;
+}
+
+AnimationNodeStateMachineTransition::SwitchMode AnimationNodeStateMachineTransition::get_switch_mode() const {
+
+ return switch_mode;
+}
+
+void AnimationNodeStateMachineTransition::set_auto_advance(bool p_enable) {
+ auto_advance = p_enable;
+}
+
+bool AnimationNodeStateMachineTransition::has_auto_advance() const {
+ return auto_advance;
+}
+
+void AnimationNodeStateMachineTransition::set_advance_condition(const StringName &p_condition) {
+ String cs = p_condition;
+ ERR_FAIL_COND(cs.find("/") != -1 || cs.find(":") != -1);
+ advance_condition = p_condition;
+ if (cs != String()) {
+ advance_condition_name = "conditions/" + cs;
+ } else {
+ advance_condition_name = StringName();
+ }
+ emit_signal("advance_condition_changed");
+}
+
+StringName AnimationNodeStateMachineTransition::get_advance_condition() const {
+ return advance_condition;
+}
+
+StringName AnimationNodeStateMachineTransition::get_advance_condition_name() const {
+ return advance_condition_name;
+}
+
+void AnimationNodeStateMachineTransition::set_xfade_time(float p_xfade) {
+
+ ERR_FAIL_COND(p_xfade < 0);
+ xfade = p_xfade;
+ emit_changed();
+}
+
+float AnimationNodeStateMachineTransition::get_xfade_time() const {
+ return xfade;
+}
+
+void AnimationNodeStateMachineTransition::set_disabled(bool p_disabled) {
+ disabled = p_disabled;
+ emit_changed();
+}
+
+bool AnimationNodeStateMachineTransition::is_disabled() const {
+ return disabled;
+}
+
+void AnimationNodeStateMachineTransition::set_priority(int p_priority) {
+ priority = p_priority;
+ emit_changed();
+}
+
+int AnimationNodeStateMachineTransition::get_priority() const {
+ return priority;
+}
+
+void AnimationNodeStateMachineTransition::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_switch_mode", "mode"), &AnimationNodeStateMachineTransition::set_switch_mode);
+ ClassDB::bind_method(D_METHOD("get_switch_mode"), &AnimationNodeStateMachineTransition::get_switch_mode);
+
+ ClassDB::bind_method(D_METHOD("set_auto_advance", "auto_advance"), &AnimationNodeStateMachineTransition::set_auto_advance);
+ ClassDB::bind_method(D_METHOD("has_auto_advance"), &AnimationNodeStateMachineTransition::has_auto_advance);
+
+ ClassDB::bind_method(D_METHOD("set_advance_condition", "name"), &AnimationNodeStateMachineTransition::set_advance_condition);
+ ClassDB::bind_method(D_METHOD("get_advance_condition"), &AnimationNodeStateMachineTransition::get_advance_condition);
+
+ ClassDB::bind_method(D_METHOD("set_xfade_time", "secs"), &AnimationNodeStateMachineTransition::set_xfade_time);
+ ClassDB::bind_method(D_METHOD("get_xfade_time"), &AnimationNodeStateMachineTransition::get_xfade_time);
+
+ ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &AnimationNodeStateMachineTransition::set_disabled);
+ ClassDB::bind_method(D_METHOD("is_disabled"), &AnimationNodeStateMachineTransition::is_disabled);
+
+ ClassDB::bind_method(D_METHOD("set_priority", "priority"), &AnimationNodeStateMachineTransition::set_priority);
+ ClassDB::bind_method(D_METHOD("get_priority"), &AnimationNodeStateMachineTransition::get_priority);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "switch_mode", PROPERTY_HINT_ENUM, "Immediate,Sync,AtEnd"), "set_switch_mode", "get_switch_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_advance"), "set_auto_advance", "has_auto_advance");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "advance_condition"), "set_advance_condition", "get_advance_condition");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01"), "set_xfade_time", "get_xfade_time");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled");
+
+ BIND_ENUM_CONSTANT(SWITCH_MODE_IMMEDIATE);
+ BIND_ENUM_CONSTANT(SWITCH_MODE_SYNC);
+ BIND_ENUM_CONSTANT(SWITCH_MODE_AT_END);
+
+ ADD_SIGNAL(MethodInfo("advance_condition_changed"));
+}
+
+AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() {
+
+ switch_mode = SWITCH_MODE_IMMEDIATE;
+ auto_advance = false;
+ xfade = 0;
+ disabled = false;
+ priority = 1;
+}
+
+////////////////////////////////////////////////////////
+
+void AnimationNodeStateMachinePlayback::travel(const StringName &p_state) {
+
+ start_request_travel = true;
+ start_request = p_state;
+ stop_request = false;
+}
+
+void AnimationNodeStateMachinePlayback::start(const StringName &p_state) {
+ start_request_travel = false;
+ start_request = p_state;
+ stop_request = false;
+}
+void AnimationNodeStateMachinePlayback::stop() {
+
+ stop_request = true;
+}
+bool AnimationNodeStateMachinePlayback::is_playing() const {
+ return playing;
+}
+StringName AnimationNodeStateMachinePlayback::get_current_node() const {
+ return current;
+}
+StringName AnimationNodeStateMachinePlayback::get_blend_from_node() const {
+ return fading_from;
+}
+Vector<StringName> AnimationNodeStateMachinePlayback::get_travel_path() const {
+ return path;
+}
+float AnimationNodeStateMachinePlayback::get_current_play_pos() const {
+ return pos_current;
+}
+float AnimationNodeStateMachinePlayback::get_current_length() const {
+ return len_current;
+}
+
+bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *sm, const StringName &p_travel) {
+
+ ERR_FAIL_COND_V(!playing, false);
+ ERR_FAIL_COND_V(!sm->states.has(p_travel), false);
+ ERR_FAIL_COND_V(!sm->states.has(current), false);
+
+ path.clear(); //a new one will be needed
+
+ if (current == p_travel)
+ return true; //nothing to do
+
+ loops_current = 0; // reset loops, so fade does not happen immediately
+
+ Vector2 current_pos = sm->states[current].position;
+ Vector2 target_pos = sm->states[p_travel].position;
+
+ Map<StringName, AStarCost> cost_map;
+
+ List<int> open_list;
+
+ //build open list
+ for (int i = 0; i < sm->transitions.size(); i++) {
+ if (sm->transitions[i].from == current) {
+ open_list.push_back(i);
+ float cost = sm->states[sm->transitions[i].to].position.distance_to(current_pos);
+ cost *= sm->transitions[i].transition->get_priority();
+ AStarCost ap;
+ ap.prev = current;
+ ap.distance = cost;
+ cost_map[sm->transitions[i].to] = ap;
+
+ if (sm->transitions[i].to == p_travel) { //prematurely found it! :D
+ path.push_back(p_travel);
+ return true;
+ }
+ }
+ }
+
+ //begin astar
+ bool found_route = false;
+ while (!found_route) {
+
+ if (open_list.size() == 0) {
+ return false; //no path found
+ }
+
+ //find the last cost transition
+ List<int>::Element *least_cost_transition = NULL;
+ float least_cost = 1e20;
+
+ for (List<int>::Element *E = open_list.front(); E; E = E->next()) {
+
+ float cost = cost_map[sm->transitions[E->get()].to].distance;
+ cost += sm->states[sm->transitions[E->get()].to].position.distance_to(target_pos);
+
+ if (cost < least_cost) {
+ least_cost_transition = E;
+ }
+ }
+
+ StringName transition_prev = sm->transitions[least_cost_transition->get()].from;
+ StringName transition = sm->transitions[least_cost_transition->get()].to;
+
+ for (int i = 0; i < sm->transitions.size(); i++) {
+ if (sm->transitions[i].from != transition || sm->transitions[i].to == transition_prev) {
+ continue; //not interested on those
+ }
+
+ float distance = sm->states[sm->transitions[i].from].position.distance_to(sm->states[sm->transitions[i].to].position);
+ distance *= sm->transitions[i].transition->get_priority();
+ distance += cost_map[sm->transitions[i].from].distance;
+
+ if (cost_map.has(sm->transitions[i].to)) {
+ //oh this was visited already, can we win the cost?
+ if (distance < cost_map[sm->transitions[i].to].distance) {
+ cost_map[sm->transitions[i].to].distance = distance;
+ cost_map[sm->transitions[i].to].prev = sm->transitions[i].from;
+ }
+ } else {
+ //add to open list
+ AStarCost ac;
+ ac.prev = sm->transitions[i].from;
+ ac.distance = distance;
+ cost_map[sm->transitions[i].to] = ac;
+
+ open_list.push_back(i);
+
+ if (sm->transitions[i].to == p_travel) {
+ found_route = true;
+ break;
+ }
+ }
+ }
+
+ if (found_route) {
+ break;
+ }
+
+ open_list.erase(least_cost_transition);
+ }
+
+ //make path
+ StringName at = p_travel;
+ while (at != current) {
+ path.push_back(at);
+ at = cost_map[at].prev;
+ }
+
+ path.invert();
+
+ return true;
+}
+
+float AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *sm, float p_time, bool p_seek) {
+
+ //if not playing and it can restart, then restart
+ if (!playing && start_request == StringName()) {
+ if (!stop_request && sm->start_node) {
+ start(sm->start_node);
+ } else {
+ return 0;
+ }
+ }
+
+ if (playing && stop_request) {
+ stop_request = false;
+ playing = false;
+ return 0;
+ }
+
+ bool play_start = false;
+
+ if (start_request != StringName()) {
+
+ if (start_request_travel) {
+ if (!playing) {
+ start_request = StringName();
+ ERR_EXPLAIN("Can't travel to '" + String(start_request) + "' if state machine is not active.");
+ ERR_FAIL_V(0);
+ }
+
+ if (!_travel(sm, start_request)) {
+ //cant travel, then teleport
+ path.clear();
+ current = start_request;
+ }
+ } else {
+ path.clear();
+ current = start_request;
+ playing = true;
+ play_start = true;
+ }
+
+ start_request = StringName(); //clear start request
+ }
+
+ bool do_start = (p_seek && p_time == 0) || play_start || current == StringName();
+
+ if (do_start) {
+
+ if (sm->start_node != StringName() && p_seek && p_time == 0) {
+ current = sm->start_node;
+ }
+
+ len_current = sm->blend_node(current, sm->states[current].node, 0, true, 1.0, AnimationNode::FILTER_IGNORE, false);
+ pos_current = 0;
+ loops_current = 0;
+ play_start = false;
+ }
+
+ if (!sm->states.has(current)) {
+ playing = false; //current does not exist
+ current = StringName();
+ return 0;
+ }
+ float fade_blend = 1.0;
+
+ if (fading_from != StringName()) {
+
+ if (!sm->states.has(fading_from)) {
+ fading_from = StringName();
+ } else {
+ if (!p_seek) {
+ fading_pos += p_time;
+ }
+ fade_blend = MIN(1.0, fading_pos / fading_time);
+ if (fade_blend >= 1.0) {
+ fading_from = StringName();
+ }
+ }
+ }
+
+ float rem = sm->blend_node(current, sm->states[current].node, p_time, p_seek, fade_blend, AnimationNode::FILTER_IGNORE, false);
+
+ if (fading_from != StringName()) {
+
+ sm->blend_node(current, sm->states[fading_from].node, p_time, p_seek, 1.0 - fade_blend, AnimationNode::FILTER_IGNORE, false);
+ }
+
+ //guess playback position
+ if (rem > len_current) { // weird but ok
+ len_current = rem;
+ }
+
+ { //advance and loop check
+
+ float next_pos = len_current - rem;
+
+ if (next_pos < pos_current) {
+ loops_current++;
+ }
+ pos_current = next_pos; //looped
+ }
+
+ //find next
+ StringName next;
+ float next_xfade = 0;
+ AnimationNodeStateMachineTransition::SwitchMode switch_mode = AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE;
+
+ if (path.size()) {
+
+ for (int i = 0; i < sm->transitions.size(); i++) {
+ if (sm->transitions[i].from == current && sm->transitions[i].to == path[0]) {
+ next_xfade = sm->transitions[i].transition->get_xfade_time();
+ switch_mode = sm->transitions[i].transition->get_switch_mode();
+ next = path[0];
+ }
+ }
+ } else {
+ float priority_best = 1e20;
+ int auto_advance_to = -1;
+ for (int i = 0; i < sm->transitions.size(); i++) {
+
+ bool auto_advance = false;
+ if (sm->transitions[i].transition->has_auto_advance()) {
+ auto_advance = true;
+ }
+ StringName advance_condition_name = sm->transitions[i].transition->get_advance_condition_name();
+ if (advance_condition_name != StringName() && bool(sm->get_parameter(advance_condition_name))) {
+ auto_advance = true;
+ }
+
+ if (sm->transitions[i].from == current && sm->transitions[i].transition->has_auto_advance()) {
+
+ if (sm->transitions[i].transition->get_priority() < priority_best) {
+ auto_advance_to = i;
+ }
+ }
+ }
+
+ if (auto_advance_to != -1) {
+ next = sm->transitions[auto_advance_to].to;
+ next_xfade = sm->transitions[auto_advance_to].transition->get_xfade_time();
+ switch_mode = sm->transitions[auto_advance_to].transition->get_switch_mode();
+ }
+ }
+
+ //if next, see when to transition
+ if (next != StringName()) {
+
+ bool goto_next = false;
+
+ if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE) {
+ goto_next = fading_from == StringName();
+ } else {
+ goto_next = next_xfade >= (len_current - pos_current) || loops_current > 0;
+ if (loops_current > 0) {
+ next_xfade = 0;
+ }
+ }
+
+ if (goto_next) { //loops should be used because fade time may be too small or zero and animation may have looped
+
+ if (next_xfade) {
+ //time to fade, baby
+ fading_from = current;
+ fading_time = next_xfade;
+ fading_pos = 0;
+ } else {
+ fading_from = StringName();
+ fading_pos = 0;
+ }
+
+ if (path.size()) { //if it came from path, remove path
+ path.remove(0);
+ }
+ current = next;
+ if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) {
+ len_current = sm->blend_node(current, sm->states[current].node, 0, true, 0, AnimationNode::FILTER_IGNORE, false);
+ pos_current = MIN(pos_current, len_current);
+ sm->blend_node(current, sm->states[current].node, pos_current, true, 0, AnimationNode::FILTER_IGNORE, false);
+
+ } else {
+ len_current = sm->blend_node(current, sm->states[current].node, 0, true, 0, AnimationNode::FILTER_IGNORE, false);
+ pos_current = 0;
+ }
+
+ rem = len_current; //so it does not show 0 on transition
+ loops_current = 0;
+ }
+ }
+
+ //compute time left for transitions by using the end node
+
+ if (sm->end_node != StringName() && sm->end_node != current) {
+
+ rem = sm->blend_node(current, sm->states[sm->end_node].node, 0, true, 0, AnimationNode::FILTER_IGNORE, false);
+ }
+
+ return rem;
+}
+
+void AnimationNodeStateMachinePlayback::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("travel", "to_node"), &AnimationNodeStateMachinePlayback::travel);
+ ClassDB::bind_method(D_METHOD("start", "node"), &AnimationNodeStateMachinePlayback::start);
+ ClassDB::bind_method(D_METHOD("stop"), &AnimationNodeStateMachinePlayback::stop);
+ ClassDB::bind_method(D_METHOD("is_playing"), &AnimationNodeStateMachinePlayback::is_playing);
+ ClassDB::bind_method(D_METHOD("get_current_node"), &AnimationNodeStateMachinePlayback::get_current_node);
+ ClassDB::bind_method(D_METHOD("get_travel_path"), &AnimationNodeStateMachinePlayback::get_travel_path);
+}
+
+AnimationNodeStateMachinePlayback::AnimationNodeStateMachinePlayback() {
+ set_local_to_scene(true); //only one per instanced scene
+
+ playing = false;
+ len_current = 0;
+ fading_time = 0;
+ stop_request = false;
+}
+
+///////////////////////////////////////////////////////
+
+void AnimationNodeStateMachine::get_parameter_list(List<PropertyInfo> *r_list) const {
+ r_list->push_back(PropertyInfo(Variant::OBJECT, playback, PROPERTY_HINT_RESOURCE_TYPE, "AnimationNodeStateMachinePlayback", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
+ List<StringName> advance_conditions;
+ for (int i = 0; i < transitions.size(); i++) {
+ StringName ac = transitions[i].transition->get_advance_condition_name();
+ if (ac != StringName() && advance_conditions.find(ac) == NULL) {
+ advance_conditions.push_back(ac);
+ }
+ }
+
+ advance_conditions.sort_custom<StringName::AlphCompare>();
+ for (List<StringName>::Element *E = advance_conditions.front(); E; E = E->next()) {
+ r_list->push_back(PropertyInfo(Variant::BOOL, E->get()));
+ }
+}
+
+Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName &p_parameter) const {
+
+ if (p_parameter == playback) {
+ Ref<AnimationNodeStateMachinePlayback> p;
+ p.instance();
+ return p;
+ } else {
+ return false; //advance condition
+ }
+}
+
+void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<AnimationNode> p_node, const Vector2 &p_position) {
+
+ ERR_FAIL_COND(states.has(p_name));
+ ERR_FAIL_COND(p_node.is_null());
+ ERR_FAIL_COND(String(p_name).find("/") != -1);
+
+ State state;
+ state.node = p_node;
+ state.position = p_position;
+
+ states[p_name] = state;
+
+ emit_changed();
+ emit_signal("tree_changed");
+
+ p_node->connect("tree_changed", this, "_tree_changed", varray(), CONNECT_REFERENCE_COUNTED);
+}
+
+Ref<AnimationNode> AnimationNodeStateMachine::get_node(const StringName &p_name) const {
+
+ ERR_FAIL_COND_V(!states.has(p_name), Ref<AnimationNode>());
+
+ return states[p_name].node;
+}
+
+StringName AnimationNodeStateMachine::get_node_name(const Ref<AnimationNode> &p_node) const {
+ for (Map<StringName, State>::Element *E = states.front(); E; E = E->next()) {
+ if (E->get().node == p_node) {
+ return E->key();
+ }
+ }
+
+ ERR_FAIL_V(StringName());
+}
+
+void AnimationNodeStateMachine::get_child_nodes(List<ChildNode> *r_child_nodes) {
+ Vector<StringName> nodes;
+
+ for (Map<StringName, State>::Element *E = states.front(); E; E = E->next()) {
+ nodes.push_back(E->key());
+ }
+
+ nodes.sort_custom<StringName::AlphCompare>();
+
+ for (int i = 0; i < nodes.size(); i++) {
+ ChildNode cn;
+ cn.name = nodes[i];
+ cn.node = states[cn.name].node;
+ r_child_nodes->push_back(cn);
+ }
+}
+
+bool AnimationNodeStateMachine::has_node(const StringName &p_name) const {
+ return states.has(p_name);
+}
+void AnimationNodeStateMachine::remove_node(const StringName &p_name) {
+
+ ERR_FAIL_COND(!states.has(p_name));
+
+ {
+ Ref<AnimationNode> node = states[p_name].node;
+ node->disconnect("tree_changed", this, "_tree_changed");
+ }
+
+ states.erase(p_name);
+ //path.erase(p_name);
+
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from == p_name || transitions[i].to == p_name) {
+ transitions.write[i].transition->disconnect("advance_condition_changed", this, "_tree_changed");
+ transitions.remove(i);
+ i--;
+ }
+ }
+
+ if (start_node == p_name) {
+ start_node = StringName();
+ }
+
+ if (end_node == p_name) {
+ end_node = StringName();
+ }
+
+ /*if (playing && current == p_name) {
+ stop();
+ }*/
+
+ emit_changed();
+ emit_signal("tree_changed");
+}
+
+void AnimationNodeStateMachine::rename_node(const StringName &p_name, const StringName &p_new_name) {
+
+ ERR_FAIL_COND(!states.has(p_name));
+ ERR_FAIL_COND(states.has(p_new_name));
+
+ states[p_new_name] = states[p_name];
+ states.erase(p_name);
+
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from == p_name) {
+ transitions.write[i].from = p_new_name;
+ }
+
+ if (transitions[i].to == p_name) {
+ transitions.write[i].to = p_new_name;
+ }
+ }
+
+ if (start_node == p_name) {
+ start_node = p_new_name;
+ }
+
+ if (end_node == p_name) {
+ end_node = p_new_name;
+ }
+
+ /*if (playing && current == p_name) {
+ current = p_new_name;
+ }*/
+
+ //path.clear(); //clear path
+ emit_signal("tree_changed");
+}
+
+void AnimationNodeStateMachine::get_node_list(List<StringName> *r_nodes) const {
+
+ List<StringName> nodes;
+ for (Map<StringName, State>::Element *E = states.front(); E; E = E->next()) {
+ nodes.push_back(E->key());
+ }
+ nodes.sort_custom<StringName::AlphCompare>();
+
+ for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
+ r_nodes->push_back(E->get());
+ }
+}
+
+bool AnimationNodeStateMachine::has_transition(const StringName &p_from, const StringName &p_to) const {
+
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from == p_from && transitions[i].to == p_to)
+ return true;
+ }
+ return false;
+}
+
+int AnimationNodeStateMachine::find_transition(const StringName &p_from, const StringName &p_to) const {
+
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from == p_from && transitions[i].to == p_to)
+ return i;
+ }
+ return -1;
+}
+
+void AnimationNodeStateMachine::add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition) {
+
+ ERR_FAIL_COND(p_from == p_to);
+ ERR_FAIL_COND(!states.has(p_from));
+ ERR_FAIL_COND(!states.has(p_to));
+ ERR_FAIL_COND(p_transition.is_null());
+
+ for (int i = 0; i < transitions.size(); i++) {
+ ERR_FAIL_COND(transitions[i].from == p_from && transitions[i].to == p_to);
+ }
+
+ Transition tr;
+ tr.from = p_from;
+ tr.to = p_to;
+ tr.transition = p_transition;
+
+ tr.transition->connect("advance_condition_changed", this, "_tree_changed", varray(), CONNECT_REFERENCE_COUNTED);
+
+ transitions.push_back(tr);
+}
+
+Ref<AnimationNodeStateMachineTransition> AnimationNodeStateMachine::get_transition(int p_transition) const {
+ ERR_FAIL_INDEX_V(p_transition, transitions.size(), Ref<AnimationNodeStateMachineTransition>());
+ return transitions[p_transition].transition;
+}
+StringName AnimationNodeStateMachine::get_transition_from(int p_transition) const {
+
+ ERR_FAIL_INDEX_V(p_transition, transitions.size(), StringName());
+ return transitions[p_transition].from;
+}
+StringName AnimationNodeStateMachine::get_transition_to(int p_transition) const {
+
+ ERR_FAIL_INDEX_V(p_transition, transitions.size(), StringName());
+ return transitions[p_transition].to;
+}
+
+int AnimationNodeStateMachine::get_transition_count() const {
+
+ return transitions.size();
+}
+void AnimationNodeStateMachine::remove_transition(const StringName &p_from, const StringName &p_to) {
+
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from == p_from && transitions[i].to == p_to) {
+ transitions.write[i].transition->disconnect("advance_condition_changed", this, "_tree_changed");
+ transitions.remove(i);
+ return;
+ }
+ }
+
+ /*if (playing) {
+ path.clear();
+ }*/
+}
+
+void AnimationNodeStateMachine::remove_transition_by_index(int p_transition) {
+
+ ERR_FAIL_INDEX(p_transition, transitions.size());
+ transitions.write[p_transition].transition->disconnect("advance_condition_changed", this, "_tree_changed");
+ transitions.remove(p_transition);
+ /*if (playing) {
+ path.clear();
+ }*/
+}
+
+void AnimationNodeStateMachine::set_start_node(const StringName &p_node) {
+
+ ERR_FAIL_COND(p_node != StringName() && !states.has(p_node));
+ start_node = p_node;
+}
+
+String AnimationNodeStateMachine::get_start_node() const {
+
+ return start_node;
+}
+
+void AnimationNodeStateMachine::set_end_node(const StringName &p_node) {
+
+ ERR_FAIL_COND(p_node != StringName() && !states.has(p_node));
+ end_node = p_node;
+}
+
+String AnimationNodeStateMachine::get_end_node() const {
+
+ return end_node;
+}
+
+void AnimationNodeStateMachine::set_graph_offset(const Vector2 &p_offset) {
+ graph_offset = p_offset;
+}
+
+Vector2 AnimationNodeStateMachine::get_graph_offset() const {
+ return graph_offset;
+}
+
+float AnimationNodeStateMachine::process(float p_time, bool p_seek) {
+
+ Ref<AnimationNodeStateMachinePlayback> playback = get_parameter(this->playback);
+ ERR_FAIL_COND_V(playback.is_null(), 0.0);
+
+ return playback->process(this, p_time, p_seek);
+}
+
+String AnimationNodeStateMachine::get_caption() const {
+ return "StateMachine";
+}
+
+void AnimationNodeStateMachine::_notification(int p_what) {
+}
+
+Ref<AnimationNode> AnimationNodeStateMachine::get_child_by_name(const StringName &p_name) {
+ return get_node(p_name);
+}
+
+bool AnimationNodeStateMachine::_set(const StringName &p_name, const Variant &p_value) {
+
+ String name = p_name;
+ if (name.begins_with("states/")) {
+ String node_name = name.get_slicec('/', 1);
+ String what = name.get_slicec('/', 2);
+
+ if (what == "node") {
+ Ref<AnimationNode> anode = p_value;
+ if (anode.is_valid()) {
+ add_node(node_name, p_value);
+ }
+ return true;
+ }
+
+ if (what == "position") {
+
+ if (states.has(node_name)) {
+ states[node_name].position = p_value;
+ }
+ return true;
+ }
+ } else if (name == "transitions") {
+
+ Array trans = p_value;
+ ERR_FAIL_COND_V(trans.size() % 3 != 0, false);
+
+ for (int i = 0; i < trans.size(); i += 3) {
+ add_transition(trans[i], trans[i + 1], trans[i + 2]);
+ }
+ return true;
+ } else if (name == "start_node") {
+ set_start_node(p_value);
+ return true;
+ } else if (name == "end_node") {
+ set_end_node(p_value);
+ return true;
+ } else if (name == "graph_offset") {
+ set_graph_offset(p_value);
+ return true;
+ }
+
+ return false;
+}
+
+bool AnimationNodeStateMachine::_get(const StringName &p_name, Variant &r_ret) const {
+
+ String name = p_name;
+ if (name.begins_with("states/")) {
+ String node_name = name.get_slicec('/', 1);
+ String what = name.get_slicec('/', 2);
+
+ if (what == "node") {
+ if (states.has(node_name)) {
+ r_ret = states[node_name].node;
+ return true;
+ }
+ }
+
+ if (what == "position") {
+
+ if (states.has(node_name)) {
+ r_ret = states[node_name].position;
+ return true;
+ }
+ }
+ } else if (name == "transitions") {
+ Array trans;
+ trans.resize(transitions.size() * 3);
+
+ for (int i = 0; i < transitions.size(); i++) {
+ trans[i * 3 + 0] = transitions[i].from;
+ trans[i * 3 + 1] = transitions[i].to;
+ trans[i * 3 + 2] = transitions[i].transition;
+ }
+
+ r_ret = trans;
+ return true;
+ } else if (name == "start_node") {
+ r_ret = get_start_node();
+ return true;
+ } else if (name == "end_node") {
+ r_ret = get_end_node();
+ return true;
+ } else if (name == "graph_offset") {
+ r_ret = get_graph_offset();
+ return true;
+ }
+
+ return false;
+}
+void AnimationNodeStateMachine::_get_property_list(List<PropertyInfo> *p_list) const {
+
+ List<StringName> names;
+ for (Map<StringName, State>::Element *E = states.front(); E; E = E->next()) {
+ names.push_back(E->key());
+ }
+ names.sort_custom<StringName::AlphCompare>();
+
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ String name = E->get();
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "states/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, "states/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ }
+
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING, "start_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING, "end_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+}
+
+void AnimationNodeStateMachine::set_node_position(const StringName &p_name, const Vector2 &p_position) {
+ ERR_FAIL_COND(!states.has(p_name));
+ states[p_name].position = p_position;
+}
+
+Vector2 AnimationNodeStateMachine::get_node_position(const StringName &p_name) const {
+
+ ERR_FAIL_COND_V(!states.has(p_name), Vector2());
+ return states[p_name].position;
+}
+
+void AnimationNodeStateMachine::_tree_changed() {
+ emit_signal("tree_changed");
+}
+
+void AnimationNodeStateMachine::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("add_node", "name", "node", "position"), &AnimationNodeStateMachine::add_node, DEFVAL(Vector2()));
+ ClassDB::bind_method(D_METHOD("get_node", "name"), &AnimationNodeStateMachine::get_node);
+ ClassDB::bind_method(D_METHOD("remove_node", "name"), &AnimationNodeStateMachine::remove_node);
+ ClassDB::bind_method(D_METHOD("rename_node", "name", "new_name"), &AnimationNodeStateMachine::rename_node);
+ ClassDB::bind_method(D_METHOD("has_node", "name"), &AnimationNodeStateMachine::has_node);
+ ClassDB::bind_method(D_METHOD("get_node_name", "node"), &AnimationNodeStateMachine::get_node_name);
+
+ ClassDB::bind_method(D_METHOD("set_node_position", "name", "position"), &AnimationNodeStateMachine::set_node_position);
+ ClassDB::bind_method(D_METHOD("get_node_position", "name"), &AnimationNodeStateMachine::get_node_position);
+
+ ClassDB::bind_method(D_METHOD("has_transition", "from", "to"), &AnimationNodeStateMachine::has_transition);
+ ClassDB::bind_method(D_METHOD("add_transition", "from", "to", "transition"), &AnimationNodeStateMachine::add_transition);
+ ClassDB::bind_method(D_METHOD("get_transition", "idx"), &AnimationNodeStateMachine::get_transition);
+ ClassDB::bind_method(D_METHOD("get_transition_from", "idx"), &AnimationNodeStateMachine::get_transition_from);
+ ClassDB::bind_method(D_METHOD("get_transition_to", "idx"), &AnimationNodeStateMachine::get_transition_to);
+ ClassDB::bind_method(D_METHOD("get_transition_count"), &AnimationNodeStateMachine::get_transition_count);
+ ClassDB::bind_method(D_METHOD("remove_transition_by_index", "idx"), &AnimationNodeStateMachine::remove_transition_by_index);
+ ClassDB::bind_method(D_METHOD("remove_transition", "from", "to"), &AnimationNodeStateMachine::remove_transition);
+
+ ClassDB::bind_method(D_METHOD("set_start_node", "name"), &AnimationNodeStateMachine::set_start_node);
+ ClassDB::bind_method(D_METHOD("get_start_node"), &AnimationNodeStateMachine::get_start_node);
+
+ ClassDB::bind_method(D_METHOD("set_end_node", "name"), &AnimationNodeStateMachine::set_end_node);
+ ClassDB::bind_method(D_METHOD("get_end_node"), &AnimationNodeStateMachine::get_end_node);
+
+ ClassDB::bind_method(D_METHOD("set_graph_offset", "name"), &AnimationNodeStateMachine::set_graph_offset);
+ ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeStateMachine::get_graph_offset);
+
+ ClassDB::bind_method(D_METHOD("_tree_changed"), &AnimationNodeStateMachine::_tree_changed);
+}
+
+AnimationNodeStateMachine::AnimationNodeStateMachine() {
+
+ playback = "playback";
+}
diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h
new file mode 100644
index 0000000000..5d633d6334
--- /dev/null
+++ b/scene/animation/animation_node_state_machine.h
@@ -0,0 +1,189 @@
+#ifndef ANIMATION_NODE_STATE_MACHINE_H
+#define ANIMATION_NODE_STATE_MACHINE_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,
+ };
+
+private:
+ SwitchMode switch_mode;
+ bool auto_advance;
+ StringName advance_condition;
+ StringName advance_condition_name;
+ float xfade;
+ bool disabled;
+ int priority;
+
+protected:
+ static void _bind_methods();
+
+public:
+ void set_switch_mode(SwitchMode p_mode);
+ SwitchMode get_switch_mode() const;
+
+ void set_auto_advance(bool p_enable);
+ bool has_auto_advance() const;
+
+ void set_advance_condition(const StringName &p_condition);
+ StringName get_advance_condition() const;
+
+ StringName get_advance_condition_name() const;
+
+ void set_xfade_time(float p_xfade);
+ float get_xfade_time() const;
+
+ void set_disabled(bool p_disabled);
+ bool is_disabled() const;
+
+ void set_priority(int p_priority);
+ int get_priority() const;
+
+ AnimationNodeStateMachineTransition();
+};
+
+VARIANT_ENUM_CAST(AnimationNodeStateMachineTransition::SwitchMode)
+
+class AnimationNodeStateMachine;
+
+class AnimationNodeStateMachinePlayback : public Resource {
+ GDCLASS(AnimationNodeStateMachinePlayback, Resource);
+
+ friend class AnimationNodeStateMachine;
+
+ struct AStarCost {
+ float distance;
+ StringName prev;
+ };
+
+ float len_total;
+
+ float len_current;
+ float pos_current;
+ int loops_current;
+
+ StringName current;
+
+ StringName fading_from;
+ float fading_time;
+ float fading_pos;
+
+ Vector<StringName> path;
+ bool playing;
+
+ StringName start_request;
+ bool start_request_travel;
+ bool stop_request;
+
+ bool _travel(AnimationNodeStateMachine *p_state_machine, const StringName &p_travel);
+
+ float process(AnimationNodeStateMachine *p_state_machine, float p_time, bool p_seek);
+
+protected:
+ static void _bind_methods();
+
+public:
+ void travel(const StringName &p_state);
+ void start(const StringName &p_state);
+ void stop();
+ bool is_playing() const;
+ StringName get_current_node() const;
+ StringName get_blend_from_node() const;
+ Vector<StringName> get_travel_path() const;
+ float get_current_play_pos() const;
+ float get_current_length() const;
+
+ AnimationNodeStateMachinePlayback();
+};
+
+class AnimationNodeStateMachine : public AnimationRootNode {
+
+ GDCLASS(AnimationNodeStateMachine, AnimationRootNode);
+
+private:
+ friend class AnimationNodeStateMachinePlayback;
+
+ struct State {
+ Ref<AnimationRootNode> node;
+ Vector2 position;
+ };
+
+ Map<StringName, State> states;
+
+ struct Transition {
+
+ StringName from;
+ StringName to;
+ Ref<AnimationNodeStateMachineTransition> transition;
+ };
+
+ Vector<Transition> transitions;
+
+ StringName playback;
+
+ StringName start_node;
+ StringName end_node;
+
+ Vector2 graph_offset;
+
+ void _tree_changed();
+
+protected:
+ void _notification(int p_what);
+ 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;
+
+public:
+ virtual void get_parameter_list(List<PropertyInfo> *r_list) const;
+ virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
+
+ 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;
+ 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);
+
+ bool has_transition(const StringName &p_from, const StringName &p_to) const;
+ int find_transition(const StringName &p_from, const StringName &p_to) const;
+ void add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition);
+ Ref<AnimationNodeStateMachineTransition> get_transition(int p_transition) const;
+ StringName get_transition_from(int p_transition) const;
+ StringName get_transition_to(int p_transition) const;
+ int get_transition_count() const;
+ void remove_transition_by_index(int p_transition);
+ void remove_transition(const StringName &p_from, const StringName &p_to);
+
+ void set_start_node(const StringName &p_node);
+ String get_start_node() const;
+
+ void set_end_node(const StringName &p_node);
+ String get_end_node() const;
+
+ void set_graph_offset(const Vector2 &p_offset);
+ Vector2 get_graph_offset() const;
+
+ virtual float process(float p_time, bool p_seek);
+ virtual String get_caption() const;
+
+ virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name);
+
+ AnimationNodeStateMachine();
+};
+
+#endif // ANIMATION_NODE_STATE_MACHINE_H
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index 9db4a5fb04..d8db1973d2 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -33,7 +33,7 @@
#include "engine.h"
#include "message_queue.h"
#include "scene/scene_string_names.h"
-
+#include "servers/audio/audio_stream.h"
#ifdef TOOLS_ENABLED
void AnimatedValuesBackup::update_skeletons() {
@@ -182,8 +182,8 @@ void AnimationPlayer::_notification(int p_what) {
if (!processing) {
//make sure that a previous process state was not saved
//only process if "processing" is set
- set_physics_process(false);
- set_process(false);
+ set_physics_process_internal(false);
+ set_process_internal(false);
}
//_set_process(false);
clear_caches();
@@ -233,7 +233,7 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim) {
for (int i = 0; i < a->get_track_count(); i++) {
- p_anim->node_cache[i] = NULL;
+ p_anim->node_cache.write[i] = NULL;
RES resource;
Vector<StringName> leftover_path;
Node *child = parent->get_node_and_resource(a->track_get_path(i), resource, leftover_path);
@@ -246,8 +246,9 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim) {
if (a->track_get_path(i).get_subname_count() == 1 && Object::cast_to<Skeleton>(child)) {
- bone_idx = Object::cast_to<Skeleton>(child)->find_bone(a->track_get_path(i).get_subname(0));
- if (bone_idx == -1) {
+ Skeleton *sk = Object::cast_to<Skeleton>(child);
+ bone_idx = sk->find_bone(a->track_get_path(i).get_subname(0));
+ if (bone_idx == -1 || sk->is_bone_ignore_animation(bone_idx)) {
continue;
}
@@ -264,12 +265,12 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim) {
if (node_cache_map.has(key)) {
- p_anim->node_cache[i] = &node_cache_map[key];
+ p_anim->node_cache.write[i] = &node_cache_map[key];
} else {
node_cache_map[key] = TrackNodeCache();
- p_anim->node_cache[i] = &node_cache_map[key];
+ p_anim->node_cache.write[i] = &node_cache_map[key];
p_anim->node_cache[i]->path = a->track_get_path(i);
p_anim->node_cache[i]->node = child;
p_anim->node_cache[i]->resource = resource;
@@ -324,10 +325,23 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim) {
p_anim->node_cache[i]->property_anim[a->track_get_path(i).get_concatenated_subnames()] = pa;
}
}
+
+ if (a->track_get_type(i) == Animation::TYPE_BEZIER && leftover_path.size()) {
+
+ if (!p_anim->node_cache[i]->bezier_anim.has(a->track_get_path(i).get_concatenated_subnames())) {
+
+ TrackNodeCache::BezierAnim ba;
+ ba.bezier_property = leftover_path;
+ ba.object = resource.is_valid() ? (Object *)resource.ptr() : (Object *)child;
+ ba.owner = p_anim->node_cache[i];
+
+ p_anim->node_cache[i]->bezier_anim[a->track_get_path(i).get_concatenated_subnames()] = ba;
+ }
+ }
}
}
-void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_allow_discrete) {
+void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started) {
_ensure_node_caches(p_anim);
ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count());
@@ -393,7 +407,51 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
TrackNodeCache::PropertyAnim *pa = &E->get();
- if (a->value_track_get_update_mode(i) == Animation::UPDATE_CONTINUOUS || (p_delta == 0 && a->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE)) { //delta == 0 means seek
+ Animation::UpdateMode update_mode = a->value_track_get_update_mode(i);
+
+ if (update_mode == Animation::UPDATE_CAPTURE) {
+
+ if (p_started) {
+ pa->capture = pa->object->get_indexed(pa->subpath);
+ }
+
+ int key_count = a->track_get_key_count(i);
+ if (key_count == 0)
+ continue; //eeh not worth it
+
+ float first_key_time = a->track_get_key_time(i, 0);
+ float transition = 1.0;
+ int first_key = 0;
+
+ if (first_key_time == 0.0) {
+ //ignore, use for transition
+ if (key_count == 1)
+ continue; //with one key we cant do anything
+ transition = a->track_get_key_transition(i, 0);
+ first_key_time = a->track_get_key_time(i, 1);
+ first_key = 1;
+ }
+
+ if (p_time < first_key_time) {
+ float c = Math::ease(p_time / first_key_time, transition);
+ Variant first_value = a->track_get_key_value(i, first_key);
+ Variant interp_value;
+ Variant::interpolate(pa->capture, first_value, c, interp_value);
+
+ if (pa->accum_pass != accum_pass) {
+ ERR_CONTINUE(cache_update_prop_size >= NODE_CACHE_UPDATE_MAX);
+ cache_update_prop[cache_update_prop_size++] = pa;
+ pa->value_accum = interp_value;
+ pa->accum_pass = accum_pass;
+ } else {
+ Variant::interpolate(pa->value_accum, interp_value, p_interp, pa->value_accum);
+ }
+
+ continue; //handled
+ }
+ }
+
+ if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE || (p_delta == 0 && update_mode == Animation::UPDATE_DISCRETE)) { //delta == 0 means seek
Variant value = a->value_track_interpolate(i, p_time);
@@ -414,7 +472,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
Variant::interpolate(pa->value_accum, value, p_interp, pa->value_accum);
}
- } else if (p_allow_discrete && p_delta != 0) {
+ } else if (p_is_current && p_delta != 0) {
List<int> indices;
a->value_track_get_key_indices(i, p_time, p_delta, &indices);
@@ -469,9 +527,10 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
if (!nc->node)
continue;
- if (p_delta == 0)
+ if (p_delta == 0) {
continue;
- if (!p_allow_discrete)
+ }
+ if (!p_is_current)
break;
List<int> indices;
@@ -486,6 +545,12 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
int s = params.size();
ERR_CONTINUE(s > VARIANT_ARG_MAX);
+#ifdef DEBUG_ENABLED
+ if (!nc->node->has_method(method)) {
+ ERR_PRINTS("Invalid method call '" + method + "'. '" + a->get_name() + "' at node '" + get_path() + "'.");
+ }
+#endif
+
if (can_call) {
MessageQueue::get_singleton()->push_call(
nc->node,
@@ -499,14 +564,197 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, float
}
} break;
+ case Animation::TYPE_BEZIER: {
+
+ if (!nc->node)
+ continue;
+
+ Map<StringName, TrackNodeCache::BezierAnim>::Element *E = nc->bezier_anim.find(a->track_get_path(i).get_concatenated_subnames());
+ ERR_CONTINUE(!E); //should it continue, or create a new one?
+
+ TrackNodeCache::BezierAnim *ba = &E->get();
+
+ float bezier = a->bezier_track_interpolate(i, p_time);
+ if (ba->accum_pass != accum_pass) {
+ ERR_CONTINUE(cache_update_bezier_size >= NODE_CACHE_UPDATE_MAX);
+ cache_update_bezier[cache_update_bezier_size++] = ba;
+ ba->bezier_accum = bezier;
+ ba->accum_pass = accum_pass;
+ } else {
+ ba->bezier_accum = Math::lerp(ba->bezier_accum, bezier, p_interp);
+ }
+
+ } break;
+ case Animation::TYPE_AUDIO: {
+
+ if (!nc->node)
+ continue;
+ if (p_delta == 0) {
+ continue;
+ }
+
+ if (p_seeked) {
+ //find whathever should be playing
+ int idx = a->track_find_key(i, p_time);
+ if (idx < 0)
+ continue;
+
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (!stream.is_valid()) {
+ nc->node->call("stop");
+ nc->audio_playing = false;
+ playing_caches.erase(nc);
+ } else {
+ float start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ start_ofs += p_time - a->track_get_key_time(i, idx);
+ float end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ float len = stream->get_length();
+
+ if (start_ofs > len - end_ofs) {
+ nc->node->call("stop");
+ nc->audio_playing = false;
+ playing_caches.erase(nc);
+ continue;
+ }
+
+ nc->node->call("set_stream", stream);
+ nc->node->call("play", start_ofs);
+
+ nc->audio_playing = true;
+ playing_caches.insert(nc);
+ if (len && end_ofs > 0) { //force a end at a time
+ nc->audio_len = len - start_ofs - end_ofs;
+ } else {
+ nc->audio_len = 0;
+ }
+
+ nc->audio_start = p_time;
+ }
+
+ } else {
+ //find stuff to play
+ List<int> to_play;
+ a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play);
+ if (to_play.size()) {
+ int idx = to_play.back()->get();
+
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (!stream.is_valid()) {
+ nc->node->call("stop");
+ nc->audio_playing = false;
+ playing_caches.erase(nc);
+ } else {
+ float start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ float end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ float len = stream->get_length();
+
+ nc->node->call("set_stream", stream);
+ nc->node->call("play", start_ofs);
+
+ nc->audio_playing = true;
+ playing_caches.insert(nc);
+ if (len && end_ofs > 0) { //force a end at a time
+ nc->audio_len = len - start_ofs - end_ofs;
+ } else {
+ nc->audio_len = 0;
+ }
+
+ nc->audio_start = p_time;
+ }
+ } else if (nc->audio_playing) {
+
+ bool loop = a->has_loop();
+
+ bool stop = false;
+
+ if (!loop && p_time < nc->audio_start) {
+ stop = true;
+ } else if (nc->audio_len > 0) {
+ float len = nc->audio_start > p_time ? (a->get_length() - nc->audio_start) + p_time : p_time - nc->audio_start;
+
+ if (len > nc->audio_len) {
+ stop = true;
+ }
+ }
+
+ if (stop) {
+ //time to stop
+ nc->node->call("stop");
+ nc->audio_playing = false;
+ playing_caches.erase(nc);
+ }
+ }
+ }
+
+ } break;
+ case Animation::TYPE_ANIMATION: {
+
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(nc->node);
+ if (!player)
+ continue;
+
+ if (p_delta == 0 || p_seeked) {
+ //seek
+ int idx = a->track_find_key(i, p_time);
+ if (idx < 0)
+ continue;
+
+ float pos = a->track_get_key_time(i, idx);
+
+ StringName anim_name = a->animation_track_get_key_animation(i, idx);
+ if (String(anim_name) == "[stop]" || !player->has_animation(anim_name))
+ continue;
+
+ Ref<Animation> anim = player->get_animation(anim_name);
+
+ float at_anim_pos;
+
+ if (anim->has_loop()) {
+ at_anim_pos = Math::fposmod(p_time - pos, anim->get_length()); //seek to loop
+ } else {
+ at_anim_pos = MAX(anim->get_length(), p_time - pos); //seek to end
+ }
+
+ if (player->is_playing() || p_seeked) {
+ player->play(anim_name);
+ player->seek(at_anim_pos);
+ nc->animation_playing = true;
+ playing_caches.insert(nc);
+ } else {
+ player->set_assigned_animation(anim_name);
+ player->seek(at_anim_pos, true);
+ }
+ } else {
+ //find stuff to play
+ List<int> to_play;
+ a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play);
+ if (to_play.size()) {
+ int idx = to_play.back()->get();
+
+ StringName anim_name = a->animation_track_get_key_animation(i, idx);
+ if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) {
+
+ if (playing_caches.has(nc)) {
+ playing_caches.erase(nc);
+ player->stop();
+ nc->animation_playing = false;
+ }
+ } else {
+ player->play(anim_name);
+ nc->animation_playing = true;
+ playing_caches.insert(nc);
+ }
+ }
+ }
+
+ } break;
}
}
}
-void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, float p_blend) {
+void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, float p_blend, bool p_seeked, bool p_started) {
float delta = p_delta * speed_scale * cd.speed_scale;
- bool backwards = delta < 0;
float next_pos = cd.pos + delta;
float len = cd.from->animation->get_length();
@@ -524,6 +772,8 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, f
if (&cd == &playback.current) {
+ bool backwards = delta < 0;
+
if (!backwards && cd.pos <= len && next_pos == len /*&& playback.blend.empty()*/) {
//playback finished
end_reached = true;
@@ -551,22 +801,25 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, float p_delta, f
cd.pos = next_pos;
- _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current);
+ _animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started);
}
-void AnimationPlayer::_animation_process2(float p_delta) {
+void AnimationPlayer::_animation_process2(float p_delta, bool p_started) {
Playback &c = playback;
accum_pass++;
- _animation_process_data(c.current, p_delta, 1.0f);
+ _animation_process_data(c.current, p_delta, 1.0f, c.seeked && p_delta != 0, p_started);
+ if (p_delta != 0) {
+ c.seeked = false;
+ }
List<Blend>::Element *prev = NULL;
for (List<Blend>::Element *E = c.blend.back(); E; E = prev) {
Blend &b = E->get();
float blend = b.blend_left / b.blend_time;
- _animation_process_data(b.data, p_delta, blend);
+ _animation_process_data(b.data, p_delta, blend, false, false);
b.blend_left -= Math::absf(speed_scale * p_delta);
@@ -579,20 +832,16 @@ void AnimationPlayer::_animation_process2(float p_delta) {
}
void AnimationPlayer::_animation_update_transforms() {
+ {
+ Transform t;
+ for (int i = 0; i < cache_update_size; i++) {
- for (int i = 0; i < cache_update_size; i++) {
-
- TrackNodeCache *nc = cache_update[i];
+ TrackNodeCache *nc = cache_update[i];
- ERR_CONTINUE(nc->accum_pass != accum_pass);
+ ERR_CONTINUE(nc->accum_pass != accum_pass);
- if (nc->spatial) {
-
- Transform t;
t.origin = nc->loc_accum;
- t.basis = nc->rot_accum;
- t.basis.scale(nc->scale_accum);
-
+ t.basis.set_quat_scale(nc->rot_accum, nc->scale_accum);
if (nc->skeleton && nc->bone_idx >= 0) {
nc->skeleton->set_bone_pose(nc->bone_idx, t);
@@ -654,6 +903,16 @@ void AnimationPlayer::_animation_update_transforms() {
}
cache_update_prop_size = 0;
+
+ for (int i = 0; i < cache_update_bezier_size; i++) {
+
+ TrackNodeCache::BezierAnim *ba = cache_update_bezier[i];
+
+ ERR_CONTINUE(ba->accum_pass != accum_pass);
+ ba->object->set_indexed(ba->bezier_property, ba->bezier_accum);
+ }
+
+ cache_update_bezier_size = 0;
}
void AnimationPlayer::_animation_process(float p_delta) {
@@ -662,7 +921,12 @@ void AnimationPlayer::_animation_process(float p_delta) {
end_reached = false;
end_notify = false;
- _animation_process2(p_delta);
+ _animation_process2(p_delta, playback.started);
+
+ if (playback.started) {
+ playback.started = false;
+ }
+
_animation_update_transforms();
if (end_reached) {
if (queued.size()) {
@@ -696,8 +960,6 @@ Error AnimationPlayer::add_animation(const StringName &p_name, const Ref<Animati
ERR_FAIL_COND_V(p_animation.is_null(), ERR_INVALID_PARAMETER);
- //print_line("Add anim: "+String(p_name)+" name: "+p_animation->get_name());
-
if (animation_set.has(p_name)) {
_unref_anim(animation_set[p_name].animation);
@@ -867,7 +1129,7 @@ void AnimationPlayer::queue(const StringName &p_name) {
void AnimationPlayer::clear_queue() {
queued.clear();
-};
+}
void AnimationPlayer::play_backwards(const StringName &p_name, float p_custom_blend) {
@@ -932,10 +1194,14 @@ void AnimationPlayer::play(const StringName &p_name, float p_custom_blend, float
}
}
+ _stop_playing_caches();
+
c.current.from = &animation_set[name];
c.current.pos = p_from_end ? c.current.from->animation->get_length() : 0;
c.current.speed_scale = p_custom_scale;
c.assigned = p_name;
+ c.seeked = false;
+ c.started = true;
if (!end_reached)
queued.clear();
@@ -1006,10 +1272,12 @@ String AnimationPlayer::get_assigned_animation() const {
void AnimationPlayer::stop(bool p_reset) {
+ _stop_playing_caches();
Playback &c = playback;
c.blend.clear();
if (p_reset) {
c.current.from = NULL;
+ c.current.speed_scale = 1;
}
_set_process(false);
queued.clear();
@@ -1024,6 +1292,13 @@ 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(float p_time, bool p_update) {
@@ -1036,6 +1311,7 @@ void AnimationPlayer::seek(float p_time, bool p_update) {
}
playback.current.pos = p_time;
+ playback.seeked = true;
if (p_update) {
_animation_process(0);
}
@@ -1078,6 +1354,25 @@ float AnimationPlayer::get_current_animation_length() const {
void AnimationPlayer::_animation_changed() {
clear_caches();
+ emit_signal("caches_cleared");
+}
+
+void AnimationPlayer::_stop_playing_caches() {
+
+ for (Set<TrackNodeCache *>::Element *E = playing_caches.front(); E; E = E->next()) {
+
+ if (E->get()->node && E->get()->audio_playing) {
+ E->get()->node->call("stop");
+ }
+ if (E->get()->node && E->get()->animation_playing) {
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(E->get()->node);
+ if (!player)
+ continue;
+ player->stop();
+ }
+ }
+
+ playing_caches.clear();
}
void AnimationPlayer::_node_removed(Node *p_node) {
@@ -1087,6 +1382,8 @@ void AnimationPlayer::_node_removed(Node *p_node) {
void AnimationPlayer::clear_caches() {
+ _stop_playing_caches();
+
node_cache_map.clear();
for (Map<StringName, AnimationData>::Element *E = animation_set.front(); E; E = E->next()) {
@@ -1096,6 +1393,7 @@ void AnimationPlayer::clear_caches() {
cache_update_size = 0;
cache_update_prop_size = 0;
+ cache_update_bezier_size = 0;
}
void AnimationPlayer::set_active(bool p_active) {
@@ -1202,7 +1500,7 @@ NodePath AnimationPlayer::get_root() const {
void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
String pf = p_function;
- if (p_function == "play" || p_function == "remove_animation" || p_function == "has_animation" || p_function == "queue") {
+ if (p_function == "play" || p_function == "play_backwards" || p_function == "remove_animation" || p_function == "has_animation" || p_function == "queue") {
List<StringName> al;
get_animation_list(&al);
for (List<StringName>::Element *E = al.front(); E; E = E->next()) {
@@ -1315,6 +1613,7 @@ void AnimationPlayer::_bind_methods() {
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);
@@ -1343,7 +1642,7 @@ void AnimationPlayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::REAL, "current_animation_position", PROPERTY_HINT_NONE, "", 0), "", "get_current_animation_position");
ADD_GROUP("Playback Options", "playback_");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_animation_process_mode", "get_animation_process_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_animation_process_mode", "get_animation_process_mode");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "playback_default_blend_time", PROPERTY_HINT_RANGE, "0,4096,0.01"), "set_default_blend_time", "get_default_blend_time");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_active", PROPERTY_HINT_NONE, "", 0), "set_active", "is_active");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "playback_speed", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale");
@@ -1351,9 +1650,11 @@ void AnimationPlayer::_bind_methods() {
ADD_SIGNAL(MethodInfo("animation_finished", PropertyInfo(Variant::STRING, "anim_name")));
ADD_SIGNAL(MethodInfo("animation_changed", PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name")));
ADD_SIGNAL(MethodInfo("animation_started", PropertyInfo(Variant::STRING, "anim_name")));
+ ADD_SIGNAL(MethodInfo("caches_cleared"));
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE);
+ BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL);
}
AnimationPlayer::AnimationPlayer() {
@@ -1361,6 +1662,7 @@ AnimationPlayer::AnimationPlayer() {
accum_pass = 1;
cache_update_size = 0;
cache_update_prop_size = 0;
+ cache_update_bezier_size = 0;
speed_scale = 1;
end_reached = false;
end_notify = false;
@@ -1370,6 +1672,8 @@ AnimationPlayer::AnimationPlayer() {
root = SceneStringNames::get_singleton()->path_pp;
playing = false;
active = true;
+ playback.seeked = false;
+ playback.started = false;
}
AnimationPlayer::~AnimationPlayer() {
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index ef758bac44..f50b2454ec 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -65,6 +65,7 @@ public:
enum AnimationProcessMode {
ANIMATION_PROCESS_PHYSICS,
ANIMATION_PROCESS_IDLE,
+ ANIMATION_PROCESS_MANUAL,
};
private:
@@ -98,6 +99,12 @@ private:
Vector3 scale_accum;
uint64_t accum_pass;
+ bool audio_playing;
+ float audio_start;
+ float audio_len;
+
+ bool animation_playing;
+
struct PropertyAnim {
TrackNodeCache *owner;
@@ -106,6 +113,7 @@ private:
Object *object;
Variant value_accum;
uint64_t accum_pass;
+ Variant capture;
PropertyAnim() {
accum_pass = 0;
object = NULL;
@@ -114,6 +122,22 @@ private:
Map<StringName, PropertyAnim> property_anim;
+ struct BezierAnim {
+
+ Vector<StringName> bezier_property;
+ TrackNodeCache *owner;
+ float bezier_accum;
+ Object *object;
+ uint64_t accum_pass;
+ BezierAnim() {
+ accum_pass = 0;
+ bezier_accum = 0;
+ object = NULL;
+ }
+ };
+
+ Map<StringName, BezierAnim> bezier_anim;
+
TrackNodeCache() {
skeleton = NULL;
spatial = NULL;
@@ -121,6 +145,8 @@ private:
accum_pass = 0;
bone_idx = -1;
node_2d = NULL;
+ audio_playing = false;
+ animation_playing = false;
}
};
@@ -146,6 +172,10 @@ private:
int cache_update_size;
TrackNodeCache::PropertyAnim *cache_update_prop[NODE_CACHE_UPDATE_MAX];
int cache_update_prop_size;
+ TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX];
+ int cache_update_bezier_size;
+ Set<TrackNodeCache *> playing_caches;
+
Map<Ref<Animation>, int> used_anims;
uint64_t accum_pass;
@@ -202,6 +232,8 @@ private:
List<Blend> blend;
PlaybackData current;
StringName assigned;
+ bool seeked;
+ bool started;
} playback;
List<StringName> queued;
@@ -216,15 +248,16 @@ private:
NodePath root;
- void _animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_allow_discrete = true);
+ void _animation_process_animation(AnimationData *p_anim, float p_time, float p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false);
void _ensure_node_caches(AnimationData *p_anim);
- void _animation_process_data(PlaybackData &cd, float p_delta, float p_blend);
- void _animation_process2(float p_delta);
+ void _animation_process_data(PlaybackData &cd, float p_delta, float p_blend, bool p_seeked, bool p_started);
+ void _animation_process2(float p_delta, bool p_started);
void _animation_update_transforms();
void _animation_process(float p_delta);
void _node_removed(Node *p_node);
+ void _stop_playing_caches();
// bind helpers
PoolVector<String> _get_animation_list() const {
@@ -293,6 +326,7 @@ public:
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;
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
new file mode 100644
index 0000000000..73bd00e456
--- /dev/null
+++ b/scene/animation/animation_tree.cpp
@@ -0,0 +1,1504 @@
+#include "animation_tree.h"
+#include "animation_blend_tree.h"
+#include "core/method_bind_ext.gen.inc"
+#include "engine.h"
+#include "scene/scene_string_names.h"
+#include "servers/audio/audio_stream.h"
+
+void AnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const {
+}
+
+Variant AnimationNode::get_parameter_default_value(const StringName &p_parameter) const {
+ return Variant();
+}
+
+void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_value) {
+ ERR_FAIL_COND(!state);
+ ERR_FAIL_COND(!state->tree->property_parent_map.has(base_path));
+ ERR_FAIL_COND(!state->tree->property_parent_map[base_path].has(p_name));
+ StringName path = state->tree->property_parent_map[base_path][p_name];
+
+ state->tree->property_map[path] = p_value;
+}
+
+Variant AnimationNode::get_parameter(const StringName &p_name) const {
+ ERR_FAIL_COND_V(!state, Variant());
+ ERR_FAIL_COND_V(!state->tree->property_parent_map.has(base_path), Variant());
+ ERR_FAIL_COND_V(!state->tree->property_parent_map[base_path].has(p_name), Variant());
+
+ StringName path = state->tree->property_parent_map[base_path][p_name];
+ return state->tree->property_map[path];
+}
+
+void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) {
+}
+
+void AnimationNode::blend_animation(const StringName &p_animation, float p_time, float p_delta, bool p_seeked, float p_blend) {
+
+ ERR_FAIL_COND(!state);
+ ERR_FAIL_COND(!state->player->has_animation(p_animation));
+
+ Ref<Animation> animation = state->player->get_animation(p_animation);
+
+ if (animation.is_null()) {
+
+ AnimationNodeBlendTree *btree = Object::cast_to<AnimationNodeBlendTree>(parent);
+ if (btree) {
+ String name = btree->get_node_name(Ref<AnimationNodeAnimation>(this));
+ make_invalid(vformat(RTR("In node '%s', invalid animation: '%s'."), name, p_animation));
+ } else {
+ make_invalid(vformat(RTR("Invalid animation: '%s'."), p_animation));
+ }
+ return;
+ }
+
+ ERR_FAIL_COND(!animation.is_valid());
+
+ AnimationState anim_state;
+ anim_state.blend = p_blend;
+ anim_state.track_blends = &blends;
+ anim_state.delta = p_delta;
+ anim_state.time = p_time;
+ anim_state.animation = animation;
+ anim_state.seeked = p_seeked;
+
+ state->animation_states.push_back(anim_state);
+}
+
+float AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, float p_time, bool p_seek, const Vector<StringName> &p_connections) {
+
+ base_path = p_base_path;
+ parent = p_parent;
+ connections = p_connections;
+ state = p_state;
+
+ float t = process(p_time, p_seek);
+
+ state = NULL;
+ parent = NULL;
+ base_path = StringName();
+ connections.clear();
+
+ return t;
+}
+
+void AnimationNode::make_invalid(const String &p_reason) {
+ ERR_FAIL_COND(!state);
+ state->valid = false;
+ if (state->invalid_reasons != String()) {
+ state->invalid_reasons += "\n";
+ }
+ state->invalid_reasons += "- " + p_reason;
+}
+
+float AnimationNode::blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize) {
+ ERR_FAIL_INDEX_V(p_input, inputs.size(), 0);
+ ERR_FAIL_COND_V(!state, 0);
+
+ AnimationNodeBlendTree *blend_tree = Object::cast_to<AnimationNodeBlendTree>(parent);
+ ERR_FAIL_COND_V(!blend_tree, 0);
+
+ StringName node_name = connections[p_input];
+
+ if (!blend_tree->has_node(node_name)) {
+ String name = blend_tree->get_node_name(Ref<AnimationNode>(this));
+ make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), name));
+ return 0;
+ }
+
+ Ref<AnimationNode> node = blend_tree->get_node(node_name);
+
+ //inputs.write[p_input].last_pass = state->last_pass;
+ float activity = 0;
+ float ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), NULL, node, p_time, p_seek, p_blend, p_filter, p_optimize, &activity);
+
+ Vector<AnimationTree::Activity> *activity_ptr = state->tree->input_activity_map.getptr(base_path);
+
+ if (activity_ptr && p_input < activity_ptr->size()) {
+ activity_ptr->write[p_input].last_pass = state->last_pass;
+ activity_ptr->write[p_input].activity = activity;
+ }
+ return ret;
+}
+
+float AnimationNode::blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize) {
+
+ return _blend_node(p_sub_path, Vector<StringName>(), this, p_node, p_time, p_seek, p_blend, p_filter, p_optimize);
+}
+
+float AnimationNode::_blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize, float *r_max) {
+
+ ERR_FAIL_COND_V(!p_node.is_valid(), 0);
+ ERR_FAIL_COND_V(!state, 0);
+
+ int blend_count = blends.size();
+
+ if (p_node->blends.size() != blend_count) {
+ p_node->blends.resize(blend_count);
+ }
+
+ float *blendw = p_node->blends.ptrw();
+ const float *blendr = blends.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
+ }
+
+ const NodePath *K = NULL;
+ while ((K = filter.next(K))) {
+ if (!state->track_map.has(*K)) {
+ continue;
+ }
+ int idx = state->track_map[*K];
+ 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 dont
+ for (int i = 0; i < blend_count; i++) {
+ if (blendw[i] == 0) //not filtered, does not pass
+ continue;
+
+ blendw[i] = blendr[i] * p_blend;
+ if (blendw[i] > CMP_EPSILON) {
+ any_valid = true;
+ }
+ }
+
+ } break;
+ case FILTER_STOP: {
+
+ //values filtered dont 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_blend;
+ if (blendw[i] > CMP_EPSILON) {
+ 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_blend; //filtered, blend
+ } else {
+ blendw[i] = blendr[i]; //not filtered, do not blend
+ }
+
+ if (blendw[i] > CMP_EPSILON) {
+ any_valid = true;
+ }
+ }
+
+ } break;
+ }
+ } else {
+ for (int i = 0; i < blend_count; i++) {
+
+ //regular blend
+ blendw[i] = blendr[i] * p_blend;
+ if (blendw[i] > CMP_EPSILON) {
+ any_valid = true;
+ }
+ }
+ }
+
+ if (r_max) {
+ *r_max = 0;
+ for (int i = 0; i < blend_count; i++) {
+ *r_max = MAX(*r_max, blendw[i]);
+ }
+ }
+
+ if (!p_seek && p_optimize && !any_valid) //pointless to go on, all are zero
+ return 0;
+
+ 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(base_path) + String(p_subpath) + "/";
+ } else {
+ ERR_FAIL_COND_V(!parent, 0);
+ new_parent = parent;
+ new_path = String(parent->base_path) + String(p_subpath) + "/";
+ }
+ return p_node->_pre_process(new_path, new_parent, state, p_time, p_seek, p_connections);
+}
+
+int AnimationNode::get_input_count() const {
+
+ return inputs.size();
+}
+String AnimationNode::get_input_name(int p_input) {
+ ERR_FAIL_INDEX_V(p_input, inputs.size(), String());
+ return inputs[p_input].name;
+}
+
+String AnimationNode::get_caption() const {
+
+ if (get_script_instance()) {
+ return get_script_instance()->call("get_caption");
+ }
+
+ return "Node";
+}
+
+void AnimationNode::add_input(const String &p_name) {
+ //root nodes cant add inputs
+ ERR_FAIL_COND(Object::cast_to<AnimationRootNode>(this) != NULL)
+ Input input;
+ ERR_FAIL_COND(p_name.find(".") != -1 || p_name.find("/") != -1);
+ input.name = p_name;
+ inputs.push_back(input);
+ emit_changed();
+}
+
+void AnimationNode::set_input_name(int p_input, const String &p_name) {
+ ERR_FAIL_INDEX(p_input, inputs.size());
+ ERR_FAIL_COND(p_name.find(".") != -1 || p_name.find("/") != -1);
+ inputs.write[p_input].name = p_name;
+ emit_changed();
+}
+
+void AnimationNode::remove_input(int p_index) {
+ ERR_FAIL_INDEX(p_index, inputs.size());
+ inputs.remove(p_index);
+ emit_changed();
+}
+
+float AnimationNode::process(float p_time, bool p_seek) {
+
+ if (get_script_instance()) {
+ return get_script_instance()->call("process", p_time, p_seek);
+ }
+
+ return 0;
+}
+
+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;
+}
+
+bool AnimationNode::is_path_filtered(const NodePath &p_path) const {
+ return filter.has(p_path);
+}
+
+bool AnimationNode::has_filter() const {
+ return false;
+}
+
+Array AnimationNode::_get_filters() const {
+
+ Array paths;
+
+ const NodePath *K = NULL;
+ while ((K = filter.next(K))) {
+ paths.push_back(String(*K)); //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 &property) const {
+ if (!has_filter() && (property.name == "filter_enabled" || property.name == "filters")) {
+ property.usage = 0;
+ }
+}
+
+Ref<AnimationNode> AnimationNode::get_child_by_name(const StringName &p_name) {
+ return Ref<AnimationNode>();
+}
+
+void AnimationNode::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("get_input_count"), &AnimationNode::get_input_count);
+ ClassDB::bind_method(D_METHOD("get_input_name", "input"), &AnimationNode::get_input_name);
+
+ ClassDB::bind_method(D_METHOD("add_input", "name"), &AnimationNode::add_input);
+ ClassDB::bind_method(D_METHOD("remove_input", "index"), &AnimationNode::remove_input);
+
+ ClassDB::bind_method(D_METHOD("set_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", "blend"), &AnimationNode::blend_animation);
+ ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true));
+
+ ClassDB::bind_method(D_METHOD("set_parameter", "name", "value"), &AnimationNode::set_parameter);
+ ClassDB::bind_method(D_METHOD("get_parameter", "name"), &AnimationNode::get_parameter);
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter_enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_filter_enabled", "is_filter_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "filters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_filters", "_get_filters");
+
+ BIND_VMETHOD(MethodInfo("process", PropertyInfo(Variant::REAL, "time"), PropertyInfo(Variant::BOOL, "seek")));
+ BIND_VMETHOD(MethodInfo(Variant::STRING, "get_caption"));
+ BIND_VMETHOD(MethodInfo(Variant::STRING, "has_filter"));
+
+ ADD_SIGNAL(MethodInfo("removed_from_graph"));
+
+ ADD_SIGNAL(MethodInfo("tree_changed"));
+
+ BIND_ENUM_CONSTANT(FILTER_IGNORE);
+ BIND_ENUM_CONSTANT(FILTER_PASS);
+ BIND_ENUM_CONSTANT(FILTER_STOP);
+ BIND_ENUM_CONSTANT(FILTER_BLEND);
+}
+
+AnimationNode::AnimationNode() {
+
+ state = NULL;
+ parent = NULL;
+ filter_enabled = false;
+}
+
+////////////////////
+
+void AnimationTree::set_tree_root(const Ref<AnimationNode> &p_root) {
+
+ if (root.is_valid()) {
+ root->disconnect("tree_changed", this, "_tree_changed");
+ }
+
+ root = p_root;
+
+ if (root.is_valid()) {
+ root->connect("tree_changed", this, "_tree_changed");
+ }
+
+ properties_dirty = true;
+
+ update_configuration_warning();
+}
+
+Ref<AnimationNode> AnimationTree::get_tree_root() const {
+ return root;
+}
+
+void AnimationTree::set_active(bool p_active) {
+
+ if (active == p_active)
+ return;
+
+ active = p_active;
+ started = active;
+
+ if (process_mode == ANIMATION_PROCESS_IDLE) {
+ set_process_internal(active);
+ } else {
+
+ set_physics_process_internal(active);
+ }
+
+ if (!active && is_inside_tree()) {
+ for (Set<TrackCache *>::Element *E = playing_caches.front(); E; E = E->next()) {
+
+ if (ObjectDB::get_instance(E->get()->object_id)) {
+ E->get()->object->call("stop");
+ }
+ }
+
+ playing_caches.clear();
+ }
+}
+
+bool AnimationTree::is_active() const {
+
+ return active;
+}
+
+void AnimationTree::set_process_mode(AnimationProcessMode p_mode) {
+
+ if (process_mode == p_mode)
+ return;
+
+ bool was_active = is_active();
+ if (was_active) {
+ set_active(false);
+ }
+
+ process_mode = p_mode;
+
+ if (was_active) {
+ set_active(true);
+ }
+}
+
+AnimationTree::AnimationProcessMode AnimationTree::get_process_mode() const {
+ return process_mode;
+}
+
+void AnimationTree::_node_removed(Node *p_node) {
+ cache_valid = false;
+}
+
+bool AnimationTree::_update_caches(AnimationPlayer *player) {
+
+ setup_pass++;
+
+ if (!player->has_node(player->get_root())) {
+ ERR_PRINT("AnimationTree: AnimationPlayer root is invalid.");
+ set_active(false);
+ return false;
+ }
+ Node *parent = player->get_node(player->get_root());
+
+ List<StringName> sname;
+ player->get_animation_list(&sname);
+
+ for (List<StringName>::Element *E = sname.front(); E; E = E->next()) {
+ Ref<Animation> anim = player->get_animation(E->get());
+ for (int i = 0; i < anim->get_track_count(); i++) {
+ NodePath path = anim->track_get_path(i);
+ Animation::TrackType track_type = anim->track_get_type(i);
+
+ TrackCache *track = NULL;
+ if (track_cache.has(path)) {
+ track = track_cache.get(path);
+ }
+
+ //if not valid, delete track
+ if (track && (track->type != track_type || ObjectDB::get_instance(track->object_id) == NULL)) {
+ playing_caches.erase(track);
+ memdelete(track);
+ track_cache.erase(path);
+ track = NULL;
+ }
+
+ if (!track) {
+
+ RES resource;
+ Vector<StringName> leftover_path;
+ Node *child = parent->get_node_and_resource(path, resource, leftover_path);
+
+ if (!child) {
+ ERR_PRINTS("AnimationTree: '" + String(E->get()) + "', couldn't resolve track: '" + String(path) + "'");
+ continue;
+ }
+
+ if (!child->is_connected("tree_exited", this, "_node_removed")) {
+ child->connect("tree_exited", this, "_node_removed", varray(child));
+ }
+
+ switch (track_type) {
+ case Animation::TYPE_VALUE: {
+
+ TrackCacheValue *track_value = memnew(TrackCacheValue);
+
+ if (resource.is_valid()) {
+ track_value->object = resource.ptr();
+ } else {
+ track_value->object = child;
+ }
+
+ track_value->subpath = leftover_path;
+ track_value->object_id = track_value->object->get_instance_id();
+
+ track = track_value;
+
+ } break;
+ case Animation::TYPE_TRANSFORM: {
+
+ Spatial *spatial = Object::cast_to<Spatial>(child);
+
+ if (!spatial) {
+ ERR_PRINTS("AnimationTree: '" + String(E->get()) + "', transform track does not point to spatial: '" + String(path) + "'");
+ continue;
+ }
+
+ TrackCacheTransform *track_xform = memnew(TrackCacheTransform);
+
+ track_xform->spatial = spatial;
+ track_xform->skeleton = NULL;
+ track_xform->bone_idx = -1;
+
+ if (path.get_subname_count() == 1 && Object::cast_to<Skeleton>(spatial)) {
+
+ Skeleton *sk = Object::cast_to<Skeleton>(spatial);
+ int bone_idx = sk->find_bone(path.get_subname(0));
+ if (bone_idx != -1 && !sk->is_bone_ignore_animation(bone_idx)) {
+
+ track_xform->skeleton = sk;
+ track_xform->bone_idx = bone_idx;
+ }
+ }
+
+ track_xform->object = spatial;
+ track_xform->object_id = track_xform->object->get_instance_id();
+
+ track = track_xform;
+
+ } break;
+ case Animation::TYPE_METHOD: {
+
+ TrackCacheMethod *track_method = memnew(TrackCacheMethod);
+
+ if (resource.is_valid()) {
+ track_method->object = resource.ptr();
+ } else {
+ track_method->object = child;
+ }
+
+ track_method->object_id = track_method->object->get_instance_id();
+
+ track = track_method;
+
+ } break;
+ case Animation::TYPE_BEZIER: {
+
+ TrackCacheBezier *track_bezier = memnew(TrackCacheBezier);
+
+ if (resource.is_valid()) {
+ track_bezier->object = resource.ptr();
+ } else {
+ track_bezier->object = child;
+ }
+
+ track_bezier->subpath = leftover_path;
+ track_bezier->object_id = track_bezier->object->get_instance_id();
+
+ track = track_bezier;
+ } break;
+ case Animation::TYPE_AUDIO: {
+
+ TrackCacheAudio *track_audio = memnew(TrackCacheAudio);
+
+ track_audio->object = child;
+ track_audio->object_id = track_audio->object->get_instance_id();
+
+ track = track_audio;
+
+ } break;
+ case Animation::TYPE_ANIMATION: {
+
+ TrackCacheAnimation *track_animation = memnew(TrackCacheAnimation);
+
+ track_animation->object = child;
+ track_animation->object_id = track_animation->object->get_instance_id();
+
+ track = track_animation;
+
+ } break;
+ }
+
+ track_cache[path] = track;
+ }
+
+ track->setup_pass = setup_pass;
+ }
+ }
+
+ List<NodePath> to_delete;
+
+ const NodePath *K = NULL;
+ while ((K = track_cache.next(K))) {
+ TrackCache *tc = track_cache[*K];
+ if (tc->setup_pass != setup_pass) {
+ to_delete.push_back(*K);
+ }
+ }
+
+ while (to_delete.front()) {
+ NodePath np = to_delete.front()->get();
+ memdelete(track_cache[np]);
+ track_cache.erase(np);
+ to_delete.pop_front();
+ }
+
+ state.track_map.clear();
+
+ K = NULL;
+ int idx = 0;
+ while ((K = track_cache.next(K))) {
+ state.track_map[*K] = idx;
+ idx++;
+ }
+
+ state.track_count = idx;
+
+ cache_valid = true;
+
+ return true;
+}
+
+void AnimationTree::_clear_caches() {
+
+ const NodePath *K = NULL;
+ while ((K = track_cache.next(K))) {
+ memdelete(track_cache[*K]);
+ }
+ playing_caches.clear();
+
+ track_cache.clear();
+ cache_valid = false;
+}
+
+void AnimationTree::_process_graph(float p_delta) {
+
+ _update_properties(); //if properties need updating, update them
+
+ //check all tracks, see if they need modification
+
+ root_motion_transform = Transform();
+
+ if (!root.is_valid()) {
+ ERR_PRINT("AnimationTree: root AnimationNode is not set, disabling playback.");
+ set_active(false);
+ cache_valid = false;
+ return;
+ }
+
+ if (!has_node(animation_player)) {
+ ERR_PRINT("AnimationTree: no valid AnimationPlayer path set, disabling playback");
+ set_active(false);
+ cache_valid = false;
+ return;
+ }
+
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node(animation_player));
+
+ ObjectID current_animation_player = 0;
+
+ if (player) {
+ current_animation_player = player->get_instance_id();
+ }
+
+ if (last_animation_player != current_animation_player) {
+
+ if (last_animation_player) {
+ Object *old_player = ObjectDB::get_instance(last_animation_player);
+ if (old_player) {
+ old_player->disconnect("caches_cleared", this, "_clear_caches");
+ }
+ }
+
+ if (player) {
+ player->connect("caches_cleared", this, "_clear_caches");
+ }
+
+ last_animation_player = current_animation_player;
+ }
+
+ if (!player) {
+ ERR_PRINT("AnimationTree: path points to a node not an AnimationPlayer, disabling playback");
+ set_active(false);
+ cache_valid = false;
+ return;
+ }
+
+ if (!cache_valid) {
+ if (!_update_caches(player)) {
+ return;
+ }
+ }
+
+ { //setup
+
+ process_pass++;
+
+ state.valid = true;
+ state.invalid_reasons = "";
+ state.animation_states.clear(); //will need to be re-created
+ state.valid = true;
+ state.player = player;
+ state.last_pass = process_pass;
+ state.tree = this;
+
+ // root source blends
+
+ root->blends.resize(state.track_count);
+ float *src_blendsw = root->blends.ptrw();
+ for (int i = 0; i < state.track_count; i++) {
+ src_blendsw[i] = 1.0; //by default all go to 1 for the root input
+ }
+ }
+
+ //process
+
+ {
+
+ if (started) {
+ //if started, seek
+ root->_pre_process(SceneStringNames::get_singleton()->parameters_base_path, NULL, &state, 0, true, Vector<StringName>());
+ started = false;
+ }
+
+ root->_pre_process(SceneStringNames::get_singleton()->parameters_base_path, NULL, &state, p_delta, false, Vector<StringName>());
+ }
+
+ if (!state.valid) {
+ return; //state is not valid. do nothing.
+ }
+ //apply value/transform/bezier blends to track caches and execute method/audio/animation tracks
+
+ {
+
+ bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint();
+
+ for (List<AnimationNode::AnimationState>::Element *E = state.animation_states.front(); E; E = E->next()) {
+
+ const AnimationNode::AnimationState &as = E->get();
+
+ Ref<Animation> a = as.animation;
+ float time = as.time;
+ float delta = as.delta;
+ bool seeked = as.seeked;
+
+ for (int i = 0; i < a->get_track_count(); i++) {
+
+ NodePath path = a->track_get_path(i);
+ TrackCache *track = track_cache[path];
+ if (track->type != a->track_get_type(i)) {
+ continue; //may happen should not
+ }
+
+ track->root_motion = root_motion_track == path;
+
+ ERR_CONTINUE(!state.track_map.has(path));
+ int blend_idx = state.track_map[path];
+
+ ERR_CONTINUE(blend_idx < 0 || blend_idx >= state.track_count);
+
+ float blend = (*as.track_blends)[blend_idx];
+
+ if (blend < CMP_EPSILON)
+ continue; //nothing to blend
+
+ switch (track->type) {
+
+ case Animation::TYPE_TRANSFORM: {
+
+ TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
+
+ if (t->process_pass != process_pass) {
+
+ t->process_pass = process_pass;
+ t->loc = Vector3();
+ t->rot = Quat();
+ t->rot_blend_accum = 0;
+ t->scale = Vector3();
+ }
+
+ if (track->root_motion) {
+
+ float prev_time = time - delta;
+ if (prev_time < 0) {
+ if (!a->has_loop()) {
+ prev_time = 0;
+ } else {
+ prev_time = a->get_length() + prev_time;
+ }
+ }
+
+ Vector3 loc[2];
+ Quat rot[2];
+ Vector3 scale[2];
+
+ if (prev_time > time) {
+
+ Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]);
+ if (err != OK) {
+ continue;
+ }
+
+ a->transform_track_interpolate(i, a->get_length(), &loc[1], &rot[1], &scale[1]);
+
+ t->loc += (loc[1] - loc[0]) * blend;
+ t->scale += (scale[1] - scale[0]) * blend;
+ Quat q = Quat().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized();
+ t->rot = (t->rot * q).normalized();
+
+ prev_time = 0;
+ }
+
+ Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]);
+ if (err != OK) {
+ continue;
+ }
+
+ a->transform_track_interpolate(i, time, &loc[1], &rot[1], &scale[1]);
+
+ t->loc += (loc[1] - loc[0]) * blend;
+ t->scale += (scale[1] - scale[0]) * blend;
+ Quat q = Quat().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized();
+ t->rot = (t->rot * q).normalized();
+
+ prev_time = 0;
+
+ } else {
+ Vector3 loc;
+ Quat rot;
+ Vector3 scale;
+
+ Error err = a->transform_track_interpolate(i, time, &loc, &rot, &scale);
+ //ERR_CONTINUE(err!=OK); //used for testing, should be removed
+
+ scale -= Vector3(1.0, 1.0, 1.0); //helps make it work properly with Add nodes
+
+ if (err != OK)
+ continue;
+
+ t->loc = t->loc.linear_interpolate(loc, blend);
+ if (t->rot_blend_accum == 0) {
+ t->rot = rot;
+ t->rot_blend_accum = blend;
+ } else {
+ float rot_total = t->rot_blend_accum + blend;
+ t->rot = rot.slerp(t->rot, t->rot_blend_accum / rot_total).normalized();
+ t->rot_blend_accum = rot_total;
+ }
+ t->scale = t->scale.linear_interpolate(scale, blend);
+ }
+
+ } break;
+ case Animation::TYPE_VALUE: {
+
+ TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
+
+ Animation::UpdateMode update_mode = a->value_track_get_update_mode(i);
+
+ if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) { //delta == 0 means seek
+
+ Variant value = a->value_track_interpolate(i, time);
+
+ if (value == Variant())
+ continue;
+
+ if (t->process_pass != process_pass) {
+ Variant::CallError ce;
+ t->value = Variant::construct(value.get_type(), NULL, 0, ce); //reset
+ t->process_pass = process_pass;
+ }
+
+ Variant::interpolate(t->value, value, blend, t->value);
+
+ } else if (delta != 0) {
+
+ List<int> indices;
+ a->value_track_get_key_indices(i, time, delta, &indices);
+
+ for (List<int>::Element *F = indices.front(); F; F = F->next()) {
+
+ Variant value = a->track_get_key_value(i, F->get());
+ t->object->set_indexed(t->subpath, value);
+ }
+ }
+
+ } break;
+ case Animation::TYPE_METHOD: {
+
+ if (delta == 0) {
+ continue;
+ }
+ TrackCacheMethod *t = static_cast<TrackCacheMethod *>(track);
+
+ List<int> indices;
+
+ a->method_track_get_key_indices(i, time, delta, &indices);
+
+ for (List<int>::Element *E = indices.front(); E; E = E->next()) {
+
+ StringName method = a->method_track_get_name(i, E->get());
+ Vector<Variant> params = a->method_track_get_params(i, E->get());
+
+ int s = params.size();
+
+ ERR_CONTINUE(s > VARIANT_ARG_MAX);
+ if (can_call) {
+ t->object->call_deferred(
+ method,
+ s >= 1 ? params[0] : Variant(),
+ s >= 2 ? params[1] : Variant(),
+ s >= 3 ? params[2] : Variant(),
+ s >= 4 ? params[3] : Variant(),
+ s >= 5 ? params[4] : Variant());
+ }
+ }
+
+ } break;
+ case Animation::TYPE_BEZIER: {
+
+ TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
+
+ float bezier = a->bezier_track_interpolate(i, time);
+
+ if (t->process_pass != process_pass) {
+ t->value = 0;
+ t->process_pass = process_pass;
+ }
+
+ t->value = Math::lerp(t->value, bezier, blend);
+
+ } break;
+ case Animation::TYPE_AUDIO: {
+
+ TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
+
+ if (seeked) {
+ //find whathever should be playing
+ int idx = a->track_find_key(i, time);
+ if (idx < 0)
+ continue;
+
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (!stream.is_valid()) {
+ t->object->call("stop");
+ t->playing = false;
+ playing_caches.erase(t);
+ } else {
+ float start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ start_ofs += time - a->track_get_key_time(i, idx);
+ float end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ float len = stream->get_length();
+
+ if (start_ofs > len - end_ofs) {
+ t->object->call("stop");
+ t->playing = false;
+ playing_caches.erase(t);
+ continue;
+ }
+
+ t->object->call("set_stream", stream);
+ t->object->call("play", start_ofs);
+
+ t->playing = true;
+ playing_caches.insert(t);
+ if (len && end_ofs > 0) { //force a end at a time
+ t->len = len - start_ofs - end_ofs;
+ } else {
+ t->len = 0;
+ }
+
+ t->start = time;
+ }
+
+ } else {
+ //find stuff to play
+ List<int> to_play;
+ a->track_get_key_indices_in_range(i, time, delta, &to_play);
+ if (to_play.size()) {
+ int idx = to_play.back()->get();
+
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (!stream.is_valid()) {
+ t->object->call("stop");
+ t->playing = false;
+ playing_caches.erase(t);
+ } else {
+ float start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ float end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ float len = stream->get_length();
+
+ t->object->call("set_stream", stream);
+ t->object->call("play", start_ofs);
+
+ t->playing = true;
+ playing_caches.insert(t);
+ if (len && end_ofs > 0) { //force a end at a time
+ t->len = len - start_ofs - end_ofs;
+ } else {
+ t->len = 0;
+ }
+
+ t->start = time;
+ }
+ } else if (t->playing) {
+
+ bool loop = a->has_loop();
+
+ bool stop = false;
+
+ if (!loop && time < t->start) {
+ stop = true;
+ } else if (t->len > 0) {
+ float len = t->start > time ? (a->get_length() - t->start) + time : time - t->start;
+
+ if (len > t->len) {
+ stop = true;
+ }
+ }
+
+ if (stop) {
+ //time to stop
+ t->object->call("stop");
+ t->playing = false;
+ playing_caches.erase(t);
+ }
+ }
+ }
+
+ float db = Math::linear2db(MAX(blend, 0.00001));
+ if (t->object->has_method("set_unit_db")) {
+ t->object->call("set_unit_db", db);
+ } else {
+ t->object->call("set_volume_db", db);
+ }
+ } break;
+ case Animation::TYPE_ANIMATION: {
+
+ TrackCacheAnimation *t = static_cast<TrackCacheAnimation *>(track);
+
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(t->object);
+
+ if (!player)
+ continue;
+
+ if (delta == 0 || seeked) {
+ //seek
+ int idx = a->track_find_key(i, time);
+ if (idx < 0)
+ continue;
+
+ float pos = a->track_get_key_time(i, idx);
+
+ StringName anim_name = a->animation_track_get_key_animation(i, idx);
+ if (String(anim_name) == "[stop]" || !player->has_animation(anim_name))
+ continue;
+
+ Ref<Animation> anim = player->get_animation(anim_name);
+
+ float at_anim_pos;
+
+ if (anim->has_loop()) {
+ at_anim_pos = Math::fposmod(time - pos, anim->get_length()); //seek to loop
+ } else {
+ at_anim_pos = MAX(anim->get_length(), time - pos); //seek to end
+ }
+
+ if (player->is_playing() || seeked) {
+ player->play(anim_name);
+ player->seek(at_anim_pos);
+ t->playing = true;
+ playing_caches.insert(t);
+ } else {
+ player->set_assigned_animation(anim_name);
+ player->seek(at_anim_pos, true);
+ }
+ } else {
+ //find stuff to play
+ List<int> to_play;
+ a->track_get_key_indices_in_range(i, time, delta, &to_play);
+ if (to_play.size()) {
+ int idx = to_play.back()->get();
+
+ StringName anim_name = a->animation_track_get_key_animation(i, idx);
+ if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) {
+
+ if (playing_caches.has(t)) {
+ playing_caches.erase(t);
+ player->stop();
+ t->playing = false;
+ }
+ } else {
+ player->play(anim_name);
+ t->playing = true;
+ playing_caches.insert(t);
+ }
+ }
+ }
+
+ } break;
+ }
+ }
+ }
+ }
+
+ {
+ // finally, set the tracks
+ const NodePath *K = NULL;
+ while ((K = track_cache.next(K))) {
+ TrackCache *track = track_cache[*K];
+ if (track->process_pass != process_pass)
+ continue; //not processed, ignore
+
+ switch (track->type) {
+
+ case Animation::TYPE_TRANSFORM: {
+
+ TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
+
+ Transform xform;
+ xform.origin = t->loc;
+
+ t->scale += Vector3(1.0, 1.0, 1.0); //helps make it work properly with Add nodes and root motion
+
+ xform.basis.set_quat_scale(t->rot, t->scale);
+
+ if (t->root_motion) {
+
+ root_motion_transform = xform;
+
+ if (t->skeleton && t->bone_idx >= 0) {
+ root_motion_transform = (t->skeleton->get_bone_rest(t->bone_idx) * root_motion_transform) * t->skeleton->get_bone_rest(t->bone_idx).affine_inverse();
+ }
+ } else if (t->skeleton && t->bone_idx >= 0) {
+
+ t->skeleton->set_bone_pose(t->bone_idx, xform);
+
+ } else {
+
+ t->spatial->set_transform(xform);
+ }
+
+ } break;
+ case Animation::TYPE_VALUE: {
+
+ TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
+
+ t->object->set_indexed(t->subpath, t->value);
+
+ } break;
+ case Animation::TYPE_BEZIER: {
+
+ TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
+
+ t->object->set_indexed(t->subpath, t->value);
+
+ } break;
+ default: {} //the rest dont matter
+ }
+ }
+ }
+}
+
+void AnimationTree::advance(float p_time) {
+
+ _process_graph(p_time);
+}
+
+void AnimationTree::_notification(int p_what) {
+
+ if (active && p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS && process_mode == ANIMATION_PROCESS_PHYSICS) {
+ _process_graph(get_physics_process_delta_time());
+ }
+
+ if (active && p_what == NOTIFICATION_INTERNAL_PROCESS && process_mode == ANIMATION_PROCESS_IDLE) {
+ _process_graph(get_process_delta_time());
+ }
+
+ if (p_what == NOTIFICATION_EXIT_TREE) {
+ _clear_caches();
+ if (last_animation_player) {
+
+ Object *old_player = ObjectDB::get_instance(last_animation_player);
+ if (old_player) {
+ old_player->disconnect("caches_cleared", this, "_clear_caches");
+ }
+ }
+ }
+}
+
+void AnimationTree::set_animation_player(const NodePath &p_player) {
+ animation_player = p_player;
+ update_configuration_warning();
+}
+
+NodePath AnimationTree::get_animation_player() const {
+ return animation_player;
+}
+
+bool AnimationTree::is_state_invalid() const {
+
+ return !state.valid;
+}
+String AnimationTree::get_invalid_state_reason() const {
+
+ return state.invalid_reasons;
+}
+
+uint64_t AnimationTree::get_last_process_pass() const {
+ return process_pass;
+}
+
+String AnimationTree::get_configuration_warning() const {
+
+ String warning = Node::get_configuration_warning();
+
+ if (!root.is_valid()) {
+ if (warning != String()) {
+ warning += "\n";
+ }
+ warning += TTR("A root AnimationNode for the graph is not set.");
+ }
+
+ if (!has_node(animation_player)) {
+
+ if (warning != String()) {
+ warning += "\n";
+ }
+
+ warning += TTR("Path to an AnimationPlayer node containing animations is not set.");
+ return warning;
+ }
+
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node(animation_player));
+
+ if (!player) {
+ if (warning != String()) {
+ warning += "\n";
+ }
+
+ warning += TTR("Path set for AnimationPlayer does not lead to an AnimationPlayer node.");
+ return warning;
+ }
+
+ if (!player->has_node(player->get_root())) {
+ if (warning != String()) {
+ warning += "\n";
+ }
+
+ warning += TTR("AnimationPlayer root is not a valid node.");
+ return warning;
+ }
+
+ return warning;
+}
+
+void AnimationTree::set_root_motion_track(const NodePath &p_track) {
+ root_motion_track = p_track;
+}
+
+NodePath AnimationTree::get_root_motion_track() const {
+ return root_motion_track;
+}
+
+Transform AnimationTree::get_root_motion_transform() const {
+ return root_motion_transform;
+}
+
+void AnimationTree::_tree_changed() {
+ if (properties_dirty) {
+ return;
+ }
+
+ call_deferred("_update_properties");
+ properties_dirty = true;
+}
+
+void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref<AnimationNode> node) {
+
+ if (!property_parent_map.has(p_base_path)) {
+ property_parent_map[p_base_path] = HashMap<StringName, StringName>();
+ }
+
+ if (node->get_input_count() && !input_activity_map.has(p_base_path)) {
+
+ Vector<Activity> activity;
+ for (int i = 0; i < node->get_input_count(); i++) {
+ Activity a;
+ 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;
+ node->get_parameter_list(&plist);
+ for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) {
+ PropertyInfo pinfo = E->get();
+
+ StringName key = pinfo.name;
+
+ if (!property_map.has(p_base_path + key)) {
+ property_map[p_base_path + key] = node->get_parameter_default_value(key);
+ }
+
+ 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;
+ node->get_child_nodes(&children);
+
+ for (List<AnimationNode::ChildNode>::Element *E = children.front(); E; E = E->next()) {
+ _update_properties_for_node(p_base_path + E->get().name + "/", E->get().node);
+ }
+}
+
+void AnimationTree::_update_properties() {
+ if (!properties_dirty) {
+ return;
+ }
+
+ properties.clear();
+ property_parent_map.clear();
+ input_activity_map.clear();
+ input_activity_map_get.clear();
+
+ if (root.is_valid()) {
+ _update_properties_for_node(SceneStringNames::get_singleton()->parameters_base_path, root);
+ }
+
+ properties_dirty = false;
+
+ _change_notify();
+}
+
+bool AnimationTree::_set(const StringName &p_name, const Variant &p_value) {
+ if (properties_dirty) {
+ _update_properties();
+ }
+
+ if (property_map.has(p_name)) {
+ property_map[p_name] = p_value;
+#ifdef TOOLS_ENABLED
+ _change_notify(p_name.operator String().utf8().get_data());
+#endif
+ return true;
+ }
+
+ return false;
+}
+
+bool AnimationTree::_get(const StringName &p_name, Variant &r_ret) const {
+ if (properties_dirty) {
+ const_cast<AnimationTree *>(this)->_update_properties();
+ }
+
+ if (property_map.has(p_name)) {
+ r_ret = property_map[p_name];
+ 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 List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
+ p_list->push_back(E->get());
+ }
+}
+
+void AnimationTree::rename_parameter(const String &p_base, const String &p_new_base) {
+
+ //rename values first
+ for (const List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) {
+ if (E->get().name.begins_with(p_base)) {
+ String new_name = E->get().name.replace_first(p_base, p_new_base);
+ property_map[new_name] = property_map[E->get().name];
+ }
+ }
+
+ //update tree second
+ properties_dirty = true;
+ _update_properties();
+}
+
+float 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_active", "active"), &AnimationTree::set_active);
+ ClassDB::bind_method(D_METHOD("is_active"), &AnimationTree::is_active);
+
+ ClassDB::bind_method(D_METHOD("set_tree_root", "root"), &AnimationTree::set_tree_root);
+ ClassDB::bind_method(D_METHOD("get_tree_root"), &AnimationTree::get_tree_root);
+
+ ClassDB::bind_method(D_METHOD("set_process_mode", "mode"), &AnimationTree::set_process_mode);
+ ClassDB::bind_method(D_METHOD("get_process_mode"), &AnimationTree::get_process_mode);
+
+ ClassDB::bind_method(D_METHOD("set_animation_player", "root"), &AnimationTree::set_animation_player);
+ ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationTree::get_animation_player);
+
+ ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track);
+ ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track);
+
+ ClassDB::bind_method(D_METHOD("get_root_motion_transform"), &AnimationTree::get_root_motion_transform);
+
+ ClassDB::bind_method(D_METHOD("_tree_changed"), &AnimationTree::_tree_changed);
+ ClassDB::bind_method(D_METHOD("_update_properties"), &AnimationTree::_update_properties);
+
+ ClassDB::bind_method(D_METHOD("rename_parameter", "old_name", "new_name"), &AnimationTree::rename_parameter);
+
+ ClassDB::bind_method(D_METHOD("advance", "delta"), &AnimationTree::advance);
+
+ ClassDB::bind_method(D_METHOD("_node_removed"), &AnimationTree::_node_removed);
+ ClassDB::bind_method(D_METHOD("_clear_caches"), &AnimationTree::_clear_caches);
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode"), "set_tree_root", "get_tree_root");
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_mode", "get_process_mode");
+ ADD_GROUP("Root Motion", "root_motion_");
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track");
+
+ BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS);
+ BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE);
+ BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL);
+}
+
+AnimationTree::AnimationTree() {
+
+ process_mode = ANIMATION_PROCESS_IDLE;
+ active = false;
+ cache_valid = false;
+ setup_pass = 1;
+ started = true;
+ properties_dirty = true;
+ last_animation_player = 0;
+}
+
+AnimationTree::~AnimationTree() {
+}
diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h
new file mode 100644
index 0000000000..6cb363d50a
--- /dev/null
+++ b/scene/animation/animation_tree.h
@@ -0,0 +1,314 @@
+#ifndef ANIMATION_GRAPH_PLAYER_H
+#define ANIMATION_GRAPH_PLAYER_H
+
+#include "animation_player.h"
+#include "scene/3d/skeleton.h"
+#include "scene/3d/spatial.h"
+#include "scene/resources/animation.h"
+
+class AnimationNodeBlendTree;
+class AnimationPlayer;
+class AnimationTree;
+
+class AnimationNode : public Resource {
+ GDCLASS(AnimationNode, Resource)
+public:
+ enum FilterAction {
+ FILTER_IGNORE,
+ FILTER_PASS,
+ FILTER_STOP,
+ FILTER_BLEND
+ };
+
+ struct Input {
+
+ String name;
+ };
+
+ Vector<Input> inputs;
+
+ float process_input(int p_input, float p_time, bool p_seek, float p_blend);
+
+ friend class AnimationTree;
+
+ struct AnimationState {
+
+ Ref<Animation> animation;
+ float time;
+ float delta;
+ const Vector<float> *track_blends;
+ float blend;
+ bool seeked;
+ };
+
+ struct State {
+
+ int track_count;
+ HashMap<NodePath, int> track_map;
+ List<AnimationState> animation_states;
+ bool valid;
+ AnimationPlayer *player;
+ AnimationTree *tree;
+ String invalid_reasons;
+ uint64_t last_pass;
+ };
+
+ Vector<float> blends;
+ State *state;
+
+ float _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, float p_time, bool p_seek, const Vector<StringName> &p_connections);
+ void _pre_update_animations(HashMap<NodePath, int> *track_map);
+
+ //all this is temporary
+ StringName base_path;
+ Vector<StringName> connections;
+ AnimationNode *parent;
+
+ HashMap<NodePath, bool> filter;
+ bool filter_enabled;
+
+ Array _get_filters() const;
+ void _set_filters(const Array &p_filters);
+ friend class AnimationNodeBlendTree;
+ float _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, float *r_max = NULL);
+
+protected:
+ void blend_animation(const StringName &p_animation, float p_time, float p_delta, bool p_seeked, float p_blend);
+ float blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true);
+ float blend_input(int p_input, float p_time, bool p_seek, float p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true);
+ void make_invalid(const String &p_reason);
+
+ static void _bind_methods();
+
+ void _validate_property(PropertyInfo &property) const;
+
+ void _set_parent(Object *p_parent);
+
+public:
+ virtual void get_parameter_list(List<PropertyInfo> *r_list) const;
+ virtual Variant get_parameter_default_value(const StringName &p_parameter) const;
+
+ void set_parameter(const StringName &p_name, const Variant &p_value);
+ Variant get_parameter(const StringName &p_name) const;
+
+ struct ChildNode {
+ StringName name;
+ Ref<AnimationNode> node;
+ };
+
+ virtual void get_child_nodes(List<ChildNode> *r_child_nodes);
+
+ virtual float process(float p_time, bool p_seek);
+ virtual String get_caption() const;
+
+ int get_input_count() const;
+ String get_input_name(int p_input);
+
+ void add_input(const String &p_name);
+ void set_input_name(int p_input, const String &p_name);
+ void remove_input(int p_index);
+
+ 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;
+
+ virtual bool has_filter() const;
+
+ virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name);
+
+ AnimationNode();
+};
+
+VARIANT_ENUM_CAST(AnimationNode::FilterAction)
+
+//root node does not allow inputs
+class AnimationRootNode : public AnimationNode {
+ GDCLASS(AnimationRootNode, AnimationNode)
+public:
+ AnimationRootNode() {}
+};
+
+class AnimationTree : public Node {
+ GDCLASS(AnimationTree, Node)
+public:
+ enum AnimationProcessMode {
+ ANIMATION_PROCESS_PHYSICS,
+ ANIMATION_PROCESS_IDLE,
+ ANIMATION_PROCESS_MANUAL,
+ };
+
+private:
+ struct TrackCache {
+
+ bool root_motion;
+ uint64_t setup_pass;
+ uint64_t process_pass;
+ Animation::TrackType type;
+ Object *object;
+ ObjectID object_id;
+
+ TrackCache() {
+ root_motion = false;
+ setup_pass = 0;
+ process_pass = 0;
+ object = NULL;
+ object_id = 0;
+ }
+ virtual ~TrackCache() {}
+ };
+
+ struct TrackCacheTransform : public TrackCache {
+ Spatial *spatial;
+ Skeleton *skeleton;
+ int bone_idx;
+ Vector3 loc;
+ Quat rot;
+ float rot_blend_accum;
+ Vector3 scale;
+
+ TrackCacheTransform() {
+ type = Animation::TYPE_TRANSFORM;
+ spatial = NULL;
+ bone_idx = -1;
+ skeleton = NULL;
+ }
+ };
+
+ struct TrackCacheValue : public TrackCache {
+
+ Variant value;
+ Vector<StringName> subpath;
+ TrackCacheValue() { type = Animation::TYPE_VALUE; }
+ };
+
+ struct TrackCacheMethod : public TrackCache {
+
+ TrackCacheMethod() { type = Animation::TYPE_METHOD; }
+ };
+
+ struct TrackCacheBezier : public TrackCache {
+
+ float value;
+ Vector<StringName> subpath;
+ TrackCacheBezier() {
+ type = Animation::TYPE_BEZIER;
+ value = 0;
+ }
+ };
+
+ struct TrackCacheAudio : public TrackCache {
+
+ bool playing;
+ float start;
+ float len;
+
+ TrackCacheAudio() {
+ type = Animation::TYPE_AUDIO;
+ playing = false;
+ start = 0;
+ len = 0;
+ }
+ };
+
+ struct TrackCacheAnimation : public TrackCache {
+
+ bool playing;
+
+ TrackCacheAnimation() {
+ type = Animation::TYPE_ANIMATION;
+ playing = false;
+ }
+ };
+
+ HashMap<NodePath, TrackCache *> track_cache;
+ Set<TrackCache *> playing_caches;
+
+ Ref<AnimationNode> root;
+
+ AnimationProcessMode process_mode;
+ bool active;
+ NodePath animation_player;
+
+ AnimationNode::State state;
+ bool cache_valid;
+ void _node_removed(Node *p_node);
+ void _caches_cleared();
+
+ void _clear_caches();
+ bool _update_caches(AnimationPlayer *player);
+ void _process_graph(float p_delta);
+
+ uint64_t setup_pass;
+ uint64_t process_pass;
+
+ bool started;
+
+ NodePath root_motion_track;
+ Transform root_motion_transform;
+
+ friend class AnimationNode;
+ bool properties_dirty;
+ void _tree_changed();
+ void _update_properties();
+ List<PropertyInfo> properties;
+ HashMap<StringName, HashMap<StringName, StringName> > property_parent_map;
+ HashMap<StringName, Variant> property_map;
+
+ struct Activity {
+ uint64_t last_pass;
+ float activity;
+ };
+
+ HashMap<StringName, Vector<Activity> > input_activity_map;
+ HashMap<StringName, Vector<Activity> *> input_activity_map_get;
+
+ void _update_properties_for_node(const String &p_base_path, Ref<AnimationNode> node);
+
+ ObjectID last_animation_player;
+
+protected:
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void set_tree_root(const Ref<AnimationNode> &p_root);
+ Ref<AnimationNode> get_tree_root() const;
+
+ void set_active(bool p_active);
+ bool is_active() const;
+
+ void set_process_mode(AnimationProcessMode p_mode);
+ AnimationProcessMode get_process_mode() const;
+
+ void set_animation_player(const NodePath &p_player);
+ NodePath get_animation_player() const;
+
+ virtual String get_configuration_warning() const;
+
+ bool is_state_invalid() const;
+ String get_invalid_state_reason() const;
+
+ void set_root_motion_track(const NodePath &p_track);
+ NodePath get_root_motion_track() const;
+
+ Transform get_root_motion_transform() const;
+
+ float get_connection_activity(const StringName &p_path, int p_connection) const;
+ void advance(float p_time);
+
+ void rename_parameter(const String &p_base, const String &p_new_base);
+
+ uint64_t get_last_process_pass() const;
+ AnimationTree();
+ ~AnimationTree();
+};
+
+VARIANT_ENUM_CAST(AnimationTree::AnimationProcessMode)
+
+#endif // ANIMATION_GRAPH_PLAYER_H
diff --git a/scene/animation/animation_tree_player.cpp b/scene/animation/animation_tree_player.cpp
index 89f0e43a86..179f5d9698 100644
--- a/scene/animation/animation_tree_player.cpp
+++ b/scene/animation/animation_tree_player.cpp
@@ -813,7 +813,11 @@ void AnimationTreePlayer::_process_animation(float p_delta) {
t.value = t.object->get_indexed(t.subpath);
t.value.zero();
- t.skip = false;
+ if (t.skeleton) {
+ t.skip = t.skeleton->is_bone_ignore_animation(t.bone_idx);
+ } else {
+ t.skip = false;
+ }
}
/* STEP 2 PROCESS ANIMATIONS */
@@ -895,13 +899,12 @@ void AnimationTreePlayer::_process_animation(float p_delta) {
}
Transform xform;
- xform.basis = t.rot;
xform.origin = t.loc;
t.scale.x += 1.0;
t.scale.y += 1.0;
t.scale.z += 1.0;
- xform.basis.scale(t.scale);
+ xform.basis.set_quat_scale(t.rot, t.scale);
if (t.bone_idx >= 0) {
if (t.skeleton)
@@ -1149,7 +1152,7 @@ void AnimationTreePlayer::transition_node_set_input_auto_advance(const StringNam
GET_NODE(NODE_TRANSITION, TransitionNode);
ERR_FAIL_INDEX(p_input, n->input_data.size());
- n->input_data[p_input].auto_advance = p_auto_advance;
+ n->input_data.write[p_input].auto_advance = p_auto_advance;
}
void AnimationTreePlayer::transition_node_set_xfade_time(const StringName &p_node, float p_time) {
@@ -1213,6 +1216,12 @@ String AnimationTreePlayer::animation_node_get_master_animation(const StringName
return n->from;
}
+float AnimationTreePlayer::animation_node_get_position(const StringName &p_node) const {
+
+ GET_NODE_V(NODE_ANIMATION, AnimationNode, 0);
+ return n->time;
+}
+
bool AnimationTreePlayer::animation_node_is_path_filtered(const StringName &p_node, const NodePath &p_path) const {
GET_NODE_V(NODE_ANIMATION, AnimationNode, 0);
@@ -1356,7 +1365,7 @@ void AnimationTreePlayer::remove_node(const StringName &p_node) {
for (int i = 0; i < nb->inputs.size(); i++) {
if (nb->inputs[i].node == p_node)
- nb->inputs[i].node = StringName();
+ nb->inputs.write[i].node = StringName();
}
}
@@ -1417,11 +1426,11 @@ Error AnimationTreePlayer::connect_nodes(const StringName &p_src_node, const Str
for (int i = 0; i < nb->inputs.size(); i++) {
if (nb->inputs[i].node == p_src_node)
- nb->inputs[i].node = StringName();
+ nb->inputs.write[i].node = StringName();
}
}
- dst->inputs[p_dst_input].node = p_src_node;
+ dst->inputs.write[p_dst_input].node = p_src_node;
_clear_cycle_test();
@@ -1454,7 +1463,7 @@ void AnimationTreePlayer::disconnect_nodes(const StringName &p_node, int p_input
NodeBase *dst = node_map[p_node];
ERR_FAIL_INDEX(p_input, dst->inputs.size());
- dst->inputs[p_input].node = StringName();
+ dst->inputs.write[p_input].node = StringName();
last_error = CONNECT_INCOMPLETE;
dirty_caches = true;
}
@@ -1694,7 +1703,7 @@ Error AnimationTreePlayer::node_rename(const StringName &p_node, const StringNam
for (int i = 0; i < nb->inputs.size(); i++) {
if (nb->inputs[i].node == p_node) {
- nb->inputs[i].node = p_new_name;
+ nb->inputs.write[i].node = p_new_name;
}
}
}
@@ -1721,6 +1730,7 @@ void AnimationTreePlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("animation_node_set_master_animation", "id", "source"), &AnimationTreePlayer::animation_node_set_master_animation);
ClassDB::bind_method(D_METHOD("animation_node_get_master_animation", "id"), &AnimationTreePlayer::animation_node_get_master_animation);
+ ClassDB::bind_method(D_METHOD("animation_node_get_position", "id"), &AnimationTreePlayer::animation_node_get_position);
ClassDB::bind_method(D_METHOD("animation_node_set_filter_path", "id", "path", "enable"), &AnimationTreePlayer::animation_node_set_filter_path);
ClassDB::bind_method(D_METHOD("oneshot_node_set_fadein_time", "id", "time_sec"), &AnimationTreePlayer::oneshot_node_set_fadein_time);
@@ -1804,7 +1814,7 @@ void AnimationTreePlayer::_bind_methods() {
ADD_GROUP("Playback", "playback_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_animation_process_mode", "get_animation_process_mode");
- ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "master_player"), "set_master_player", "get_master_player");
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "master_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_master_player", "get_master_player");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "base_path"), "set_base_path", "get_base_path");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active");
diff --git a/scene/animation/animation_tree_player.h b/scene/animation/animation_tree_player.h
index 09d6f6fcb4..d2d7b1c9ec 100644
--- a/scene/animation/animation_tree_player.h
+++ b/scene/animation/animation_tree_player.h
@@ -348,6 +348,7 @@ public:
Ref<Animation> animation_node_get_animation(const StringName &p_node) const;
void animation_node_set_master_animation(const StringName &p_node, const String &p_master_animation);
String animation_node_get_master_animation(const StringName &p_node) const;
+ float animation_node_get_position(const StringName &p_node) const;
void animation_node_set_filter_path(const StringName &p_node, const NodePath &p_track_path, bool p_filter);
void animation_node_set_get_filtered_paths(const StringName &p_node, List<NodePath> *r_paths) const;
diff --git a/scene/animation/root_motion_view.cpp b/scene/animation/root_motion_view.cpp
new file mode 100644
index 0000000000..a28c63064a
--- /dev/null
+++ b/scene/animation/root_motion_view.cpp
@@ -0,0 +1,178 @@
+#include "root_motion_view.h"
+#include "scene/animation/animation_tree.h"
+#include "scene/resources/material.h"
+void RootMotionView::set_animation_path(const NodePath &p_path) {
+ path = p_path;
+ first = true;
+}
+
+NodePath RootMotionView::get_animation_path() 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) {
+
+ if (p_what == NOTIFICATION_ENTER_TREE) {
+
+ VS::get_singleton()->immediate_set_material(immediate, SpatialMaterial::get_material_rid_for_2d(false, true, false, false, false));
+ first = true;
+ }
+
+ if (p_what == NOTIFICATION_INTERNAL_PROCESS || p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
+ Transform transform;
+
+ if (has_node(path)) {
+
+ Node *node = get_node(path);
+
+ AnimationTree *tree = Object::cast_to<AnimationTree>(node);
+ if (tree && tree->is_active() && tree->get_root_motion_track() != NodePath()) {
+ if (is_processing_internal() && tree->get_process_mode() == AnimationTree::ANIMATION_PROCESS_PHYSICS) {
+ set_process_internal(false);
+ set_physics_process_internal(true);
+ }
+
+ if (is_physics_processing_internal() && tree->get_process_mode() == AnimationTree::ANIMATION_PROCESS_IDLE) {
+ set_process_internal(true);
+ set_physics_process_internal(false);
+ }
+
+ transform = tree->get_root_motion_transform();
+ }
+ }
+
+ if (!first && transform == Transform()) {
+ return;
+ }
+
+ first = false;
+
+ transform.orthonormalize(); //dont want scale, too imprecise
+ transform.affine_invert();
+
+ accumulated = transform * accumulated;
+ 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);
+
+ VS::get_singleton()->immediate_clear(immediate);
+
+ int cells_in_radius = int((radius / cell_size) + 1.0);
+
+ VS::get_singleton()->immediate_begin(immediate, VS::PRIMITIVE_LINES);
+ 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(from);
+ from_i = accumulated.xform(from_i);
+ from_j = accumulated.xform(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);
+
+ VS::get_singleton()->immediate_color(immediate, c);
+ VS::get_singleton()->immediate_vertex(immediate, from);
+
+ VS::get_singleton()->immediate_color(immediate, c_i);
+ VS::get_singleton()->immediate_vertex(immediate, from_i);
+
+ VS::get_singleton()->immediate_color(immediate, c);
+ VS::get_singleton()->immediate_vertex(immediate, from);
+
+ VS::get_singleton()->immediate_color(immediate, c_j);
+ VS::get_singleton()->immediate_vertex(immediate, from_j);
+ }
+ }
+
+ VS::get_singleton()->immediate_end(immediate);
+ }
+}
+
+AABB RootMotionView::get_aabb() const {
+
+ return AABB(Vector3(-radius, 0, -radius), Vector3(radius * 2, 0.001, radius * 2));
+}
+PoolVector<Face3> RootMotionView::get_faces(uint32_t p_usage_flags) const {
+ return PoolVector<Face3>();
+}
+
+void RootMotionView::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_animation_path", "path"), &RootMotionView::set_animation_path);
+ ClassDB::bind_method(D_METHOD("get_animation_path"), &RootMotionView::get_animation_path);
+
+ 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, "AnimationTree"), "set_animation_path", "get_animation_path");
+ ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "cell_size", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater"), "set_cell_size", "get_cell_size");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater"), "set_radius", "get_radius");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "zero_y"), "set_zero_y", "get_zero_y");
+}
+
+RootMotionView::RootMotionView() {
+ zero_y = true;
+ radius = 10;
+ cell_size = 1;
+ set_process_internal(true);
+ immediate = VisualServer::get_singleton()->immediate_create();
+ set_base(immediate);
+ color = Color(0.5, 0.5, 1.0);
+}
+
+RootMotionView::~RootMotionView() {
+ set_base(RID());
+ VisualServer::get_singleton()->free(immediate);
+}
diff --git a/scene/animation/root_motion_view.h b/scene/animation/root_motion_view.h
new file mode 100644
index 0000000000..611183d364
--- /dev/null
+++ b/scene/animation/root_motion_view.h
@@ -0,0 +1,47 @@
+#ifndef ROOT_MOTION_VIEW_H
+#define ROOT_MOTION_VIEW_H
+
+#include "scene/3d/visual_instance.h"
+
+class RootMotionView : public VisualInstance {
+ GDCLASS(RootMotionView, VisualInstance)
+public:
+ RID immediate;
+ NodePath path;
+ float cell_size;
+ float radius;
+ bool use_in_game;
+ Color color;
+ bool first;
+ bool zero_y;
+
+ Transform accumulated;
+
+private:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void set_animation_path(const NodePath &p_path);
+ NodePath get_animation_path() const;
+
+ void set_color(const Color &p_path);
+ 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;
+ virtual PoolVector<Face3> get_faces(uint32_t p_usage_flags) const;
+
+ RootMotionView();
+ ~RootMotionView();
+};
+
+#endif // ROOT_MOTION_VIEW_H
diff --git a/scene/animation/skeleton_ik.cpp b/scene/animation/skeleton_ik.cpp
new file mode 100644
index 0000000000..9b1cb1369a
--- /dev/null
+++ b/scene/animation/skeleton_ik.cpp
@@ -0,0 +1,555 @@
+/*************************************************************************/
+/* skeleton_ik.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+/**
+ * @author AndreaCatania
+ */
+
+#include "skeleton_ik.h"
+
+#ifndef _3D_DISABLED
+
+FabrikInverseKinematic::ChainItem *FabrikInverseKinematic::ChainItem::find_child(const BoneId p_bone_id) {
+ for (int i = childs.size() - 1; 0 <= i; --i) {
+ if (p_bone_id == childs[i].bone) {
+ return &childs.write[i];
+ }
+ }
+ return NULL;
+}
+
+FabrikInverseKinematic::ChainItem *FabrikInverseKinematic::ChainItem::add_child(const BoneId p_bone_id) {
+ const int infant_child_id = childs.size();
+ childs.resize(infant_child_id + 1);
+ childs.write[infant_child_id].bone = p_bone_id;
+ childs.write[infant_child_id].parent_item = this;
+ return &childs.write[infant_child_id];
+}
+
+/// Build a chain that starts from the root to tip
+void FabrikInverseKinematic::build_chain(Task *p_task, bool p_force_simple_chain) {
+
+ ERR_FAIL_COND(-1 == p_task->root_bone);
+
+ Chain &chain(p_task->chain);
+
+ chain.tips.resize(p_task->end_effectors.size());
+ chain.chain_root.bone = p_task->root_bone;
+ chain.chain_root.initial_transform = p_task->skeleton->get_bone_global_pose(chain.chain_root.bone);
+ chain.chain_root.current_pos = chain.chain_root.initial_transform.origin;
+ chain.chain_root.pb = p_task->skeleton->get_physical_bone(chain.chain_root.bone);
+ chain.middle_chain_item = NULL;
+
+ // Holds all IDs that are composing a single chain in reverse order
+ Vector<BoneId> chain_ids;
+ // This is used to know the chain size
+ int sub_chain_size;
+ // Resize only one time in order to fit all joints for performance reason
+ chain_ids.resize(p_task->skeleton->get_bone_count());
+
+ for (int x = p_task->end_effectors.size() - 1; 0 <= x; --x) {
+
+ const EndEffector *ee(&p_task->end_effectors[x]);
+ ERR_FAIL_COND(p_task->root_bone >= ee->tip_bone);
+ ERR_FAIL_INDEX(ee->tip_bone, p_task->skeleton->get_bone_count());
+
+ sub_chain_size = 0;
+ // Picks all IDs that composing a single chain in reverse order (except the root)
+ BoneId chain_sub_tip(ee->tip_bone);
+ while (chain_sub_tip > p_task->root_bone) {
+
+ chain_ids.write[sub_chain_size++] = chain_sub_tip;
+ chain_sub_tip = p_task->skeleton->get_bone_parent(chain_sub_tip);
+ }
+
+ BoneId middle_chain_item_id = (((float)sub_chain_size) * 0.5);
+
+ // Build chain by reading chain ids in reverse order
+ // For each chain item id will be created a ChainItem if doesn't exists
+ ChainItem *sub_chain(&chain.chain_root);
+ for (int i = sub_chain_size - 1; 0 <= i; --i) {
+
+ ChainItem *child_ci(sub_chain->find_child(chain_ids[i]));
+ if (!child_ci) {
+
+ child_ci = sub_chain->add_child(chain_ids[i]);
+
+ child_ci->pb = p_task->skeleton->get_physical_bone(child_ci->bone);
+
+ child_ci->initial_transform = p_task->skeleton->get_bone_global_pose(child_ci->bone);
+ child_ci->current_pos = child_ci->initial_transform.origin;
+
+ if (child_ci->parent_item) {
+ child_ci->length = (child_ci->current_pos - child_ci->parent_item->current_pos).length();
+ }
+ }
+
+ sub_chain = child_ci;
+
+ if (middle_chain_item_id == i) {
+ chain.middle_chain_item = child_ci;
+ }
+ }
+
+ if (!middle_chain_item_id)
+ chain.middle_chain_item = NULL;
+
+ // Initialize current tip
+ chain.tips.write[x].chain_item = sub_chain;
+ chain.tips.write[x].end_effector = ee;
+
+ if (p_force_simple_chain) {
+ // NOTE:
+ // This is an "hack" that force to create only one tip per chain since the solver of multi tip (end effector)
+ // is not yet created.
+ // Remove this code when this is done
+ break;
+ }
+ }
+}
+
+void FabrikInverseKinematic::update_chain(const Skeleton *p_sk, ChainItem *p_chain_item) {
+
+ if (!p_chain_item)
+ return;
+
+ p_chain_item->initial_transform = p_sk->get_bone_global_pose(p_chain_item->bone);
+ p_chain_item->current_pos = p_chain_item->initial_transform.origin;
+
+ for (int i = p_chain_item->childs.size() - 1; 0 <= i; --i) {
+ update_chain(p_sk, &p_chain_item->childs.write[i]);
+ }
+}
+
+void FabrikInverseKinematic::solve_simple(Task *p_task, bool p_solve_magnet) {
+
+ real_t distance_to_goal(1e4);
+ real_t previous_distance_to_goal(0);
+ int can_solve(p_task->max_iterations);
+ while (distance_to_goal > p_task->min_distance && Math::abs(previous_distance_to_goal - distance_to_goal) > 0.005 && can_solve) {
+ previous_distance_to_goal = distance_to_goal;
+ --can_solve;
+
+ solve_simple_backwards(p_task->chain, p_solve_magnet);
+ solve_simple_forwards(p_task->chain, p_solve_magnet);
+
+ distance_to_goal = (p_task->chain.tips[0].chain_item->current_pos - p_task->chain.tips[0].end_effector->goal_transform.origin).length();
+ }
+}
+
+void FabrikInverseKinematic::solve_simple_backwards(Chain &r_chain, bool p_solve_magnet) {
+
+ if (p_solve_magnet && !r_chain.middle_chain_item) {
+ return;
+ }
+
+ Vector3 goal;
+ ChainItem *sub_chain_tip;
+ if (p_solve_magnet) {
+ goal = r_chain.magnet_position;
+ sub_chain_tip = r_chain.middle_chain_item;
+ } else {
+ goal = r_chain.tips[0].end_effector->goal_transform.origin;
+ sub_chain_tip = r_chain.tips[0].chain_item;
+ }
+
+ while (sub_chain_tip) {
+ sub_chain_tip->current_pos = goal;
+
+ if (sub_chain_tip->parent_item) {
+ // Not yet in the chain root
+ // So calculate next goal location
+
+ const Vector3 look_parent((sub_chain_tip->parent_item->current_pos - sub_chain_tip->current_pos).normalized());
+ goal = sub_chain_tip->current_pos + (look_parent * sub_chain_tip->length);
+
+ // [TODO] Constraints goes here
+ }
+
+ sub_chain_tip = sub_chain_tip->parent_item;
+ }
+}
+
+void FabrikInverseKinematic::solve_simple_forwards(Chain &r_chain, bool p_solve_magnet) {
+
+ if (p_solve_magnet && !r_chain.middle_chain_item) {
+ return;
+ }
+
+ ChainItem *sub_chain_root(&r_chain.chain_root);
+ Vector3 origin(r_chain.chain_root.initial_transform.origin);
+
+ while (sub_chain_root) { // Reach the tip
+ sub_chain_root->current_pos = origin;
+
+ if (!sub_chain_root->childs.empty()) {
+
+ ChainItem &child(sub_chain_root->childs.write[0]);
+
+ // Is not tip
+ // So calculate next origin location
+
+ // Look child
+ sub_chain_root->current_ori = (child.current_pos - sub_chain_root->current_pos).normalized();
+ origin = sub_chain_root->current_pos + (sub_chain_root->current_ori * child.length);
+
+ // [TODO] Constraints goes here
+
+ if (p_solve_magnet && sub_chain_root == r_chain.middle_chain_item) {
+ // In case of magnet solving this is the tip
+ sub_chain_root = NULL;
+ } else {
+ sub_chain_root = &child;
+ }
+ } else {
+
+ // Is tip
+ sub_chain_root = NULL;
+ }
+ }
+}
+
+FabrikInverseKinematic::Task *FabrikInverseKinematic::create_simple_task(Skeleton *p_sk, BoneId root_bone, BoneId tip_bone, const Transform &goal_transform) {
+
+ FabrikInverseKinematic::EndEffector ee;
+ ee.tip_bone = tip_bone;
+
+ Task *task(memnew(Task));
+ task->skeleton = p_sk;
+ task->root_bone = root_bone;
+ task->end_effectors.push_back(ee);
+ task->goal_global_transform = goal_transform;
+
+ build_chain(task);
+
+ return task;
+}
+
+void FabrikInverseKinematic::free_task(Task *p_task) {
+ if (p_task)
+ memdelete(p_task);
+}
+
+void FabrikInverseKinematic::set_goal(Task *p_task, const Transform &p_goal) {
+ p_task->goal_global_transform = p_goal;
+}
+
+void FabrikInverseKinematic::make_goal(Task *p_task, const Transform &p_inverse_transf, real_t blending_delta) {
+
+ if (blending_delta >= 0.99f) {
+ // Update the end_effector (local transform) without blending
+ p_task->end_effectors.write[0].goal_transform = p_inverse_transf * p_task->goal_global_transform;
+ } else {
+
+ // End effector in local transform
+ const Transform end_effector_pose(p_task->skeleton->get_bone_global_pose(p_task->end_effectors.write[0].tip_bone));
+
+ // Update the end_effector (local transform) by blending with current pose
+ p_task->end_effectors.write[0].goal_transform = end_effector_pose.interpolate_with(p_inverse_transf * p_task->goal_global_transform, blending_delta);
+ }
+}
+
+void FabrikInverseKinematic::solve(Task *p_task, real_t blending_delta, bool p_use_magnet, const Vector3 &p_magnet_position) {
+
+ if (blending_delta <= 0.01f) {
+ return; // Skip solving
+ }
+
+ make_goal(p_task, p_task->skeleton->get_global_transform().affine_inverse().scaled(p_task->skeleton->get_global_transform().get_basis().get_scale()), blending_delta);
+
+ update_chain(p_task->skeleton, &p_task->chain.chain_root);
+
+ if (p_use_magnet && p_task->chain.middle_chain_item) {
+ p_task->chain.magnet_position = p_task->chain.middle_chain_item->initial_transform.origin.linear_interpolate(p_magnet_position, blending_delta);
+ solve_simple(p_task, true);
+ }
+ solve_simple(p_task, false);
+
+ // Assign new bone position.
+ ChainItem *ci(&p_task->chain.chain_root);
+ while (ci) {
+ Transform new_bone_pose(ci->initial_transform);
+ new_bone_pose.origin = ci->current_pos;
+
+ if (!ci->childs.empty()) {
+
+ /// Rotate basis
+ const Vector3 initial_ori((ci->childs[0].initial_transform.origin - ci->initial_transform.origin).normalized());
+ const Vector3 rot_axis(initial_ori.cross(ci->current_ori).normalized());
+
+ if (rot_axis[0] != 0 && rot_axis[1] != 0 && rot_axis[2] != 0) {
+ const real_t rot_angle(Math::acos(CLAMP(initial_ori.dot(ci->current_ori), -1, 1)));
+ new_bone_pose.basis.rotate(rot_axis, rot_angle);
+ }
+ } else {
+ // Set target orientation to tip
+ new_bone_pose.basis = p_task->chain.tips[0].end_effector->goal_transform.basis;
+ }
+
+ p_task->skeleton->set_bone_global_pose(ci->bone, new_bone_pose);
+
+ if (!ci->childs.empty())
+ ci = &ci->childs.write[0];
+ else
+ ci = NULL;
+ }
+}
+
+void SkeletonIK::_validate_property(PropertyInfo &property) const {
+
+ if (property.name == "root_bone" || property.name == "tip_bone") {
+
+ if (skeleton) {
+
+ String names;
+ for (int i = 0; i < skeleton->get_bone_count(); i++) {
+ if (i > 0)
+ names += ",";
+ names += skeleton->get_bone_name(i);
+ }
+
+ property.hint = PROPERTY_HINT_ENUM;
+ property.hint_string = names;
+ } else {
+
+ property.hint = PROPERTY_HINT_NONE;
+ property.hint_string = "";
+ }
+ }
+}
+
+void SkeletonIK::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_root_bone", "root_bone"), &SkeletonIK::set_root_bone);
+ ClassDB::bind_method(D_METHOD("get_root_bone"), &SkeletonIK::get_root_bone);
+
+ ClassDB::bind_method(D_METHOD("set_tip_bone", "tip_bone"), &SkeletonIK::set_tip_bone);
+ ClassDB::bind_method(D_METHOD("get_tip_bone"), &SkeletonIK::get_tip_bone);
+
+ ClassDB::bind_method(D_METHOD("set_interpolation", "interpolation"), &SkeletonIK::set_interpolation);
+ ClassDB::bind_method(D_METHOD("get_interpolation"), &SkeletonIK::get_interpolation);
+
+ ClassDB::bind_method(D_METHOD("set_target_transform", "target"), &SkeletonIK::set_target_transform);
+ ClassDB::bind_method(D_METHOD("get_target_transform"), &SkeletonIK::get_target_transform);
+
+ ClassDB::bind_method(D_METHOD("set_target_node", "node"), &SkeletonIK::set_target_node);
+ ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonIK::get_target_node);
+
+ ClassDB::bind_method(D_METHOD("set_use_magnet", "use"), &SkeletonIK::set_use_magnet);
+ ClassDB::bind_method(D_METHOD("is_using_magnet"), &SkeletonIK::is_using_magnet);
+
+ ClassDB::bind_method(D_METHOD("set_magnet_position", "local_position"), &SkeletonIK::set_magnet_position);
+ ClassDB::bind_method(D_METHOD("get_magnet_position"), &SkeletonIK::get_magnet_position);
+
+ ClassDB::bind_method(D_METHOD("get_parent_skeleton"), &SkeletonIK::get_parent_skeleton);
+ ClassDB::bind_method(D_METHOD("is_running"), &SkeletonIK::is_running);
+
+ ClassDB::bind_method(D_METHOD("set_min_distance", "min_distance"), &SkeletonIK::set_min_distance);
+ ClassDB::bind_method(D_METHOD("get_min_distance"), &SkeletonIK::get_min_distance);
+
+ ClassDB::bind_method(D_METHOD("set_max_iterations", "iterations"), &SkeletonIK::set_max_iterations);
+ ClassDB::bind_method(D_METHOD("get_max_iterations"), &SkeletonIK::get_max_iterations);
+
+ ClassDB::bind_method(D_METHOD("start", "one_time"), &SkeletonIK::start, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("stop"), &SkeletonIK::stop);
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "root_bone"), "set_root_bone", "get_root_bone");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "tip_bone"), "set_tip_bone", "get_tip_bone");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "interpolation", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_interpolation", "get_interpolation");
+ ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "target"), "set_target_transform", "get_target_transform");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_magnet"), "set_use_magnet", "is_using_magnet");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "magnet"), "set_magnet_position", "get_magnet_position");
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node"), "set_target_node", "get_target_node");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "min_distance"), "set_min_distance", "get_min_distance");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "max_iterations"), "set_max_iterations", "get_max_iterations");
+}
+
+void SkeletonIK::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ skeleton = Object::cast_to<Skeleton>(get_parent());
+ reload_chain();
+ } break;
+ case NOTIFICATION_INTERNAL_PROCESS: {
+
+ if (target_node_override)
+ reload_goal();
+
+ _solve_chain();
+
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ reload_chain();
+ } break;
+ }
+}
+
+SkeletonIK::SkeletonIK() :
+ Node(),
+ interpolation(1),
+ skeleton(NULL),
+ target_node_override(NULL),
+ use_magnet(false),
+ min_distance(0.01),
+ max_iterations(10),
+ task(NULL) {
+
+ set_process_priority(1);
+}
+
+SkeletonIK::~SkeletonIK() {
+ FabrikInverseKinematic::free_task(task);
+ task = NULL;
+}
+
+void SkeletonIK::set_root_bone(const StringName &p_root_bone) {
+ root_bone = p_root_bone;
+ reload_chain();
+}
+
+StringName SkeletonIK::get_root_bone() const {
+ return root_bone;
+}
+
+void SkeletonIK::set_tip_bone(const StringName &p_tip_bone) {
+ tip_bone = p_tip_bone;
+ reload_chain();
+}
+
+StringName SkeletonIK::get_tip_bone() const {
+ return tip_bone;
+}
+
+void SkeletonIK::set_interpolation(real_t p_interpolation) {
+ interpolation = p_interpolation;
+}
+
+real_t SkeletonIK::get_interpolation() const {
+ return interpolation;
+}
+
+void SkeletonIK::set_target_transform(const Transform &p_target) {
+ target = p_target;
+ reload_goal();
+}
+
+const Transform &SkeletonIK::get_target_transform() const {
+ return target;
+}
+
+void SkeletonIK::set_target_node(const NodePath &p_node) {
+ target_node_path_override = p_node;
+ target_node_override = NULL;
+ reload_goal();
+}
+
+NodePath SkeletonIK::get_target_node() {
+ return target_node_path_override;
+}
+
+void SkeletonIK::set_use_magnet(bool p_use) {
+ use_magnet = p_use;
+}
+
+bool SkeletonIK::is_using_magnet() const {
+ return use_magnet;
+}
+
+void SkeletonIK::set_magnet_position(const Vector3 &p_local_position) {
+ magnet_position = p_local_position;
+}
+
+const Vector3 &SkeletonIK::get_magnet_position() const {
+ return magnet_position;
+}
+
+void SkeletonIK::set_min_distance(real_t p_min_distance) {
+ min_distance = p_min_distance;
+}
+
+void SkeletonIK::set_max_iterations(int p_iterations) {
+ max_iterations = p_iterations;
+}
+
+bool SkeletonIK::is_running() {
+ return is_processing_internal();
+}
+
+void SkeletonIK::start(bool p_one_time) {
+ if (p_one_time) {
+ set_process_internal(false);
+ _solve_chain();
+ } else {
+ set_process_internal(true);
+ }
+}
+
+void SkeletonIK::stop() {
+ set_process_internal(false);
+}
+
+Transform SkeletonIK::_get_target_transform() {
+
+ if (!target_node_override && !target_node_path_override.is_empty())
+ target_node_override = Object::cast_to<Spatial>(get_node(target_node_path_override));
+
+ if (target_node_override)
+ return target_node_override->get_global_transform();
+ else
+ return target;
+}
+
+void SkeletonIK::reload_chain() {
+
+ FabrikInverseKinematic::free_task(task);
+ task = NULL;
+
+ if (!skeleton)
+ return;
+
+ task = FabrikInverseKinematic::create_simple_task(skeleton, skeleton->find_bone(root_bone), skeleton->find_bone(tip_bone), _get_target_transform());
+ task->max_iterations = max_iterations;
+ task->min_distance = min_distance;
+}
+
+void SkeletonIK::reload_goal() {
+ if (!task)
+ return;
+
+ FabrikInverseKinematic::set_goal(task, _get_target_transform());
+}
+
+void SkeletonIK::_solve_chain() {
+ if (!task)
+ return;
+ FabrikInverseKinematic::solve(task, interpolation, use_magnet, magnet_position);
+}
+
+#endif // _3D_DISABLED
diff --git a/scene/animation/skeleton_ik.h b/scene/animation/skeleton_ik.h
new file mode 100644
index 0000000000..08fb00e798
--- /dev/null
+++ b/scene/animation/skeleton_ik.h
@@ -0,0 +1,216 @@
+/*************************************************************************/
+/* skeleton_ik.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef SKELETON_IK_H
+#define SKELETON_IK_H
+
+#ifndef _3D_DISABLED
+
+/**
+ * @author AndreaCatania
+ */
+
+#include "core/math/transform.h"
+#include "scene/3d/skeleton.h"
+
+class FabrikInverseKinematic {
+
+ struct EndEffector {
+ BoneId tip_bone;
+ Transform goal_transform;
+ };
+
+ struct ChainItem {
+
+ Vector<ChainItem> childs;
+ ChainItem *parent_item;
+
+ // Bone info
+ BoneId bone;
+ PhysicalBone *pb;
+
+ real_t length;
+ /// Positions relative to root bone
+ Transform initial_transform;
+ Vector3 current_pos;
+ // Direction from this bone to child
+ Vector3 current_ori;
+
+ ChainItem() :
+ parent_item(NULL),
+ bone(-1),
+ pb(NULL),
+ length(0) {}
+
+ ChainItem *find_child(const BoneId p_bone_id);
+ ChainItem *add_child(const BoneId p_bone_id);
+ };
+
+ struct ChainTip {
+ ChainItem *chain_item;
+ const EndEffector *end_effector;
+
+ ChainTip() :
+ chain_item(NULL),
+ end_effector(NULL) {}
+
+ ChainTip(ChainItem *p_chain_item, const EndEffector *p_end_effector) :
+ chain_item(p_chain_item),
+ end_effector(p_end_effector) {}
+
+ ChainTip(const ChainTip &p_other_ct) :
+ chain_item(p_other_ct.chain_item),
+ end_effector(p_other_ct.end_effector) {}
+ };
+
+ struct Chain {
+ ChainItem chain_root;
+ ChainItem *middle_chain_item;
+ Vector<ChainTip> tips;
+ Vector3 magnet_position;
+ };
+
+public:
+ struct Task : public RID_Data {
+ RID self;
+ Skeleton *skeleton;
+
+ Chain chain;
+
+ // Settings
+ real_t min_distance;
+ int max_iterations;
+
+ // Bone data
+ BoneId root_bone;
+ Vector<EndEffector> end_effectors;
+
+ Transform goal_global_transform;
+
+ Task() :
+ skeleton(NULL),
+ min_distance(0.01),
+ max_iterations(10),
+ root_bone(-1) {}
+ };
+
+private:
+ /// Init a chain that starts from the root to tip
+ static void build_chain(Task *p_task, bool p_force_simple_chain = true);
+
+ static void update_chain(const Skeleton *p_sk, ChainItem *p_chain_item);
+
+ static void solve_simple(Task *p_task, bool p_solve_magnet);
+ /// Special solvers that solve only chains with one end effector
+ static void solve_simple_backwards(Chain &r_chain, bool p_solve_magnet);
+ static void solve_simple_forwards(Chain &r_chain, bool p_solve_magnet);
+
+public:
+ static Task *create_simple_task(Skeleton *p_sk, BoneId root_bone, BoneId tip_bone, const Transform &goal_transform);
+ static void free_task(Task *p_task);
+ // The goal of chain should be always in local space
+ static void set_goal(Task *p_task, const Transform &p_goal);
+ static void make_goal(Task *p_task, const Transform &p_inverse_transf, real_t blending_delta);
+ static void solve(Task *p_task, real_t blending_delta, bool p_use_magnet, const Vector3 &p_magnet_position);
+};
+
+class SkeletonIK : public Node {
+ GDCLASS(SkeletonIK, Node);
+
+ StringName root_bone;
+ StringName tip_bone;
+ real_t interpolation;
+ Transform target;
+ NodePath target_node_path_override;
+ bool use_magnet;
+ Vector3 magnet_position;
+
+ real_t min_distance;
+ int max_iterations;
+
+ Skeleton *skeleton;
+ Spatial *target_node_override;
+ FabrikInverseKinematic::Task *task;
+
+protected:
+ virtual void
+ _validate_property(PropertyInfo &property) const;
+
+ static void _bind_methods();
+ virtual void _notification(int p_notification);
+
+public:
+ SkeletonIK();
+ virtual ~SkeletonIK();
+
+ void set_root_bone(const StringName &p_root_bone);
+ StringName get_root_bone() const;
+
+ void set_tip_bone(const StringName &p_tip_bone);
+ StringName get_tip_bone() const;
+
+ void set_interpolation(real_t p_interpolation);
+ real_t get_interpolation() const;
+
+ void set_target_transform(const Transform &p_target);
+ const Transform &get_target_transform() const;
+
+ void set_target_node(const NodePath &p_node);
+ NodePath get_target_node();
+
+ void set_use_magnet(bool p_use);
+ bool is_using_magnet() const;
+
+ void set_magnet_position(const Vector3 &p_constraint);
+ const Vector3 &get_magnet_position() const;
+
+ void set_min_distance(real_t p_min_distance);
+ real_t get_min_distance() const { return min_distance; }
+
+ void set_max_iterations(int p_iterations);
+ int get_max_iterations() const { return max_iterations; }
+
+ Skeleton *get_parent_skeleton() const { return skeleton; }
+
+ bool is_running();
+
+ void start(bool p_one_time = false);
+ void stop();
+
+private:
+ Transform _get_target_transform();
+ void reload_chain();
+ void reload_goal();
+ void _solve_chain();
+};
+
+#endif // _3D_DISABLED
+
+#endif // SKELETON_IK_H
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index 49013b160a..58be636e44 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -150,7 +150,7 @@ void Tween::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE: {
- if (!processing) {
+ if (!is_active()) {
//make sure that a previous process state was not saved
//only process if "processing" is set
set_physics_process_internal(false);
@@ -164,7 +164,7 @@ void Tween::_notification(int p_what) {
if (tween_process_mode == TWEEN_PROCESS_PHYSICS)
break;
- if (processing)
+ if (is_active())
_tween_process(get_process_delta_time());
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
@@ -172,7 +172,7 @@ void Tween::_notification(int p_what) {
if (tween_process_mode == TWEEN_PROCESS_IDLE)
break;
- if (processing)
+ if (is_active())
_tween_process(get_physics_process_delta_time());
} break;
case NOTIFICATION_EXIT_TREE: {
@@ -204,7 +204,7 @@ void Tween::_bind_methods() {
ClassDB::bind_method(D_METHOD("resume", "object", "key"), &Tween::resume, DEFVAL(""));
ClassDB::bind_method(D_METHOD("resume_all"), &Tween::resume_all);
ClassDB::bind_method(D_METHOD("remove", "object", "key"), &Tween::remove, DEFVAL(""));
- ClassDB::bind_method(D_METHOD("_remove", "object", "key", "first_only"), &Tween::_remove);
+ ClassDB::bind_method(D_METHOD("_remove_by_uid", "uid"), &Tween::_remove_by_uid);
ClassDB::bind_method(D_METHOD("remove_all"), &Tween::remove_all);
ClassDB::bind_method(D_METHOD("seek", "time"), &Tween::seek);
ClassDB::bind_method(D_METHOD("tell"), &Tween::tell);
@@ -521,8 +521,8 @@ void Tween::_tween_process(float p_delta) {
pending_update++;
// if repeat and all interpolates was finished then reset all interpolates
+ bool all_finished = true;
if (repeat) {
- bool all_finished = true;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
@@ -538,9 +538,12 @@ void Tween::_tween_process(float p_delta) {
reset_all();
}
+ all_finished = true;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
InterpolateData &data = E->get();
+ all_finished = all_finished && data.finish;
+
if (!data.active || data.finish)
continue;
@@ -554,8 +557,8 @@ void Tween::_tween_process(float p_delta) {
continue;
else if (prev_delaying) {
- emit_signal("tween_started", object, NodePath(Vector<StringName>(), data.key, false));
_apply_tween_value(data, data.initial_val);
+ emit_signal("tween_started", object, NodePath(Vector<StringName>(), data.key, false));
}
if (data.elapsed > (data.delay + data.duration)) {
@@ -602,32 +605,29 @@ void Tween::_tween_process(float p_delta) {
}
} else {
Variant result = _run_equation(data);
- emit_signal("tween_step", object, NodePath(Vector<StringName>(), data.key, false), data.elapsed, result);
_apply_tween_value(data, result);
+ emit_signal("tween_step", object, NodePath(Vector<StringName>(), data.key, false), data.elapsed, result);
}
if (data.finish) {
_apply_tween_value(data, data.final_val);
+ data.elapsed = 0;
emit_signal("tween_completed", object, NodePath(Vector<StringName>(), data.key, false));
// not repeat mode, remove completed action
if (!repeat)
- call_deferred("_remove", object, NodePath(Vector<StringName>(), data.key, false), true);
- }
+ call_deferred("_remove_by_uid", data.uid);
+ } else if (!repeat)
+ all_finished = all_finished && data.finish;
}
pending_update--;
+
+ if (all_finished)
+ set_active(false);
}
void Tween::set_tween_process_mode(TweenProcessMode p_mode) {
- if (tween_process_mode == p_mode)
- return;
-
- bool pr = processing;
- if (pr)
- _set_process(false);
tween_process_mode = p_mode;
- if (pr)
- _set_process(true);
}
Tween::TweenProcessMode Tween::get_tween_process_mode() const {
@@ -635,32 +635,21 @@ Tween::TweenProcessMode Tween::get_tween_process_mode() const {
return tween_process_mode;
}
-void Tween::_set_process(bool p_process, bool p_force) {
-
- if (processing == p_process && !p_force)
- return;
-
- switch (tween_process_mode) {
-
- case TWEEN_PROCESS_PHYSICS: set_physics_process_internal(p_process && active); break;
- case TWEEN_PROCESS_IDLE: set_process_internal(p_process && active); break;
- }
-
- processing = p_process;
-}
-
bool Tween::is_active() const {
- return active;
+ return is_processing_internal() || is_physics_processing_internal();
}
void Tween::set_active(bool p_active) {
- if (active == p_active)
+ if (is_active() == p_active)
return;
- active = p_active;
- _set_process(processing, true);
+ switch (tween_process_mode) {
+
+ case TWEEN_PROCESS_IDLE: set_process_internal(p_active); break;
+ case TWEEN_PROCESS_PHYSICS: set_physics_process_internal(p_active); break;
+ }
}
bool Tween::is_repeat() const {
@@ -686,7 +675,6 @@ float Tween::get_speed_scale() const {
bool Tween::start() {
set_active(true);
- _set_process(true);
return true;
}
@@ -746,7 +734,6 @@ bool Tween::stop(Object *p_object, StringName p_key) {
bool Tween::stop_all() {
set_active(false);
- _set_process(false);
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
@@ -761,7 +748,6 @@ bool Tween::stop_all() {
bool Tween::resume(Object *p_object, StringName p_key) {
set_active(true);
- _set_process(true);
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
@@ -780,7 +766,6 @@ bool Tween::resume(Object *p_object, StringName p_key) {
bool Tween::resume_all() {
set_active(true);
- _set_process(true);
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
@@ -793,15 +778,9 @@ bool Tween::resume_all() {
}
bool Tween::remove(Object *p_object, StringName p_key) {
- _remove(p_object, p_key, false);
- return true;
-}
-
-void Tween::_remove(Object *p_object, StringName p_key, bool first_only) {
-
if (pending_update != 0) {
- call_deferred("_remove", p_object, p_key, first_only);
- return;
+ call_deferred("remove", p_object, p_key);
+ return true;
}
List<List<InterpolateData>::Element *> for_removal;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
@@ -812,14 +791,33 @@ void Tween::_remove(Object *p_object, StringName p_key, bool first_only) {
continue;
if (object == p_object && (data.concatenated_key == p_key || p_key == "")) {
for_removal.push_back(E);
- if (first_only) {
- break;
- }
}
}
for (List<List<InterpolateData>::Element *>::Element *E = for_removal.front(); E; E = E->next()) {
interpolates.erase(E->get());
}
+ return true;
+}
+
+void Tween::_remove_by_uid(int uid) {
+ if (pending_update != 0) {
+ call_deferred("_remove_by_uid", uid);
+ return;
+ }
+
+ for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
+ if (uid == E->get().uid) {
+ E->erase();
+ break;
+ }
+ }
+}
+
+void Tween::_push_interpolate_data(InterpolateData &p_data) {
+ pending_update++;
+ p_data.uid = ++uid;
+ interpolates.push_back(p_data);
+ pending_update--;
}
bool Tween::remove_all() {
@@ -829,8 +827,8 @@ bool Tween::remove_all() {
return true;
}
set_active(false);
- _set_process(false);
interpolates.clear();
+ uid = 0;
return true;
}
@@ -886,6 +884,10 @@ real_t Tween::tell() const {
real_t Tween::get_runtime() const {
+ if (speed_scale == 0) {
+ return INFINITY;
+ }
+
pending_update++;
real_t runtime = 0;
for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
@@ -896,7 +898,8 @@ real_t Tween::get_runtime() const {
runtime = t;
}
pending_update--;
- return runtime;
+
+ return runtime / speed_scale;
}
bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final_val, Variant &p_delta_val) {
@@ -1038,7 +1041,7 @@ bool Tween::interpolate_property(Object *p_object, NodePath p_property, Variant
if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val))
return false;
- interpolates.push_back(data);
+ _push_interpolate_data(data);
return true;
}
@@ -1081,7 +1084,7 @@ bool Tween::interpolate_method(Object *p_object, StringName p_method, Variant p_
if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val))
return false;
- interpolates.push_back(data);
+ _push_interpolate_data(data);
return true;
}
@@ -1133,9 +1136,7 @@ bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_c
data.arg[3] = p_arg4;
data.arg[4] = p_arg5;
- pending_update++;
- interpolates.push_back(data);
- pending_update--;
+ _push_interpolate_data(data);
return true;
}
@@ -1186,9 +1187,7 @@ bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, S
data.arg[3] = p_arg4;
data.arg[4] = p_arg5;
- pending_update++;
- interpolates.push_back(data);
- pending_update--;
+ _push_interpolate_data(data);
return true;
}
@@ -1243,7 +1242,7 @@ bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_ini
data.ease_type = p_ease_type;
data.delay = p_delay;
- interpolates.push_back(data);
+ _push_interpolate_data(data);
return true;
}
@@ -1294,7 +1293,7 @@ bool Tween::follow_method(Object *p_object, StringName p_method, Variant p_initi
data.ease_type = p_ease_type;
data.delay = p_delay;
- interpolates.push_back(data);
+ _push_interpolate_data(data);
return true;
}
@@ -1352,7 +1351,7 @@ bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_
if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val))
return false;
- interpolates.push_back(data);
+ _push_interpolate_data(data);
return true;
}
@@ -1407,7 +1406,7 @@ bool Tween::targeting_method(Object *p_object, StringName p_method, Object *p_in
if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val))
return false;
- interpolates.push_back(data);
+ _push_interpolate_data(data);
return true;
}
@@ -1415,11 +1414,10 @@ Tween::Tween() {
//String autoplay;
tween_process_mode = TWEEN_PROCESS_IDLE;
- processing = false;
- active = false;
repeat = false;
speed_scale = 1;
pending_update = 0;
+ uid = 0;
}
Tween::~Tween() {
diff --git a/scene/animation/tween.h b/scene/animation/tween.h
index 757d80e90a..aa47c00717 100644
--- a/scene/animation/tween.h
+++ b/scene/animation/tween.h
@@ -100,15 +100,15 @@ private:
real_t delay;
int args;
Variant arg[5];
+ int uid;
};
String autoplay;
TweenProcessMode tween_process_mode;
- bool processing;
- bool active;
bool repeat;
float speed_scale;
mutable int pending_update;
+ int uid;
List<InterpolateData> interpolates;
@@ -133,8 +133,8 @@ private:
bool _apply_tween_value(InterpolateData &p_data, Variant &value);
void _tween_process(float p_delta);
- void _set_process(bool p_process, bool p_force = false);
- void _remove(Object *p_object, StringName p_key, bool first_only);
+ void _remove_by_uid(int uid);
+ void _push_interpolate_data(InterpolateData &p_data);
protected:
bool _set(const StringName &p_name, const Variant &p_value);