Browse Source

-Added AnimationGraphPlayer (still missing features)
-Added ability to edit resources from built-in inspector (wip, needs testing and feedback)

Juan Linietsky 7 years ago
parent
commit
0a1c1c660f

+ 0 - 1
core/resource.cpp

@@ -187,7 +187,6 @@ Ref<Resource> Resource::duplicate_for_local_scene(Node *p_for_scene, Map<Ref<Res
 
 void Resource::configure_for_local_scene(Node *p_for_scene, Map<Ref<Resource>, Ref<Resource> > &remap_cache) {
 
-	print_line("configure for local: " + get_class());
 	List<PropertyInfo> plist;
 	get_property_list(&plist);
 

+ 22 - 1
editor/editor_data.cpp

@@ -78,7 +78,7 @@ void EditorHistory::cleanup_history() {
 		current = history.size() - 1;
 }
 
-void EditorHistory::_add_object(ObjectID p_object, const String &p_property, int p_level_change) {
+void EditorHistory::_add_object(ObjectID p_object, const String &p_property, int p_level_change, bool p_inspector_only) {
 
 	Object *obj = ObjectDB::get_instance(p_object);
 	ERR_FAIL_COND(!obj);
@@ -88,6 +88,7 @@ void EditorHistory::_add_object(ObjectID p_object, const String &p_property, int
 		o.ref = REF(r);
 	o.object = p_object;
 	o.property = p_property;
+	o.inspector_only = p_inspector_only;
 
 	History h;
 
@@ -120,6 +121,11 @@ void EditorHistory::_add_object(ObjectID p_object, const String &p_property, int
 	current++;
 }
 
+void EditorHistory::add_object_inspector_only(ObjectID p_object) {
+
+	_add_object(p_object, "", -1, true);
+}
+
 void EditorHistory::add_object(ObjectID p_object) {
 
 	_add_object(p_object, "", -1);
@@ -142,6 +148,13 @@ int EditorHistory::get_history_pos() {
 	return current;
 }
 
+bool EditorHistory::is_history_obj_inspector_only(int p_obj) const {
+
+	ERR_FAIL_INDEX_V(p_obj, history.size(), false);
+	ERR_FAIL_INDEX_V(history[p_obj].level, history[p_obj].path.size(), false);
+	return history[p_obj].path[history[p_obj].level].inspector_only;
+}
+
 ObjectID EditorHistory::get_history_obj(int p_obj) const {
 	ERR_FAIL_INDEX_V(p_obj, history.size(), 0);
 	ERR_FAIL_INDEX_V(history[p_obj].level, history[p_obj].path.size(), 0);
@@ -180,6 +193,14 @@ bool EditorHistory::previous() {
 	return true;
 }
 
+bool EditorHistory::is_current_inspector_only() const {
+
+	if (current < 0 || current >= history.size())
+		return false;
+
+	const History &h = history[current];
+	return h.path[h.level].inspector_only;
+}
 ObjectID EditorHistory::get_current() {
 
 	if (current < 0 || current >= history.size())

+ 5 - 1
editor/editor_data.h

@@ -50,6 +50,7 @@ class EditorHistory {
 		REF ref;
 		ObjectID object;
 		String property;
+		bool inspector_only;
 	};
 
 	struct History {
@@ -70,7 +71,7 @@ class EditorHistory {
 		Variant value;
 	};
 
-	void _add_object(ObjectID p_object, const String &p_property, int p_level_change);
+	void _add_object(ObjectID p_object, const String &p_property, int p_level_change, bool p_inspector_only = false);
 
 public:
 	void cleanup_history();
@@ -78,6 +79,7 @@ public:
 	bool is_at_beginning() const;
 	bool is_at_end() const;
 
+	void add_object_inspector_only(ObjectID p_object);
 	void add_object(ObjectID p_object);
 	void add_object(ObjectID p_object, const String &p_subprop);
 	void add_object(ObjectID p_object, int p_relevel);
@@ -85,10 +87,12 @@ public:
 	int get_history_len();
 	int get_history_pos();
 	ObjectID get_history_obj(int p_obj) const;
+	bool is_history_obj_inspector_only(int p_obj) const;
 
 	bool next();
 	bool previous();
 	ObjectID get_current();
+	bool is_current_inspector_only() const;
 
 	int get_path_size() const;
 	ObjectID get_path_object(int p_index) const;

+ 9 - 0
editor/editor_inspector.cpp

@@ -1477,6 +1477,9 @@ void EditorInspector::update_tree() {
 
 					ep->object = object;
 					ep->connect("property_changed", this, "_property_changed");
+					if (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED) {
+						ep->connect("property_changed", this, "_property_changed_update_all", varray(), CONNECT_DEFERRED);
+					}
 					ep->connect("property_keyed", this, "_property_keyed");
 					ep->connect("property_keyed_with_value", this, "_property_keyed_with_value");
 					ep->connect("property_checked", this, "_property_checked");
@@ -1782,6 +1785,10 @@ void EditorInspector::_property_changed(const String &p_path, const Variant &p_v
 	_edit_set(p_path, p_value, false, "");
 }
 
+void EditorInspector::_property_changed_update_all(const String &p_path, const Variant &p_value) {
+	update_tree();
+}
+
 void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values) {
 
 	ERR_FAIL_COND(p_paths.size() == 0 || p_values.size() == 0);
@@ -1951,6 +1958,8 @@ void EditorInspector::_bind_methods() {
 
 	ClassDB::bind_method("_multiple_properties_changed", &EditorInspector::_multiple_properties_changed);
 	ClassDB::bind_method("_property_changed", &EditorInspector::_property_changed);
+	ClassDB::bind_method("_property_changed_update_all", &EditorInspector::_property_changed_update_all);
+
 	ClassDB::bind_method("_edit_request_change", &EditorInspector::_edit_request_change);
 	ClassDB::bind_method("_node_removed", &EditorInspector::_node_removed);
 	ClassDB::bind_method("_filter_changed", &EditorInspector::_filter_changed);

+ 1 - 0
editor/editor_inspector.h

@@ -257,6 +257,7 @@ class EditorInspector : public ScrollContainer {
 	void _edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field);
 
 	void _property_changed(const String &p_path, const Variant &p_value);
+	void _property_changed_update_all(const String &p_path, const Variant &p_value);
 	void _multiple_properties_changed(Vector<String> p_paths, Array p_values);
 	void _property_keyed(const String &p_path);
 	void _property_keyed_with_value(const String &p_path, const Variant &p_value);

+ 62 - 35
editor/editor_node.cpp

@@ -66,6 +66,7 @@
 #include "editor/import/resource_importer_scene.h"
 #include "editor/import/resource_importer_texture.h"
 #include "editor/import/resource_importer_wav.h"
+#include "editor/plugins/animation_blend_tree_editor_plugin.h"
 #include "editor/plugins/animation_player_editor_plugin.h"
 #include "editor/plugins/animation_tree_editor_plugin.h"
 #include "editor/plugins/asset_library_editor_plugin.h"
@@ -1300,7 +1301,25 @@ void EditorNode::_dialog_action(String p_file) {
 	}
 }
 
-void EditorNode::push_item(Object *p_object, const String &p_property) {
+bool EditorNode::item_has_editor(Object *p_object) {
+
+	return editor_data.get_subeditors(p_object).size() > 0;
+}
+
+void EditorNode::edit_item(Object *p_object) {
+
+	Vector<EditorPlugin *> sub_plugins = editor_data.get_subeditors(p_object);
+
+	if (!sub_plugins.empty()) {
+		_display_top_editors(false);
+
+		_set_top_editors(sub_plugins);
+		_set_editing_top_editors(p_object);
+		_display_top_editors(true);
+	}
+}
+
+void EditorNode::push_item(Object *p_object, const String &p_property, bool p_inspector_only) {
 
 	if (!p_object) {
 		get_inspector()->edit(NULL);
@@ -1312,7 +1331,9 @@ void EditorNode::push_item(Object *p_object, const String &p_property) {
 	uint32_t id = p_object->get_instance_id();
 	if (id != editor_history.get_current()) {
 
-		if (p_property == "")
+		if (p_inspector_only) {
+			editor_history.add_object_inspector_only(id);
+		} else if (p_property == "")
 			editor_history.add_object(id);
 		else
 			editor_history.add_object(id, p_property);
@@ -1366,6 +1387,7 @@ void EditorNode::_edit_current() {
 
 	uint32_t current = editor_history.get_current();
 	Object *current_obj = current > 0 ? ObjectDB::get_instance(current) : NULL;
+	bool inspector_only = editor_history.is_current_inspector_only();
 
 	this->current = current_obj;
 
@@ -1457,57 +1479,60 @@ void EditorNode::_edit_current() {
 
 	/* Take care of PLUGIN EDITOR */
 
-	EditorPlugin *main_plugin = editor_data.get_editor(current_obj);
+	if (!inspector_only) {
 
-	if (main_plugin) {
+		EditorPlugin *main_plugin = editor_data.get_editor(current_obj);
 
-		// special case if use of external editor is true
-		if (main_plugin->get_name() == "Script" && (bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor")) || overrides_external_editor(current_obj))) {
-			if (!changing_scene)
-				main_plugin->edit(current_obj);
-		}
+		if (main_plugin) {
 
-		else if (main_plugin != editor_plugin_screen && (!ScriptEditor::get_singleton() || !ScriptEditor::get_singleton()->is_visible_in_tree() || ScriptEditor::get_singleton()->can_take_away_focus())) {
-			// update screen main_plugin
+			// special case if use of external editor is true
+			if (main_plugin->get_name() == "Script" && (bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor")) || overrides_external_editor(current_obj))) {
+				if (!changing_scene)
+					main_plugin->edit(current_obj);
+			}
 
-			if (!changing_scene) {
+			else if (main_plugin != editor_plugin_screen && (!ScriptEditor::get_singleton() || !ScriptEditor::get_singleton()->is_visible_in_tree() || ScriptEditor::get_singleton()->can_take_away_focus())) {
+				// update screen main_plugin
 
-				if (editor_plugin_screen)
-					editor_plugin_screen->make_visible(false);
-				editor_plugin_screen = main_plugin;
-				editor_plugin_screen->edit(current_obj);
+				if (!changing_scene) {
 
-				editor_plugin_screen->make_visible(true);
+					if (editor_plugin_screen)
+						editor_plugin_screen->make_visible(false);
+					editor_plugin_screen = main_plugin;
+					editor_plugin_screen->edit(current_obj);
 
-				int plugin_count = editor_data.get_editor_plugin_count();
-				for (int i = 0; i < plugin_count; i++) {
-					editor_data.get_editor_plugin(i)->notify_main_screen_changed(editor_plugin_screen->get_name());
-				}
+					editor_plugin_screen->make_visible(true);
+
+					int plugin_count = editor_data.get_editor_plugin_count();
+					for (int i = 0; i < plugin_count; i++) {
+						editor_data.get_editor_plugin(i)->notify_main_screen_changed(editor_plugin_screen->get_name());
+					}
 
-				for (int i = 0; i < editor_table.size(); i++) {
+					for (int i = 0; i < editor_table.size(); i++) {
 
-					main_editor_buttons[i]->set_pressed(editor_table[i] == main_plugin);
+						main_editor_buttons[i]->set_pressed(editor_table[i] == main_plugin);
+					}
 				}
-			}
 
-		} else {
+			} else {
 
-			editor_plugin_screen->edit(current_obj);
+				editor_plugin_screen->edit(current_obj);
+			}
 		}
-	}
 
-	Vector<EditorPlugin *> sub_plugins = editor_data.get_subeditors(current_obj);
+		Vector<EditorPlugin *> sub_plugins = editor_data.get_subeditors(current_obj);
 
-	if (!sub_plugins.empty()) {
-		_display_top_editors(false);
+		if (!sub_plugins.empty()) {
+			_display_top_editors(false);
 
-		_set_top_editors(sub_plugins);
-		_set_editing_top_editors(current_obj);
-		_display_top_editors(true);
+			_set_top_editors(sub_plugins);
+			_set_editing_top_editors(current_obj);
+			_display_top_editors(true);
 
-	} else if (!editor_plugins_over->get_plugins_list().empty()) {
+		} else if (!editor_plugins_over->get_plugins_list().empty()) {
 
-		_hide_top_editors();
+			_hide_top_editors();
+		}
 	}
 
 	inspector_dock->update(current_obj);
@@ -5346,6 +5371,8 @@ EditorNode::EditorNode() {
 	add_editor_plugin(memnew(SpatialEditorPlugin(this)));
 	add_editor_plugin(memnew(ScriptEditorPlugin(this)));
 
+	add_editor_plugin(memnew(AnimationNodeBlendTreeEditorPlugin(this)));
+
 	EditorAudioBuses *audio_bus_editor = EditorAudioBuses::register_editor();
 
 	ScriptTextEditor::register_editor(); //register one for text scripts

+ 3 - 1
editor/editor_node.h

@@ -634,7 +634,9 @@ public:
 
 	static HBoxContainer *get_menu_hb() { return singleton->menu_hb; }
 
-	void push_item(Object *p_object, const String &p_property = "");
+	void push_item(Object *p_object, const String &p_property = "", bool p_inspector_only = false);
+	void edit_item(Object *p_object);
+	bool item_has_editor(Object *p_object);
 
 	void open_request(const String &p_path);
 

+ 33 - 3
editor/editor_properties.cpp

@@ -1986,6 +1986,13 @@ void EditorPropertyResource::_sub_inspector_object_id_selected(int p_id) {
 	emit_signal("object_id_selected", get_edited_property(), p_id);
 }
 
+void EditorPropertyResource::_open_editor_pressed() {
+	RES res = get_edited_object()->get(get_edited_property());
+	if (res.is_valid()) {
+		EditorNode::get_singleton()->edit_item(res.ptr());
+	}
+}
+
 void EditorPropertyResource::update_property() {
 
 	RES res = get_edited_object()->get(get_edited_property());
@@ -2009,9 +2016,29 @@ void EditorPropertyResource::update_property() {
 				sub_inspector->set_read_only(is_read_only());
 				sub_inspector->set_use_folding(is_using_folding());
 
-				add_child(sub_inspector);
-				set_bottom_editor(sub_inspector);
+				sub_inspector_vbox = memnew(VBoxContainer);
+				add_child(sub_inspector_vbox);
+				set_bottom_editor(sub_inspector_vbox);
+
+				sub_inspector_vbox->add_child(sub_inspector);
 				assign->set_pressed(true);
+
+				bool use_editor = false;
+				for (int i = 0; i < EditorNode::get_singleton()->get_editor_data().get_editor_plugin_count(); i++) {
+					EditorPlugin *ep = EditorNode::get_singleton()->get_editor_data().get_editor_plugin(i);
+					if (ep->handles(res.ptr())) {
+						use_editor = true;
+					}
+				}
+
+				if (use_editor) {
+					Button *open_in_editor = memnew(Button);
+					open_in_editor->set_text(TTR("Open Editor"));
+					open_in_editor->set_icon(get_icon("Edit", "EditorIcons"));
+					sub_inspector_vbox->add_child(open_in_editor);
+					open_in_editor->connect("pressed", this, "_open_editor_pressed");
+					open_in_editor->set_h_size_flags(SIZE_SHRINK_CENTER);
+				}
 			}
 
 			if (res.ptr() != sub_inspector->get_edited_object()) {
@@ -2021,8 +2048,9 @@ void EditorPropertyResource::update_property() {
 		} else {
 			if (sub_inspector) {
 				set_bottom_editor(NULL);
-				memdelete(sub_inspector);
+				memdelete(sub_inspector_vbox);
 				sub_inspector = NULL;
+				sub_inspector_vbox = NULL;
 			}
 		}
 #endif
@@ -2242,11 +2270,13 @@ void EditorPropertyResource::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &EditorPropertyResource::can_drop_data_fw);
 	ClassDB::bind_method(D_METHOD("drop_data_fw"), &EditorPropertyResource::drop_data_fw);
 	ClassDB::bind_method(D_METHOD("_button_draw"), &EditorPropertyResource::_button_draw);
+	ClassDB::bind_method(D_METHOD("_open_editor_pressed"), &EditorPropertyResource::_open_editor_pressed);
 }
 
 EditorPropertyResource::EditorPropertyResource() {
 
 	sub_inspector = NULL;
+	sub_inspector_vbox = NULL;
 	use_sub_inspector = !bool(EDITOR_GET("interface/inspector/open_resources_in_new_inspector"));
 	HBoxContainer *hbc = memnew(HBoxContainer);
 	add_child(hbc);

+ 3 - 0
editor/editor_properties.h

@@ -491,6 +491,7 @@ class EditorPropertyResource : public EditorProperty {
 	EditorFileDialog *file;
 	Vector<String> inheritors_array;
 	EditorInspector *sub_inspector;
+	VBoxContainer *sub_inspector_vbox;
 
 	bool use_sub_inspector;
 	bool dropping;
@@ -516,6 +517,8 @@ class EditorPropertyResource : public EditorProperty {
 	bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
 	void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
 
+	void _open_editor_pressed();
+
 protected:
 	static void _bind_methods();
 	void _notification(int p_what);

+ 1 - 0
editor/editor_themes.cpp

@@ -948,6 +948,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
 	theme->set_stylebox("bg", "GraphEdit", style_tree_bg);
 	theme->set_color("grid_major", "GraphEdit", grid_major_color);
 	theme->set_color("grid_minor", "GraphEdit", grid_minor_color);
+	theme->set_color("activity", "GraphEdit", accent_color);
 	theme->set_icon("minus", "GraphEdit", theme->get_icon("ZoomLess", "EditorIcons"));
 	theme->set_icon("more", "GraphEdit", theme->get_icon("ZoomMore", "EditorIcons"));
 	theme->set_icon("reset", "GraphEdit", theme->get_icon("ZoomReset", "EditorIcons"));

+ 829 - 0
editor/plugins/animation_blend_tree_editor_plugin.cpp

@@ -0,0 +1,829 @@
+#include "animation_blend_tree_editor_plugin.h"
+
+#include "core/io/resource_loader.h"
+#include "core/project_settings.h"
+#include "os/input.h"
+#include "os/keyboard.h"
+#include "scene/animation/animation_player.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/panel.h"
+#include "scene/main/viewport.h"
+
+void AnimationNodeBlendTreeEditor::edit(AnimationNodeBlendTree *p_blend_tree) {
+
+	blend_tree = p_blend_tree;
+
+	if (!blend_tree) {
+		hide();
+	} else {
+		_update_graph();
+	}
+}
+
+void AnimationNodeBlendTreeEditor::add_custom_type(const String &p_name, const Ref<Script> &p_script) {
+
+	for (int i = 0; i < add_options.size(); i++) {
+		ERR_FAIL_COND(add_options[i].script == p_script);
+	}
+
+	AddOption ao;
+	ao.name = p_name;
+	ao.script = p_script;
+	add_options.push_back(ao);
+
+	_update_options_menu();
+}
+
+void AnimationNodeBlendTreeEditor::remove_custom_type(const Ref<Script> &p_script) {
+
+	for (int i = 0; i < add_options.size(); i++) {
+		if (add_options[i].script == p_script) {
+			add_options.remove(i);
+			return;
+		}
+	}
+
+	_update_options_menu();
+}
+
+void AnimationNodeBlendTreeEditor::_update_options_menu() {
+
+	add_node->get_popup()->clear();
+	for (int i = 0; i < add_options.size(); i++) {
+		add_node->get_popup()->add_item(add_options[i].name);
+	}
+}
+
+Size2 AnimationNodeBlendTreeEditor::get_minimum_size() const {
+
+	return Size2(10, 200);
+}
+
+void AnimationNodeBlendTreeEditor::_update_graph() {
+
+	if (updating)
+		return;
+
+	graph->set_scroll_ofs(blend_tree->get_graph_offset() * EDSCALE);
+
+	if (blend_tree->get_tree().is_valid()) {
+		goto_parent->show();
+	} else {
+		goto_parent->hide();
+	}
+	graph->clear_connections();
+	//erase all nodes
+	for (int i = 0; i < graph->get_child_count(); i++) {
+
+		if (Object::cast_to<GraphNode>(graph->get_child(i))) {
+			memdelete(graph->get_child(i));
+			i--;
+		}
+	}
+
+	animations.clear();
+
+	List<StringName> nodes;
+	blend_tree->get_node_list(&nodes);
+
+	for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
+
+		GraphNode *node = memnew(GraphNode);
+		graph->add_child(node);
+
+		Ref<AnimationNode> agnode = blend_tree->get_node(E->get());
+
+		if (!agnode->is_connected("changed", this, "_node_changed")) {
+			agnode->connect("changed", this, "_node_changed", varray(agnode->get_instance_id()), CONNECT_DEFERRED);
+		}
+
+		node->set_offset(agnode->get_position() * EDSCALE);
+
+		node->set_title(agnode->get_caption());
+		node->set_name(E->get());
+
+		int base = 0;
+		if (String(E->get()) != "output") {
+			LineEdit *name = memnew(LineEdit);
+			name->set_text(E->get());
+			name->set_expand_to_text_length(true);
+			node->add_child(name);
+			node->set_slot(0, false, 0, Color(), true, 0, get_color("font_color", "Label"));
+			name->connect("text_entered", this, "_node_renamed", varray(agnode));
+			name->connect("focus_exited", this, "_node_renamed_focus_out", varray(name, agnode));
+			base = 1;
+			node->set_show_close_button(true);
+			node->connect("close_request", this, "_delete_request", varray(E->get()), CONNECT_DEFERRED);
+		}
+
+		for (int i = 0; i < agnode->get_input_count(); i++) {
+			Label *in_name = memnew(Label);
+			node->add_child(in_name);
+			in_name->set_text(agnode->get_input_name(i));
+			node->set_slot(base + i, true, 0, get_color("font_color", "Label"), false, 0, Color());
+		}
+
+		node->connect("dragged", this, "_node_dragged", varray(agnode));
+
+		if (EditorNode::get_singleton()->item_has_editor(agnode.ptr())) {
+			node->add_child(memnew(HSeparator));
+			Button *open_in_editor = memnew(Button);
+			open_in_editor->set_text(TTR("Open Editor"));
+			open_in_editor->set_icon(get_icon("Edit", "EditorIcons"));
+			node->add_child(open_in_editor);
+			open_in_editor->connect("pressed", this, "_open_in_editor", varray(E->get()), CONNECT_DEFERRED);
+			open_in_editor->set_h_size_flags(SIZE_SHRINK_CENTER);
+		}
+
+		if (agnode->has_filter()) {
+
+			node->add_child(memnew(HSeparator));
+			Button *edit_filters = memnew(Button);
+			edit_filters->set_text(TTR("Edit Filters"));
+			edit_filters->set_icon(get_icon("AnimationFilter", "EditorIcons"));
+			node->add_child(edit_filters);
+			edit_filters->connect("pressed", this, "_edit_filters", varray(E->get()), CONNECT_DEFERRED);
+			edit_filters->set_h_size_flags(SIZE_SHRINK_CENTER);
+		}
+
+		Ref<AnimationNodeAnimation> anim = agnode;
+		if (anim.is_valid()) {
+
+			MenuButton *mb = memnew(MenuButton);
+			mb->set_text(anim->get_animation());
+			mb->set_icon(get_icon("Animation", "EditorIcons"));
+			Array options;
+
+			node->add_child(memnew(HSeparator));
+			node->add_child(mb);
+
+			ProgressBar *pb = memnew(ProgressBar);
+
+			AnimationGraphPlayer *player = anim->get_graph_player();
+			if (player->has_node(player->get_animation_player())) {
+				AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(player->get_node(player->get_animation_player()));
+				if (ap) {
+					List<StringName> anims;
+					ap->get_animation_list(&anims);
+
+					for (List<StringName>::Element *F = anims.front(); F; F = F->next()) {
+						mb->get_popup()->add_item(F->get());
+						options.push_back(F->get());
+					}
+
+					if (ap->has_animation(anim->get_animation())) {
+						pb->set_max(ap->get_animation(anim->get_animation())->get_length());
+					}
+				}
+			}
+
+			pb->set_percent_visible(false);
+			animations[E->get()] = pb;
+			node->add_child(pb);
+
+			mb->get_popup()->connect("index_pressed", this, "_anim_selected", varray(options, E->get()), CONNECT_DEFERRED);
+		}
+
+		Ref<AnimationNodeOneShot> oneshot = agnode;
+		if (oneshot.is_valid()) {
+
+			HBoxContainer *play_stop = memnew(HBoxContainer);
+			play_stop->add_spacer();
+			Button *play = memnew(Button);
+			play->set_icon(get_icon("Play", "EditorIcons"));
+			play->connect("pressed", this, "_oneshot_start", varray(E->get()), CONNECT_DEFERRED);
+			play_stop->add_child(play);
+			Button *stop = memnew(Button);
+			stop->set_icon(get_icon("Stop", "EditorIcons"));
+			stop->connect("pressed", this, "_oneshot_stop", varray(E->get()), CONNECT_DEFERRED);
+			play_stop->add_child(stop);
+			play_stop->add_spacer();
+			node->add_child(play_stop);
+		}
+	}
+
+	List<AnimationNodeBlendTree::NodeConnection> connections;
+	blend_tree->get_node_connections(&connections);
+
+	for (List<AnimationNodeBlendTree::NodeConnection>::Element *E = connections.front(); E; E = E->next()) {
+
+		StringName from = E->get().output_node;
+		StringName to = E->get().input_node;
+		int to_idx = E->get().input_index;
+
+		graph->connect_node(from, 0, to, to_idx);
+	}
+}
+
+void AnimationNodeBlendTreeEditor::_add_node(int p_idx) {
+
+	ERR_FAIL_INDEX(p_idx, add_options.size());
+
+	Ref<AnimationNode> anode;
+
+	if (add_options[p_idx].type != String()) {
+		AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instance(add_options[p_idx].type));
+		ERR_FAIL_COND(!an);
+		anode = Ref<AnimationNode>(an);
+	} else {
+		ERR_FAIL_COND(add_options[p_idx].script.is_null());
+		String base_type = add_options[p_idx].script->get_instance_base_type();
+		AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instance(base_type));
+		ERR_FAIL_COND(!an);
+		anode = Ref<AnimationNode>(an);
+		anode->set_script(add_options[p_idx].script.get_ref_ptr());
+	}
+
+	Point2 instance_pos = graph->get_scroll_ofs() + graph->get_size() * 0.5;
+
+	anode->set_position(instance_pos);
+
+	String base_name = add_options[p_idx].name;
+	int base = 1;
+	String name = base_name;
+	while (blend_tree->has_node(name)) {
+		base++;
+		name = base_name + " " + itos(base);
+	}
+
+	undo_redo->create_action("Add Node to BlendTree");
+	undo_redo->add_do_method(blend_tree, "add_node", name, anode);
+	undo_redo->add_undo_method(blend_tree, "remove_node", name);
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->commit_action();
+}
+
+void AnimationNodeBlendTreeEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_to, Ref<AnimationNode> p_node) {
+
+	updating = true;
+	undo_redo->create_action("Node Moved");
+	undo_redo->add_do_method(p_node.ptr(), "set_position", p_to / EDSCALE);
+	undo_redo->add_undo_method(p_node.ptr(), "set_position", p_from / EDSCALE);
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->commit_action();
+	updating = false;
+}
+
+void AnimationNodeBlendTreeEditor::_connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) {
+
+	AnimationNodeBlendTree::ConnectionError err = blend_tree->can_connect_node(p_to, p_to_index, p_from);
+
+	if (err != AnimationNodeBlendTree::CONNECTION_OK) {
+		EditorNode::get_singleton()->show_warning(TTR("Unable to connect, port may be in use or connection may be invalid."));
+		return;
+	}
+
+	undo_redo->create_action("Nodes Connected");
+	undo_redo->add_do_method(blend_tree, "connect_node", p_to, p_to_index, p_from);
+	undo_redo->add_undo_method(blend_tree, "disconnect_node", p_to, p_to_index, p_from);
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->commit_action();
+}
+
+void AnimationNodeBlendTreeEditor::_disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) {
+
+	graph->disconnect_node(p_from, p_from_index, p_to, p_to_index);
+
+	updating = true;
+	undo_redo->create_action("Nodes Disconnected");
+	undo_redo->add_do_method(blend_tree, "disconnect_node", p_to, p_to_index);
+	undo_redo->add_undo_method(blend_tree, "connect_node", p_to, p_to_index, p_from);
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->commit_action();
+	updating = false;
+}
+
+void AnimationNodeBlendTreeEditor::_anim_selected(int p_index, Array p_options, const String &p_node) {
+
+	String option = p_options[p_index];
+
+	Ref<AnimationNodeAnimation> anim = blend_tree->get_node(p_node);
+	ERR_FAIL_COND(!anim.is_valid());
+
+	undo_redo->create_action("Set Animation");
+	undo_redo->add_do_method(anim.ptr(), "set_animation", option);
+	undo_redo->add_undo_method(anim.ptr(), "set_animation", anim->get_animation());
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->commit_action();
+}
+
+void AnimationNodeBlendTreeEditor::_delete_request(const String &p_which) {
+
+	undo_redo->create_action("Delete Node");
+	undo_redo->add_do_method(blend_tree, "remove_node", p_which);
+	undo_redo->add_undo_method(blend_tree, "add_node", p_which, blend_tree->get_node(p_which));
+
+	List<AnimationNodeBlendTree::NodeConnection> conns;
+	blend_tree->get_node_connections(&conns);
+
+	for (List<AnimationNodeBlendTree::NodeConnection>::Element *E = conns.front(); E; E = E->next()) {
+		if (E->get().output_node == p_which || E->get().input_node == p_which) {
+			undo_redo->add_undo_method(blend_tree, "connect_node", E->get().input_node, E->get().input_index, E->get().output_node);
+		}
+	}
+
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->commit_action();
+}
+
+void AnimationNodeBlendTreeEditor::_oneshot_start(const StringName &p_name) {
+
+	Ref<AnimationNodeOneShot> os = blend_tree->get_node(p_name);
+	ERR_FAIL_COND(!os.is_valid());
+	os->start();
+}
+
+void AnimationNodeBlendTreeEditor::_oneshot_stop(const StringName &p_name) {
+
+	Ref<AnimationNodeOneShot> os = blend_tree->get_node(p_name);
+	ERR_FAIL_COND(!os.is_valid());
+	os->stop();
+}
+
+void AnimationNodeBlendTreeEditor::_node_selected(Object *p_node) {
+
+	GraphNode *gn = Object::cast_to<GraphNode>(p_node);
+	ERR_FAIL_COND(!gn);
+
+	String name = gn->get_name();
+
+	Ref<AnimationNode> anode = blend_tree->get_node(name);
+	ERR_FAIL_COND(!anode.is_valid());
+
+	EditorNode::get_singleton()->push_item(anode.ptr(), "", true);
+}
+
+void AnimationNodeBlendTreeEditor::_open_in_editor(const String &p_which) {
+
+	Ref<AnimationNode> an = blend_tree->get_node(p_which);
+	ERR_FAIL_COND(!an.is_valid())
+	EditorNode::get_singleton()->edit_item(an.ptr());
+}
+
+void AnimationNodeBlendTreeEditor::_open_parent() {
+	if (blend_tree->get_tree().is_valid()) {
+		EditorNode::get_singleton()->edit_item(blend_tree->get_tree().ptr());
+	}
+}
+
+void AnimationNodeBlendTreeEditor::_filter_toggled() {
+
+	updating = true;
+	undo_redo->create_action("Toggle filter on/off");
+	undo_redo->add_do_method(_filter_edit.ptr(), "set_filter_enabled", filter_enabled->is_pressed());
+	undo_redo->add_undo_method(_filter_edit.ptr(), "set_filter_enabled", _filter_edit->is_filter_enabled());
+	undo_redo->add_do_method(this, "_update_filters", _filter_edit);
+	undo_redo->add_undo_method(this, "_update_filters", _filter_edit);
+	undo_redo->commit_action();
+	updating = false;
+}
+
+void AnimationNodeBlendTreeEditor::_filter_edited() {
+
+	TreeItem *edited = filters->get_edited();
+	ERR_FAIL_COND(!edited);
+
+	NodePath edited_path = edited->get_metadata(0);
+	bool filtered = edited->is_checked(0);
+
+	updating = true;
+	undo_redo->create_action("Change filter");
+	undo_redo->add_do_method(_filter_edit.ptr(), "set_filter_path", edited_path, filtered);
+	undo_redo->add_undo_method(_filter_edit.ptr(), "set_filter_path", edited_path, _filter_edit->is_path_filtered(edited_path));
+	undo_redo->add_do_method(this, "_update_filters", _filter_edit);
+	undo_redo->add_undo_method(this, "_update_filters", _filter_edit);
+	undo_redo->commit_action();
+	updating = false;
+}
+
+bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &anode) {
+
+	if (updating || _filter_edit != anode)
+		return false;
+
+	NodePath player_path = anode->get_graph_player()->get_animation_player();
+
+	if (!anode->get_graph_player()->has_node(player_path)) {
+		EditorNode::get_singleton()->show_warning(TTR("No animation player set, so unable to retrieve track names."));
+		return false;
+	}
+
+	AnimationPlayer *player = Object::cast_to<AnimationPlayer>(anode->get_graph_player()->get_node(player_path));
+	if (!player) {
+		EditorNode::get_singleton()->show_warning(TTR("Player path set is invalid, so unable to retrieve track names."));
+		return false;
+	}
+
+	Node *base = player->get_node(player->get_root());
+
+	if (!base) {
+		EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names."));
+		return false;
+	}
+
+	updating = true;
+
+	Set<String> paths;
+	{
+		List<StringName> animations;
+		player->get_animation_list(&animations);
+
+		for (List<StringName>::Element *E = animations.front(); E; E = E->next()) {
+
+			Ref<Animation> anim = player->get_animation(E->get());
+			for (int i = 0; i < anim->get_track_count(); i++) {
+				paths.insert(anim->track_get_path(i));
+			}
+		}
+	}
+
+	filter_enabled->set_pressed(anode->is_filter_enabled());
+	filters->clear();
+	TreeItem *root = filters->create_item();
+
+	Map<String, TreeItem *> parenthood;
+
+	for (Set<String>::Element *E = paths.front(); E; E = E->next()) {
+
+		NodePath path = E->get();
+		TreeItem *ti = NULL;
+		String accum;
+		for (int i = 0; i < path.get_name_count(); i++) {
+			String name = path.get_name(i);
+			if (accum != String()) {
+				accum += "/";
+			}
+			accum += name;
+			if (!parenthood.has(accum)) {
+				if (ti) {
+					ti = filters->create_item(ti);
+				} else {
+					ti = filters->create_item(root);
+				}
+				parenthood[accum] = ti;
+				ti->set_text(0, name);
+				ti->set_selectable(0, false);
+				ti->set_editable(0, false);
+
+				if (base->has_node(accum)) {
+					Node *node = base->get_node(accum);
+					if (has_icon(node->get_class(), "EditorIcons")) {
+						ti->set_icon(0, get_icon(node->get_class(), "EditorIcons"));
+					} else {
+						ti->set_icon(0, get_icon("Node", "EditorIcons"));
+					}
+				}
+
+			} else {
+				ti = parenthood[accum];
+			}
+		}
+
+		Node *node = NULL;
+		if (base->has_node(accum)) {
+			node = base->get_node(accum);
+		}
+		if (!node)
+			continue; //no node, cant edit
+
+		if (path.get_subname_count()) {
+
+			String concat = path.get_concatenated_subnames();
+
+			Skeleton *skeleton = Object::cast_to<Skeleton>(node);
+			if (skeleton && skeleton->find_bone(concat) != -1) {
+				//path in skeleton
+				String bone = concat;
+				int idx = skeleton->find_bone(bone);
+				List<String> bone_path;
+				while (idx != -1) {
+					bone_path.push_front(skeleton->get_bone_name(idx));
+					idx = skeleton->get_bone_parent(idx);
+				}
+
+				accum += ":";
+				for (List<String>::Element *F = bone_path.front(); F; F = F->next()) {
+					if (F != bone_path.front()) {
+						accum += "/";
+					}
+
+					accum += F->get();
+					if (!parenthood.has(accum)) {
+						ti = filters->create_item(ti);
+						parenthood[accum] = ti;
+						ti->set_text(0, F->get());
+						ti->set_selectable(0, false);
+						ti->set_editable(0, false);
+						ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
+					} else {
+						ti = parenthood[accum];
+					}
+				}
+
+				ti->set_editable(0, true);
+				ti->set_selectable(0, true);
+				ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+				ti->set_text(0, concat);
+				ti->set_checked(0, anode->is_path_filtered(path));
+				ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
+				ti->set_metadata(0, path);
+
+			} else {
+				//just a property
+				ti = filters->create_item(ti);
+				ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+				ti->set_text(0, concat);
+				ti->set_editable(0, true);
+				ti->set_selectable(0, true);
+				ti->set_checked(0, anode->is_path_filtered(path));
+				ti->set_metadata(0, path);
+			}
+		} else {
+			if (ti) {
+				//just a node, likely call or animation track
+				ti->set_editable(0, true);
+				ti->set_selectable(0, true);
+				ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
+				ti->set_checked(0, anode->is_path_filtered(path));
+				ti->set_metadata(0, path);
+			}
+		}
+	}
+
+	updating = false;
+
+	return true;
+}
+
+void AnimationNodeBlendTreeEditor::_edit_filters(const String &p_which) {
+
+	Ref<AnimationNode> anode = blend_tree->get_node(p_which);
+	ERR_FAIL_COND(!anode.is_valid());
+
+	_filter_edit = anode;
+	if (!_update_filters(anode))
+		return;
+
+	filter_dialog->popup_centered_minsize(Size2(500, 500) * EDSCALE);
+}
+
+void AnimationNodeBlendTreeEditor::_notification(int p_what) {
+
+	if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
+
+		goto_parent->set_icon(get_icon("MoveUp", "EditorIcons"));
+
+		error_panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+		error_label->add_color_override("font_color", get_color("error_color", "Editor"));
+	}
+
+	if (p_what == NOTIFICATION_PROCESS) {
+
+		String error;
+
+		if (!blend_tree->get_graph_player()) {
+			error = TTR("BlendTree does not belong to an AnimationGraphPlayer node.");
+		} else if (!blend_tree->get_graph_player()->is_active()) {
+			error = TTR("AnimationGraphPlayer is inactive.\nActivate to enable playback, check node warnings if activation fails.");
+		} else if (blend_tree->get_graph_player()->is_state_invalid()) {
+			error = blend_tree->get_graph_player()->get_invalid_state_reason();
+		}
+
+		if (error != error_label->get_text()) {
+			error_label->set_text(error);
+			if (error != String()) {
+				error_panel->show();
+			} else {
+				error_panel->hide();
+			}
+		}
+
+		List<AnimationNodeBlendTree::NodeConnection> conns;
+		blend_tree->get_node_connections(&conns);
+		for (List<AnimationNodeBlendTree::NodeConnection>::Element *E = conns.front(); E; E = E->next()) {
+			float activity = 0;
+			if (blend_tree->get_graph_player() && !blend_tree->get_graph_player()->is_state_invalid()) {
+				activity = blend_tree->get_connection_activity(E->get().input_node, E->get().input_index);
+			}
+			graph->set_connection_activity(E->get().output_node, 0, E->get().input_node, E->get().input_index, activity);
+		}
+
+		AnimationGraphPlayer *graph_player = blend_tree->get_graph_player();
+		AnimationPlayer *player = NULL;
+		if (graph_player->has_node(graph_player->get_animation_player())) {
+			player = Object::cast_to<AnimationPlayer>(graph_player->get_node(graph_player->get_animation_player()));
+		}
+
+		if (player) {
+			for (Map<StringName, ProgressBar *>::Element *E = animations.front(); E; E = E->next()) {
+				Ref<AnimationNodeAnimation> an = blend_tree->get_node(E->key());
+				if (an.is_valid()) {
+					if (player->has_animation(an->get_animation())) {
+						Ref<Animation> anim = player->get_animation(an->get_animation());
+						if (anim.is_valid()) {
+							E->get()->set_max(anim->get_length());
+							E->get()->set_value(an->get_playback_time());
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+void AnimationNodeBlendTreeEditor::_scroll_changed(const Vector2 &p_scroll) {
+	if (updating)
+		return;
+	updating = true;
+	blend_tree->set_graph_offset(p_scroll / EDSCALE);
+	updating = false;
+}
+
+void AnimationNodeBlendTreeEditor::_node_changed(ObjectID p_node) {
+
+	AnimationNode *an = Object::cast_to<AnimationNode>(ObjectDB::get_instance(p_node));
+	if (an && an->get_tree() == blend_tree) {
+		_update_graph();
+	}
+}
+
+void AnimationNodeBlendTreeEditor::_bind_methods() {
+
+	ClassDB::bind_method("_update_graph", &AnimationNodeBlendTreeEditor::_update_graph);
+	ClassDB::bind_method("_add_node", &AnimationNodeBlendTreeEditor::_add_node);
+	ClassDB::bind_method("_node_dragged", &AnimationNodeBlendTreeEditor::_node_dragged);
+	ClassDB::bind_method("_node_renamed", &AnimationNodeBlendTreeEditor::_node_renamed);
+	ClassDB::bind_method("_node_renamed_focus_out", &AnimationNodeBlendTreeEditor::_node_renamed_focus_out);
+	ClassDB::bind_method("_connection_request", &AnimationNodeBlendTreeEditor::_connection_request);
+	ClassDB::bind_method("_disconnection_request", &AnimationNodeBlendTreeEditor::_disconnection_request);
+	ClassDB::bind_method("_node_selected", &AnimationNodeBlendTreeEditor::_node_selected);
+	ClassDB::bind_method("_open_in_editor", &AnimationNodeBlendTreeEditor::_open_in_editor);
+	ClassDB::bind_method("_open_parent", &AnimationNodeBlendTreeEditor::_open_parent);
+	ClassDB::bind_method("_scroll_changed", &AnimationNodeBlendTreeEditor::_scroll_changed);
+	ClassDB::bind_method("_delete_request", &AnimationNodeBlendTreeEditor::_delete_request);
+	ClassDB::bind_method("_edit_filters", &AnimationNodeBlendTreeEditor::_edit_filters);
+	ClassDB::bind_method("_update_filters", &AnimationNodeBlendTreeEditor::_update_filters);
+	ClassDB::bind_method("_filter_edited", &AnimationNodeBlendTreeEditor::_filter_edited);
+	ClassDB::bind_method("_filter_toggled", &AnimationNodeBlendTreeEditor::_filter_toggled);
+	ClassDB::bind_method("_oneshot_start", &AnimationNodeBlendTreeEditor::_oneshot_start);
+	ClassDB::bind_method("_oneshot_stop", &AnimationNodeBlendTreeEditor::_oneshot_stop);
+	ClassDB::bind_method("_node_changed", &AnimationNodeBlendTreeEditor::_node_changed);
+
+	ClassDB::bind_method("_anim_selected", &AnimationNodeBlendTreeEditor::_anim_selected);
+}
+
+AnimationNodeBlendTreeEditor *AnimationNodeBlendTreeEditor::singleton = NULL;
+
+void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<AnimationNode> p_node) {
+
+	String prev_name = blend_tree->get_node_name(p_node);
+	ERR_FAIL_COND(prev_name == String());
+	GraphNode *gn = Object::cast_to<GraphNode>(graph->get_node(prev_name));
+	ERR_FAIL_COND(!gn);
+
+	String new_name = p_text;
+
+	ERR_FAIL_COND(new_name == "" || new_name.find(".") != -1 || new_name.find("/") != -1)
+
+	ERR_FAIL_COND(new_name == prev_name);
+
+	String base_name = new_name;
+	int base = 1;
+	String name = base_name;
+	while (blend_tree->has_node(name)) {
+		base++;
+		name = base_name + " " + itos(base);
+	}
+
+	updating = true;
+	undo_redo->create_action("Node Renamed");
+	undo_redo->add_do_method(blend_tree, "rename_node", prev_name, name);
+	undo_redo->add_undo_method(blend_tree, "rename_node", name, prev_name);
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->commit_action();
+	updating = false;
+	gn->set_name(new_name);
+	gn->set_size(gn->get_minimum_size());
+}
+
+void AnimationNodeBlendTreeEditor::_node_renamed_focus_out(Node *le, Ref<AnimationNode> p_node) {
+	_node_renamed(le->call("get_text"), p_node);
+}
+
+AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() {
+
+	singleton = this;
+	updating = false;
+
+	blend_tree = NULL;
+	graph = memnew(GraphEdit);
+	add_child(graph);
+	graph->add_valid_right_disconnect_type(0);
+	graph->add_valid_left_disconnect_type(0);
+	graph->set_v_size_flags(SIZE_EXPAND_FILL);
+	graph->connect("connection_request", this, "_connection_request", varray(), CONNECT_DEFERRED);
+	graph->connect("disconnection_request", this, "_disconnection_request", varray(), CONNECT_DEFERRED);
+	graph->connect("node_selected", this, "_node_selected");
+	graph->connect("scroll_offset_changed", this, "_scroll_changed");
+
+	VSeparator *vs = memnew(VSeparator);
+	graph->get_zoom_hbox()->add_child(vs);
+	graph->get_zoom_hbox()->move_child(vs, 0);
+
+	goto_parent = memnew(Button);
+	goto_parent->set_text(TTR("Goto Parent"));
+	graph->get_zoom_hbox()->add_child(goto_parent);
+	graph->get_zoom_hbox()->move_child(goto_parent, 0);
+	goto_parent->hide();
+	goto_parent->connect("pressed", this, "_open_parent");
+
+	add_node = memnew(MenuButton);
+	graph->get_zoom_hbox()->add_child(add_node);
+	add_node->set_text(TTR("Add Node.."));
+	graph->get_zoom_hbox()->move_child(add_node, 0);
+	add_node->get_popup()->connect("index_pressed", this, "_add_node");
+
+	add_options.push_back(AddOption("Animation", "AnimationNodeAnimation"));
+	add_options.push_back(AddOption("OneShot", "AnimationNodeOneShot"));
+	add_options.push_back(AddOption("Add", "AnimationNodeAdd"));
+	add_options.push_back(AddOption("Blend2", "AnimationNodeBlend2"));
+	add_options.push_back(AddOption("Blend3", "AnimationNodeBlend3"));
+	add_options.push_back(AddOption("Seek", "AnimationNodeTimeSeek"));
+	add_options.push_back(AddOption("TimeScale", "AnimationNodeTimeScale"));
+	add_options.push_back(AddOption("Transition", "AnimationNodeTransition"));
+	add_options.push_back(AddOption("BlendTree", "AnimationNodeBlendTree"));
+	_update_options_menu();
+
+	error_panel = memnew(PanelContainer);
+	add_child(error_panel);
+	error_label = memnew(Label);
+	error_panel->add_child(error_label);
+	error_label->set_text("eh");
+
+	filter_dialog = memnew(AcceptDialog);
+	add_child(filter_dialog);
+	filter_dialog->set_title(TTR("Edit Filtered Tracks:"));
+
+	VBoxContainer *filter_vbox = memnew(VBoxContainer);
+	filter_dialog->add_child(filter_vbox);
+
+	filter_enabled = memnew(CheckBox);
+	filter_enabled->set_text(TTR("Enable filtering"));
+	filter_enabled->connect("pressed", this, "_filter_toggled");
+	filter_vbox->add_child(filter_enabled);
+
+	filters = memnew(Tree);
+	filter_vbox->add_child(filters);
+	filters->set_v_size_flags(SIZE_EXPAND_FILL);
+	filters->set_hide_root(true);
+	filters->connect("item_edited", this, "_filter_edited");
+
+	undo_redo = EditorNode::get_singleton()->get_undo_redo();
+}
+
+void AnimationNodeBlendTreeEditorPlugin::edit(Object *p_object) {
+
+	anim_tree_editor->edit(Object::cast_to<AnimationNodeBlendTree>(p_object));
+}
+
+bool AnimationNodeBlendTreeEditorPlugin::handles(Object *p_object) const {
+
+	return p_object->is_class("AnimationNodeBlendTree");
+}
+
+void AnimationNodeBlendTreeEditorPlugin::make_visible(bool p_visible) {
+
+	if (p_visible) {
+		//editor->hide_animation_player_editors();
+		//editor->animation_panel_make_visible(true);
+		button->show();
+		editor->make_bottom_panel_item_visible(anim_tree_editor);
+		anim_tree_editor->set_process(true);
+	} else {
+
+		if (anim_tree_editor->is_visible_in_tree())
+			editor->hide_bottom_panel();
+		button->hide();
+		anim_tree_editor->set_process(false);
+	}
+}
+
+AnimationNodeBlendTreeEditorPlugin::AnimationNodeBlendTreeEditorPlugin(EditorNode *p_node) {
+
+	editor = p_node;
+	anim_tree_editor = memnew(AnimationNodeBlendTreeEditor);
+	anim_tree_editor->set_custom_minimum_size(Size2(0, 300));
+
+	button = editor->add_bottom_panel_item(TTR("BlendTree"), anim_tree_editor);
+	button->hide();
+}
+
+AnimationNodeBlendTreeEditorPlugin::~AnimationNodeBlendTreeEditorPlugin() {
+}

+ 115 - 0
editor/plugins/animation_blend_tree_editor_plugin.h

@@ -0,0 +1,115 @@
+#ifndef ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H
+#define ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H
+
+#include "editor/editor_node.h"
+#include "editor/editor_plugin.h"
+#include "editor/property_editor.h"
+#include "scene/animation/animation_blend_tree.h"
+#include "scene/gui/button.h"
+#include "scene/gui/graph_edit.h"
+#include "scene/gui/popup.h"
+#include "scene/gui/tree.h"
+/**
+	@author Juan Linietsky <[email protected]>
+*/
+
+class AnimationNodeBlendTreeEditor : public VBoxContainer {
+
+	GDCLASS(AnimationNodeBlendTreeEditor, VBoxContainer);
+
+	AnimationNodeBlendTree *blend_tree;
+	GraphEdit *graph;
+	MenuButton *add_node;
+	Button *goto_parent;
+
+	PanelContainer *error_panel;
+	Label *error_label;
+
+	UndoRedo *undo_redo;
+
+	AcceptDialog *filter_dialog;
+	Tree *filters;
+	CheckBox *filter_enabled;
+
+	Map<StringName, ProgressBar *> animations;
+
+	void _update_graph();
+
+	struct AddOption {
+		String name;
+		String type;
+		Ref<Script> script;
+		AddOption(const String &p_name = String(), const String &p_type = String()) {
+			name = p_name;
+			type = p_type;
+		}
+	};
+
+	Vector<AddOption> add_options;
+
+	void _add_node(int p_idx);
+	void _update_options_menu();
+
+	static AnimationNodeBlendTreeEditor *singleton;
+
+	void _node_dragged(const Vector2 &p_from, const Vector2 &p_to, Ref<AnimationNode> p_node);
+	void _node_renamed(const String &p_text, Ref<AnimationNode> p_node);
+	void _node_renamed_focus_out(Node *le, Ref<AnimationNode> p_node);
+
+	bool updating;
+
+	void _connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index);
+	void _disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index);
+
+	void _scroll_changed(const Vector2 &p_scroll);
+	void _node_selected(Object *p_node);
+	void _open_in_editor(const String &p_which);
+	void _open_parent();
+	void _anim_selected(int p_index, Array p_options, const String &p_node);
+	void _delete_request(const String &p_which);
+	void _oneshot_start(const StringName &p_name);
+	void _oneshot_stop(const StringName &p_name);
+
+	bool _update_filters(const Ref<AnimationNode> &anode);
+	void _edit_filters(const String &p_which);
+	void _filter_edited();
+	void _filter_toggled();
+	Ref<AnimationNode> _filter_edit;
+
+	void _node_changed(ObjectID p_node);
+
+protected:
+	void _notification(int p_what);
+	static void _bind_methods();
+
+public:
+	static AnimationNodeBlendTreeEditor *get_singleton() { return singleton; }
+
+	void add_custom_type(const String &p_name, const Ref<Script> &p_script);
+	void remove_custom_type(const Ref<Script> &p_script);
+
+	virtual Size2 get_minimum_size() const;
+	void edit(AnimationNodeBlendTree *p_blend_tree);
+	AnimationNodeBlendTreeEditor();
+};
+
+class AnimationNodeBlendTreeEditorPlugin : public EditorPlugin {
+
+	GDCLASS(AnimationNodeBlendTreeEditorPlugin, EditorPlugin);
+
+	AnimationNodeBlendTreeEditor *anim_tree_editor;
+	EditorNode *editor;
+	Button *button;
+
+public:
+	virtual String get_name() const { return "BlendTree"; }
+	bool has_main_screen() const { return false; }
+	virtual void edit(Object *p_object);
+	virtual bool handles(Object *p_object) const;
+	virtual void make_visible(bool p_visible);
+
+	AnimationNodeBlendTreeEditorPlugin(EditorNode *p_node);
+	~AnimationNodeBlendTreeEditorPlugin();
+};
+
+#endif // ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H

+ 1105 - 0
scene/animation/animation_blend_tree.cpp

@@ -0,0 +1,1105 @@
+#include "animation_blend_tree.h"
+#include "scene/scene_string_names.h"
+
+void AnimationNodeAnimation::set_animation(const StringName &p_name) {
+	animation = p_name;
+}
+
+StringName AnimationNodeAnimation::get_animation() const {
+	return animation;
+}
+
+float AnimationNodeAnimation::get_playback_time() const {
+	return time;
+}
+
+void AnimationNodeAnimation::_validate_property(PropertyInfo &property) const {
+
+	if (property.name == "animation") {
+		AnimationGraphPlayer *gp = get_graph_player();
+		if (gp && gp->has_node(gp->get_animation_player())) {
+			AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player()));
+			if (ap) {
+				List<StringName> names;
+				ap->get_animation_list(&names);
+				String anims;
+				for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+					if (E != names.front()) {
+						anims += ",";
+					}
+					anims += String(E->get());
+				}
+				if (anims != String()) {
+					property.hint = PROPERTY_HINT_ENUM;
+					property.hint_string = anims;
+				}
+			}
+		}
+	}
+}
+
+float AnimationNodeAnimation::process(float p_time, bool p_seek) {
+
+	AnimationPlayer *ap = get_player();
+	ERR_FAIL_COND_V(!ap, 0);
+
+	Ref<Animation> anim = ap->get_animation(animation);
+	if (!anim.is_valid()) {
+
+		if (get_tree().is_valid()) {
+			String name = get_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;
+	}
+
+	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::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;
+}
+
+void AnimationNodeOneShot::start() {
+	active = true;
+	do_start = true;
+}
+void AnimationNodeOneShot::stop() {
+	active = false;
+}
+bool AnimationNodeOneShot::is_active() const {
+
+	return active;
+}
+
+String AnimationNodeOneShot::get_caption() const {
+	return "OneShot";
+}
+
+bool AnimationNodeOneShot::has_filter() const {
+	return true;
+}
+
+float AnimationNodeOneShot::process(float p_time, bool p_seek) {
+
+	if (!active) {
+		//make it as if this node doesn't exist, pass input 0 by.
+		return blend_input(0, p_time, p_seek, 1.0, FILTER_IGNORE, !sync);
+	}
+
+	bool os_seek = p_seek;
+
+	if (p_seek)
+		time = p_time;
+	if (do_start) {
+		time = 0;
+		os_seek = 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)
+			active = false;
+	}
+
+	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("start"), &AnimationNodeOneShot::start);
+	ClassDB::bind_method(D_METHOD("stop"), &AnimationNodeOneShot::stop);
+	ClassDB::bind_method(D_METHOD("is_active"), &AnimationNodeOneShot::is_active);
+
+	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_CONSTANT(MIX_MODE_BLEND)
+	BIND_CONSTANT(MIX_MODE_ADD)
+}
+
+AnimationNodeOneShot::AnimationNodeOneShot() {
+
+	add_input("in");
+	add_input("shot");
+
+	time = 0;
+	fade_in = 0.1;
+	fade_out = 0.1;
+	autorestart = false;
+	autorestart_delay = 1;
+	autorestart_remaining = 0;
+	mix = MIX_MODE_BLEND;
+	active = false;
+	do_start = false;
+	sync = false;
+}
+
+////////////////////////////////////////////////
+
+void AnimationNodeAdd::set_amount(float p_amount) {
+	amount = p_amount;
+}
+
+float AnimationNodeAdd::get_amount() const {
+	return amount;
+}
+
+String AnimationNodeAdd::get_caption() const {
+	return "Add";
+}
+void AnimationNodeAdd::set_use_sync(bool p_sync) {
+
+	sync = p_sync;
+}
+
+bool AnimationNodeAdd::is_using_sync() const {
+
+	return sync;
+}
+
+bool AnimationNodeAdd::has_filter() const {
+
+	return true;
+}
+
+float AnimationNodeAdd::process(float p_time, bool p_seek) {
+
+	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 AnimationNodeAdd::_bind_methods() {
+
+	ClassDB::bind_method(D_METHOD("set_amount", "amount"), &AnimationNodeAdd::set_amount);
+	ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeAdd::get_amount);
+
+	ClassDB::bind_method(D_METHOD("set_use_sync", "enable"), &AnimationNodeAdd::set_use_sync);
+	ClassDB::bind_method(D_METHOD("is_using_sync"), &AnimationNodeAdd::is_using_sync);
+
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_amount", "get_amount");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
+}
+
+AnimationNodeAdd::AnimationNodeAdd() {
+
+	add_input("in");
+	add_input("add");
+	amount = 0;
+	sync = false;
+}
+
+/////////////////////////////////////////////
+
+void AnimationNodeBlend2::set_amount(float p_amount) {
+	amount = p_amount;
+}
+
+float AnimationNodeBlend2::get_amount() const {
+	return amount;
+}
+String AnimationNodeBlend2::get_caption() const {
+	return "Blend2";
+}
+
+float AnimationNodeBlend2::process(float p_time, bool p_seek) {
+
+	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_amount", "amount"), &AnimationNodeBlend2::set_amount);
+	ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeBlend2::get_amount);
+
+	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::REAL, "amount", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_amount", "get_amount");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
+}
+AnimationNodeBlend2::AnimationNodeBlend2() {
+	add_input("in");
+	add_input("blend");
+	sync = false;
+
+	amount = 0;
+}
+
+//////////////////////////////////////
+
+void AnimationNodeBlend3::set_amount(float p_amount) {
+	amount = p_amount;
+}
+
+float AnimationNodeBlend3::get_amount() const {
+	return 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 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_amount", "amount"), &AnimationNodeBlend3::set_amount);
+	ClassDB::bind_method(D_METHOD("get_amount"), &AnimationNodeBlend3::get_amount);
+
+	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::REAL, "amount", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_amount", "get_amount");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sync"), "set_use_sync", "is_using_sync");
+}
+AnimationNodeBlend3::AnimationNodeBlend3() {
+	add_input("-blend");
+	add_input("in");
+	add_input("+blend");
+	sync = false;
+	amount = 0;
+}
+
+/////////////////////////////////
+
+void AnimationNodeTimeScale::set_scale(float p_scale) {
+	scale = p_scale;
+}
+
+float AnimationNodeTimeScale::get_scale() const {
+	return scale;
+}
+
+String AnimationNodeTimeScale::get_caption() const {
+	return "TimeScale";
+}
+
+float AnimationNodeTimeScale::process(float p_time, bool p_seek) {
+
+	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() {
+
+	ClassDB::bind_method(D_METHOD("set_scale", "scale"), &AnimationNodeTimeScale::set_scale);
+	ClassDB::bind_method(D_METHOD("get_scale"), &AnimationNodeTimeScale::get_scale);
+
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "scale", PROPERTY_HINT_RANGE, "0,32,0.01,or_greater"), "set_scale", "get_scale");
+}
+AnimationNodeTimeScale::AnimationNodeTimeScale() {
+	add_input("in");
+	scale = 1.0;
+}
+
+////////////////////////////////////
+
+void AnimationNodeTimeSeek::set_seek_pos(float p_seek_pos) {
+	seek_pos = p_seek_pos;
+}
+
+float AnimationNodeTimeSeek::get_seek_pos() const {
+	return seek_pos;
+}
+
+String AnimationNodeTimeSeek::get_caption() const {
+	return "Seek";
+}
+
+float AnimationNodeTimeSeek::process(float p_time, bool p_seek) {
+
+	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);
+		seek_pos = -1;
+		_change_notify("seek_pos");
+		return ret;
+	} else {
+		return blend_input(0, p_time, false, 1.0, FILTER_IGNORE, false);
+	}
+}
+
+void AnimationNodeTimeSeek::_bind_methods() {
+
+	ClassDB::bind_method(D_METHOD("set_seek_pos", "seek_pos"), &AnimationNodeTimeSeek::set_seek_pos);
+	ClassDB::bind_method(D_METHOD("get_seek_pos"), &AnimationNodeTimeSeek::get_seek_pos);
+
+	ADD_PROPERTY(PropertyInfo(Variant::REAL, "seek_pos", PROPERTY_HINT_RANGE, "-1,3600,0.01,or_greater"), "set_seek_pos", "get_seek_pos");
+}
+AnimationNodeTimeSeek::AnimationNodeTimeSeek() {
+	add_input("in");
+	seek_pos = -1;
+}
+
+/////////////////////////////////////////////////
+
+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;
+}
+
+void AnimationNodeTransition::set_current(int p_current) {
+
+	if (current == p_current)
+		return;
+	ERR_FAIL_INDEX(p_current, enabled_inputs);
+
+	if (get_tree().is_valid() && current >= 0) {
+		prev = current;
+		prev_xfading = xfade;
+		prev_time = time;
+		time = 0;
+		current = p_current;
+		switched = true;
+		_change_notify("current");
+	} else {
+		current = p_current;
+	}
+}
+
+int AnimationNodeTransition::get_current() const {
+	return current;
+}
+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) {
+
+	if (prev < 0) { // process current animation, check for transition
+
+		float 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_current((current + 1) % enabled_inputs);
+		}
+
+		return rem;
+	} else { // cross-fading from prev to current
+
+		float blend = xfade ? (prev_xfading / xfade) : 1;
+
+		float rem;
+
+		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) {
+				prev = -1;
+			}
+		}
+
+		return rem;
+	}
+}
+
+void AnimationNodeTransition::_validate_property(PropertyInfo &property) const {
+
+	if (property.name == "current" && enabled_inputs > 0) {
+		property.hint = PROPERTY_HINT_ENUM;
+		String anims;
+		for (int i = 0; i < enabled_inputs; i++) {
+			if (i > 0) {
+				anims += ",";
+			}
+			anims += inputs[i].name;
+		}
+		property.hint_string = anims;
+	}
+
+	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;
+			}
+		}
+	}
+}
+
+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_current", "index"), &AnimationNodeTransition::set_current);
+	ClassDB::bind_method(D_METHOD("get_current"), &AnimationNodeTransition::get_current);
+
+	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::INT, "current", PROPERTY_HINT_RANGE, "0,64,1"), "set_current", "get_current");
+	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() {
+	enabled_inputs = 0;
+	xfade = 0;
+	current = -1;
+	prev = -1;
+	prev_time = 0;
+	prev_xfading = 0;
+	switched = false;
+	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) {
+
+	ERR_FAIL_COND(nodes.has(p_name));
+	ERR_FAIL_COND(p_node.is_null());
+	ERR_FAIL_COND(p_node->tree != NULL);
+	ERR_FAIL_COND(p_node->player != NULL);
+	ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output);
+	ERR_FAIL_COND(String(p_name).find("/") != -1);
+	nodes[p_name] = p_node;
+
+	p_node->tree = this;
+	p_node->player = player;
+
+	emit_changed();
+}
+
+Ref<AnimationNode> AnimationNodeBlendTree::get_node(const StringName &p_name) const {
+
+	ERR_FAIL_COND_V(!nodes.has(p_name), Ref<AnimationNode>());
+
+	return nodes[p_name];
+}
+
+StringName AnimationNodeBlendTree::get_node_name(const Ref<AnimationNode> &p_node) const {
+	for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+		if (E->get() == p_node) {
+			return E->key();
+		}
+	}
+
+	ERR_FAIL_V(StringName());
+}
+bool AnimationNodeBlendTree::has_node(const StringName &p_name) const {
+	return nodes.has(p_name);
+}
+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
+
+	{
+		//erase node connections
+		Ref<AnimationNode> node = nodes[p_name];
+		for (int i = 0; i < node->get_input_count(); i++) {
+			node->set_input_connection(i, StringName());
+		}
+		node->tree = NULL;
+		node->player = NULL;
+	}
+
+	nodes.erase(p_name);
+
+	//erase connections to name
+	for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+		Ref<AnimationNode> node = E->get();
+		for (int i = 0; i < node->get_input_count(); i++) {
+			if (node->get_input_connection(i) == p_name) {
+				node->set_input_connection(i, StringName());
+			}
+		}
+	}
+
+	emit_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_new_name] = nodes[p_name];
+	nodes.erase(p_name);
+
+	//rename connections
+	for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+		Ref<AnimationNode> node = E->get();
+		for (int i = 0; i < node->get_input_count(); i++) {
+			if (node->get_input_connection(i) == p_name) {
+				node->set_input_connection(i, p_new_name);
+			}
+		}
+	}
+}
+
+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];
+	ERR_FAIL_INDEX(p_input_index, input->get_input_count());
+
+	for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+		Ref<AnimationNode> node = E->get();
+		for (int i = 0; i < node->get_input_count(); i++) {
+			StringName output = node->get_input_connection(i);
+			ERR_FAIL_COND(output == p_output_node);
+		}
+	}
+
+	input->set_input_connection(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];
+	ERR_FAIL_INDEX(p_input_index, input->get_input_count());
+
+	input->set_input_connection(p_input_index, StringName());
+}
+
+float AnimationNodeBlendTree::get_connection_activity(const StringName &p_input_node, int p_input_index) const {
+
+	ERR_FAIL_COND_V(!nodes.has(p_input_node), 0);
+
+	Ref<AnimationNode> input = nodes[p_input_node];
+	ERR_FAIL_INDEX_V(p_input_index, input->get_input_count(), 0);
+
+	return input->get_input_activity(p_input_index);
+}
+
+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];
+
+	if (p_input_index < 0 || p_input_index >= input->get_input_count()) {
+		return CONNECTION_ERROR_NO_INPUT_INDEX;
+	}
+
+	if (input->get_input_connection(p_input_index) != StringName()) {
+		return CONNECTION_ERROR_CONNECTION_EXISTS;
+	}
+
+	for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+		Ref<AnimationNode> node = E->get();
+		for (int i = 0; i < node->get_input_count(); i++) {
+			StringName output = node->get_input_connection(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, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+		Ref<AnimationNode> node = E->get();
+		for (int i = 0; i < node->get_input_count(); i++) {
+			StringName output = node->get_input_connection(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];
+	return blend_node(output, p_time, p_seek, 1.0);
+}
+
+void AnimationNodeBlendTree::get_node_list(List<StringName> *r_list) {
+
+	for (Map<StringName, Ref<AnimationNode> >::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;
+}
+
+void AnimationNodeBlendTree::set_graph_player(AnimationGraphPlayer *p_player) {
+
+	AnimationNode::set_graph_player(p_player);
+
+	for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+		Ref<AnimationNode> node = E->get();
+		node->set_graph_player(p_player);
+	}
+}
+
+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]->set_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];
+				return true;
+			}
+		}
+
+		if (what == "position") {
+
+			if (nodes.has(node_name)) {
+				r_ret = nodes[node_name]->get_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, Ref<AnimationNode> >::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 | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
+		}
+		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::_bind_methods() {
+
+	ClassDB::bind_method(D_METHOD("add_node", "name", "node"), &AnimationNodeBlendTree::add_node);
+	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_graph_offset", "offset"), &AnimationNodeBlendTree::set_graph_offset);
+	ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeBlendTree::get_graph_offset);
+
+	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();
+	output->set_position(Vector2(300, 150));
+	output->tree = this;
+	nodes["output"] = output;
+}
+
+AnimationNodeBlendTree::~AnimationNodeBlendTree() {
+
+	for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
+		E->get()->tree = NULL;
+		E->get()->player = NULL;
+	}
+}

+ 328 - 0
scene/animation/animation_blend_tree.h

@@ -0,0 +1,328 @@
+#ifndef ANIMATION_BLEND_TREE_H
+#define ANIMATION_BLEND_TREE_H
+
+#include "scene/animation/animation_graph_player.h"
+
+class AnimationNodeAnimation : public AnimationNode {
+
+	GDCLASS(AnimationNodeAnimation, AnimationNode);
+
+	StringName animation;
+
+	uint64_t last_version;
+	float time;
+	float step;
+	bool skip;
+
+protected:
+	void _validate_property(PropertyInfo &property) const;
+
+	static void _bind_methods();
+
+public:
+	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:
+	bool active;
+	bool do_start;
+	float fade_in;
+	float fade_out;
+
+	bool autorestart;
+	float autorestart_delay;
+	float autorestart_random_delay;
+	MixMode mix;
+
+	float time;
+	float remaining;
+	float autorestart_remaining;
+	bool sync;
+
+protected:
+	static void _bind_methods();
+
+public:
+	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 start();
+	void stop();
+	bool is_active() 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 AnimationNodeAdd : public AnimationNode {
+	GDCLASS(AnimationNodeAdd, AnimationNode);
+
+	float amount;
+	bool sync;
+
+protected:
+	static void _bind_methods();
+
+public:
+	virtual String get_caption() const;
+
+	void set_amount(float p_amount);
+	float get_amount() 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);
+
+	AnimationNodeAdd();
+};
+
+class AnimationNodeBlend2 : public AnimationNode {
+	GDCLASS(AnimationNodeBlend2, AnimationNode);
+
+	float amount;
+	bool sync;
+
+protected:
+	static void _bind_methods();
+
+public:
+	virtual String get_caption() const;
+	virtual float process(float p_time, bool p_seek);
+
+	void set_amount(float p_amount);
+	float get_amount() const;
+
+	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);
+
+	float amount;
+	bool sync;
+
+protected:
+	static void _bind_methods();
+
+public:
+	virtual String get_caption() const;
+
+	void set_amount(float p_amount);
+	float get_amount() 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);
+
+	float scale;
+
+protected:
+	static void _bind_methods();
+
+public:
+	virtual String get_caption() const;
+
+	void set_scale(float p_scale);
+	float get_scale() const;
+
+	float process(float p_time, bool p_seek);
+
+	AnimationNodeTimeScale();
+};
+
+class AnimationNodeTimeSeek : public AnimationNode {
+	GDCLASS(AnimationNodeTimeSeek, AnimationNode);
+
+	float seek_pos;
+
+protected:
+	static void _bind_methods();
+
+public:
+	virtual String get_caption() const;
+
+	void set_seek_pos(float p_sec);
+	float get_seek_pos() 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_time;
+	float prev_xfading;
+	int prev;
+	bool switched;
+
+	float time;
+	int current;
+
+	float xfade;
+
+	void _update_inputs();
+
+protected:
+	static void _bind_methods();
+	void _validate_property(PropertyInfo &property) const;
+
+public:
+	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_current(int p_current);
+	int get_current() 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 AnimationNode {
+	GDCLASS(AnimationNodeBlendTree, AnimationNode)
+
+	Map<StringName, Ref<AnimationNode> > nodes;
+
+	Vector2 graph_offset;
+
+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);
+	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 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);
+	float get_connection_activity(const StringName &p_input_node, int p_input_index) const;
+
+	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 void set_graph_player(AnimationGraphPlayer *p_player);
+	AnimationNodeBlendTree();
+	~AnimationNodeBlendTree();
+};
+
+VARIANT_ENUM_CAST(AnimationNodeBlendTree::ConnectionError)
+
+#endif // ANIMATION_BLEND_TREE_H

+ 1194 - 0
scene/animation/animation_graph_player.cpp

@@ -0,0 +1,1194 @@
+#include "animation_graph_player.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::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()) {
+
+		String name = get_tree()->get_node_name(Ref<AnimationNodeAnimation>(this));
+		make_invalid(vformat(RTR("In node '%s', invalid animation: '%s'."), name, 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(State *p_state, float p_time, bool p_seek) {
+	state = p_state;
+	float t = process(p_time, p_seek);
+	state = NULL;
+	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);
+	ERR_FAIL_COND_V(!get_graph_player(), 0); //should not happen, but used to catch bugs
+
+	if (!tree && get_graph_player()->get_graph_root().ptr() != this) {
+		make_invalid(RTR("Can't blend input because node is not in a tree"));
+		return 0;
+	}
+
+	ERR_FAIL_COND_V(!tree, 0); //should not happen
+
+	StringName anim_name = inputs[p_input].connected_to;
+
+	Ref<AnimationNode> node = tree->get_node(anim_name);
+
+	if (node.is_null()) {
+
+		String name = get_tree()->get_node_name(Ref<AnimationNodeAnimation>(this));
+		make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), name));
+		return 0;
+	}
+
+	inputs[p_input].last_pass = state->last_pass;
+
+	return _blend_node(node, p_time, p_seek, p_blend, p_filter, p_optimize, &inputs[p_input].activity);
+}
+
+float AnimationNode::blend_node(Ref<AnimationNode> p_node, float p_time, bool p_seek, float p_blend, FilterAction p_filter, bool p_optimize) {
+
+	return _blend_node(p_node, p_time, p_seek, p_blend, p_filter, p_optimize);
+}
+
+float AnimationNode::_blend_node(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;
+
+	return p_node->_pre_process(state, p_time, p_seek);
+}
+
+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;
+}
+
+float AnimationNode::get_input_activity(int p_input) const {
+
+	ERR_FAIL_INDEX_V(p_input, inputs.size(), 0);
+	if (!get_graph_player())
+		return 0;
+
+	if (get_graph_player()->get_last_process_pass() != inputs[p_input].last_pass) {
+		return 0;
+	}
+	return inputs[p_input].activity;
+}
+StringName AnimationNode::get_input_connection(int p_input) {
+
+	ERR_FAIL_INDEX_V(p_input, inputs.size(), StringName());
+	return inputs[p_input].connected_to;
+}
+
+void AnimationNode::set_input_connection(int p_input, const StringName &p_connection) {
+
+	ERR_FAIL_INDEX(p_input, inputs.size());
+	inputs[p_input].connected_to = p_connection;
+}
+
+String AnimationNode::get_caption() const {
+	return "Node";
+}
+
+void AnimationNode::add_input(const String &p_name) {
+	Input input;
+	ERR_FAIL_COND(p_name.find(".") != -1 || p_name.find("/") != -1);
+	input.name = p_name;
+	input.activity = 0;
+	input.last_pass = 0;
+	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[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();
+}
+
+Ref<AnimationNodeBlendTree> AnimationNode::get_tree() const {
+	if (tree) {
+		return Ref<AnimationNodeBlendTree>(tree);
+	}
+
+	return Ref<AnimationNodeBlendTree>();
+}
+
+AnimationGraphPlayer *AnimationNode::get_graph_player() const {
+
+	return player;
+}
+
+AnimationPlayer *AnimationNode::get_player() const {
+	ERR_FAIL_COND_V(!state, NULL);
+	return state->player;
+}
+
+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;
+}
+
+void AnimationNode::set_position(const Vector2 &p_position) {
+	position = p_position;
+}
+
+Vector2 AnimationNode::get_position() const {
+	return position;
+}
+
+void AnimationNode::set_graph_player(AnimationGraphPlayer *p_player) {
+
+	player = p_player;
+}
+
+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;
+	}
+}
+
+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("get_input_connection", "input"), &AnimationNode::get_input_connection);
+	ClassDB::bind_method(D_METHOD("get_input_activity", "input"), &AnimationNode::get_input_activity);
+
+	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_position", "position"), &AnimationNode::set_position);
+	ClassDB::bind_method(D_METHOD("get_position"), &AnimationNode::get_position);
+
+	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", "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));
+
+	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_ENUM_CONSTANT(FILTER_IGNORE);
+	BIND_ENUM_CONSTANT(FILTER_PASS);
+	BIND_ENUM_CONSTANT(FILTER_STOP);
+	BIND_ENUM_CONSTANT(FILTER_BLEND);
+}
+
+AnimationNode::AnimationNode() {
+
+	state = NULL;
+	tree = NULL;
+	player = NULL;
+	set_local_to_scene(true);
+	filter_enabled = false;
+}
+
+////////////////////
+
+void AnimationGraphPlayer::set_graph_root(const Ref<AnimationNode> &p_root) {
+
+	if (root.is_valid()) {
+		root->set_graph_player(NULL);
+	}
+	if (p_root.is_valid()) {
+		ERR_EXPLAIN("root node already set to another player");
+		ERR_FAIL_COND(p_root->player);
+	}
+	root = p_root;
+
+	if (root.is_valid()) {
+		root->set_graph_player(this);
+	}
+
+	update_configuration_warning();
+}
+
+Ref<AnimationNode> AnimationGraphPlayer::get_graph_root() const {
+	return root;
+}
+
+void AnimationGraphPlayer::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 AnimationGraphPlayer::is_active() const {
+
+	return active;
+}
+
+void AnimationGraphPlayer::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);
+	}
+}
+
+AnimationGraphPlayer::AnimationProcessMode AnimationGraphPlayer::get_process_mode() const {
+	return process_mode;
+}
+
+void AnimationGraphPlayer::_node_removed(Node *p_node) {
+	cache_valid = false;
+}
+
+bool AnimationGraphPlayer::_update_caches(AnimationPlayer *player) {
+
+	setup_pass++;
+
+	if (!player->has_node(player->get_root())) {
+		ERR_PRINT("AnimationGraphPlayer: 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("AnimationGraphPlayer: '" + 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("AnimationGraphPlayer: '" + 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 AnimationGraphPlayer::_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 AnimationGraphPlayer::_process_graph(float p_delta) {
+
+	//check all tracks, see if they need modification
+
+	if (!root.is_valid()) {
+		ERR_PRINT("AnimationGraphPlayer: root AnimationNode is not set, disabling playback.");
+		set_active(false);
+		cache_valid = false;
+		return;
+	}
+
+	if (!has_node(animation_player)) {
+		ERR_PRINT("AnimationGraphPlayer: no valid AnimationPlayer path set, disabling playback");
+		set_active(false);
+		cache_valid = false;
+		return;
+	}
+
+	AnimationPlayer *player = Object::cast_to<AnimationPlayer>(get_node(animation_player));
+
+	if (!player) {
+		ERR_PRINT("AnimationGraphPlayer: 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;
+
+		// 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(&state, 0, true);
+			started = false;
+		}
+
+		root->_pre_process(&state, p_delta, false);
+	}
+
+	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
+				}
+
+				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);
+
+						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;
+
+						if (t->process_pass != process_pass) {
+
+							t->process_pass = process_pass;
+							t->loc = Vector3();
+							t->rot = Quat();
+							t->scale = Vector3();
+						}
+
+						t->loc = t->loc.linear_interpolate(loc, blend);
+						t->rot = t->rot.slerp(rot, blend);
+						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) {
+								if (t->start > time || (t->len > 0 && time - t->start < t->len)) {
+									//time to stop
+									t->object->call("stop");
+									t->playing = false;
+									playing_caches.erase(t);
+								}
+							}
+						}
+
+					} 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
+
+					xform.basis.set_quat_scale(t->rot, t->scale);
+
+					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 AnimationGraphPlayer::_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();
+	}
+}
+
+void AnimationGraphPlayer::set_animation_player(const NodePath &p_player) {
+	animation_player = p_player;
+	update_configuration_warning();
+}
+
+NodePath AnimationGraphPlayer::get_animation_player() const {
+	return animation_player;
+}
+
+bool AnimationGraphPlayer::is_state_invalid() const {
+
+	return !state.valid;
+}
+String AnimationGraphPlayer::get_invalid_state_reason() const {
+
+	return state.invalid_reasons;
+}
+
+uint64_t AnimationGraphPlayer::get_last_process_pass() const {
+	return process_pass;
+}
+
+String AnimationGraphPlayer::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 AnimationGraphPlayer::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("set_active", "active"), &AnimationGraphPlayer::set_active);
+	ClassDB::bind_method(D_METHOD("is_active"), &AnimationGraphPlayer::is_active);
+
+	ClassDB::bind_method(D_METHOD("set_graph_root", "root"), &AnimationGraphPlayer::set_graph_root);
+	ClassDB::bind_method(D_METHOD("get_graph_root"), &AnimationGraphPlayer::get_graph_root);
+
+	ClassDB::bind_method(D_METHOD("set_process_mode", "mode"), &AnimationGraphPlayer::set_process_mode);
+	ClassDB::bind_method(D_METHOD("get_process_mode"), &AnimationGraphPlayer::get_process_mode);
+
+	ClassDB::bind_method(D_METHOD("set_animation_player", "root"), &AnimationGraphPlayer::set_animation_player);
+	ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationGraphPlayer::get_animation_player);
+
+	ClassDB::bind_method(D_METHOD("_node_removed"), &AnimationGraphPlayer::_node_removed);
+
+	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "graph_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNodeBlendTree", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "set_graph_root", "get_graph_root");
+	ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player"), "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"), "set_process_mode", "get_process_mode");
+}
+
+AnimationGraphPlayer::AnimationGraphPlayer() {
+
+	process_mode = ANIMATION_PROCESS_IDLE;
+	active = false;
+	cache_valid = false;
+	setup_pass = 1;
+	started = true;
+}
+
+AnimationGraphPlayer::~AnimationGraphPlayer() {
+	if (root.is_valid()) {
+		root->player = NULL;
+	}
+}

+ 261 - 0
scene/animation/animation_graph_player.h

@@ -0,0 +1,261 @@
+#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 AnimationGraphPlayer;
+
+class AnimationNode : public Resource {
+	GDCLASS(AnimationNode, Resource)
+public:
+	enum FilterAction {
+		FILTER_IGNORE,
+		FILTER_PASS,
+		FILTER_STOP,
+		FILTER_BLEND
+	};
+
+	struct Input {
+
+		String name;
+		StringName connected_to;
+		float activity;
+		uint64_t last_pass;
+	};
+
+	Vector<Input> inputs;
+
+	float process_input(int p_input, float p_time, bool p_seek, float p_blend);
+
+	friend class AnimationGraphPlayer;
+
+	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;
+		String invalid_reasons;
+		uint64_t last_pass;
+	};
+
+	Vector<float> blends;
+	State *state;
+	float _pre_process(State *p_state, float p_time, bool p_seek);
+	void _pre_update_animations(HashMap<NodePath, int> *track_map);
+	Vector2 position;
+
+	friend class AnimationNodeBlendTree;
+	AnimationNodeBlendTree *tree;
+	AnimationGraphPlayer *player;
+
+	float _blend_node(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);
+
+	HashMap<NodePath, bool> filter;
+	bool filter_enabled;
+
+	Array _get_filters() const;
+	void _set_filters(const Array &p_filters);
+
+protected:
+	void blend_animation(const StringName &p_animation, float p_time, float p_delta, bool p_seeked, float p_blend);
+	float blend_node(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;
+
+public:
+	Ref<AnimationNodeBlendTree> get_tree() const;
+	virtual void set_graph_player(AnimationGraphPlayer *p_player);
+	AnimationGraphPlayer *get_graph_player() const;
+	AnimationPlayer *get_player() const;
+
+	virtual float process(float p_time, bool p_seek);
+
+	int get_input_count() const;
+	String get_input_name(int p_input);
+	StringName get_input_connection(int p_input);
+	void set_input_connection(int p_input, const StringName &p_connection);
+	float get_input_activity(int p_input) const;
+
+	void add_input(const String &p_name);
+	void set_input_name(int p_input, const String &p_name);
+	void remove_input(int p_index);
+
+	virtual String get_caption() const;
+
+	void set_filter_path(const NodePath &p_path, bool p_enable);
+	bool is_path_filtered(const NodePath &p_path) const;
+
+	void set_filter_enabled(bool p_enable);
+	bool is_filter_enabled() const;
+
+	virtual bool has_filter() const;
+
+	void set_position(const Vector2 &p_position);
+	Vector2 get_position() const;
+
+	AnimationNode();
+};
+
+VARIANT_ENUM_CAST(AnimationNode::FilterAction)
+
+class AnimationGraphPlayer : public Node {
+	GDCLASS(AnimationGraphPlayer, Node)
+public:
+	enum AnimationProcessMode {
+		ANIMATION_PROCESS_PHYSICS,
+		ANIMATION_PROCESS_IDLE,
+	};
+
+private:
+	struct TrackCache {
+		uint64_t setup_pass;
+		uint64_t process_pass;
+		Animation::TrackType type;
+		Object *object;
+		ObjectID object_id;
+
+		TrackCache() {
+			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;
+		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;
+
+protected:
+	void _notification(int p_what);
+	static void _bind_methods();
+
+public:
+	void set_graph_root(const Ref<AnimationNode> &p_root);
+	Ref<AnimationNode> get_graph_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;
+
+	uint64_t get_last_process_pass() const;
+	AnimationGraphPlayer();
+	~AnimationGraphPlayer();
+};
+
+VARIANT_ENUM_CAST(AnimationGraphPlayer::AnimationProcessMode)
+
+#endif // ANIMATION_GRAPH_PLAYER_H

+ 2 - 0
scene/animation/animation_player.cpp

@@ -1327,6 +1327,7 @@ float AnimationPlayer::get_current_animation_length() const {
 void AnimationPlayer::_animation_changed() {
 
 	clear_caches();
+	emit_signal("caches_cleared");
 }
 
 void AnimationPlayer::_stop_playing_caches() {
@@ -1622,6 +1623,7 @@ 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);

+ 32 - 1
scene/gui/graph_edit.cpp

@@ -58,6 +58,7 @@ Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const S
 	c.from_port = p_from_port;
 	c.to = p_to;
 	c.to_port = p_to_port;
+	c.activity = 0;
 	connections.push_back(c);
 	top_layer->update();
 	update();
@@ -624,6 +625,7 @@ void GraphEdit::_draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const
 
 void GraphEdit::_connections_layer_draw() {
 
+	Color activity_color = get_color("activity");
 	//draw connections
 	List<List<Connection>::Element *> to_erase;
 	for (List<Connection>::Element *E = connections.front(); E; E = E->next()) {
@@ -661,6 +663,11 @@ void GraphEdit::_connections_layer_draw() {
 		Color color = gfrom->get_connection_output_color(E->get().from_port);
 		Vector2 topos = gto->get_connection_input_position(E->get().to_port) + gto->get_offset() * zoom;
 		Color tocolor = gto->get_connection_input_color(E->get().to_port);
+
+		if (E->get().activity > 0) {
+			color = color.linear_interpolate(activity_color, E->get().activity);
+			tocolor = tocolor.linear_interpolate(activity_color, E->get().activity);
+		}
 		_draw_cos_line(connections_layer, frompos, topos, color, tocolor);
 	}
 
@@ -980,6 +987,23 @@ void GraphEdit::_gui_input(const Ref<InputEvent> &p_ev) {
 	}
 }
 
+void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity) {
+
+	for (List<Connection>::Element *E = connections.front(); E; E = E->next()) {
+
+		if (E->get().from == p_from && E->get().from_port == p_from_port && E->get().to == p_to && E->get().to_port == p_to_port) {
+
+			if (ABS(E->get().activity != p_activity)) {
+				//update only if changed
+				top_layer->update();
+				connections_layer->update();
+			}
+			E->get().activity = p_activity;
+			return;
+		}
+	}
+}
+
 void GraphEdit::clear_connections() {
 
 	connections.clear();
@@ -1141,11 +1165,16 @@ void GraphEdit::_snap_value_changed(double) {
 	update();
 }
 
+HBoxContainer *GraphEdit::get_zoom_hbox() {
+	return zoom_hb;
+}
+
 void GraphEdit::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("connect_node", "from", "from_port", "to", "to_port"), &GraphEdit::connect_node);
 	ClassDB::bind_method(D_METHOD("is_node_connected", "from", "from_port", "to", "to_port"), &GraphEdit::is_node_connected);
 	ClassDB::bind_method(D_METHOD("disconnect_node", "from", "from_port", "to", "to_port"), &GraphEdit::disconnect_node);
+	ClassDB::bind_method(D_METHOD("set_connection_activity", "from", "from_port", "to", "to_port", "amount"), &GraphEdit::set_connection_activity);
 	ClassDB::bind_method(D_METHOD("get_connection_list"), &GraphEdit::_get_connection_list);
 	ClassDB::bind_method(D_METHOD("clear_connections"), &GraphEdit::clear_connections);
 	ClassDB::bind_method(D_METHOD("get_scroll_ofs"), &GraphEdit::get_scroll_ofs);
@@ -1187,6 +1216,8 @@ void GraphEdit::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("_update_scroll_offset"), &GraphEdit::_update_scroll_offset);
 	ClassDB::bind_method(D_METHOD("_connections_layer_draw"), &GraphEdit::_connections_layer_draw);
 
+	ClassDB::bind_method(D_METHOD("get_zoom_hbox"), &GraphEdit::get_zoom_hbox);
+
 	ClassDB::bind_method(D_METHOD("set_selected", "node"), &GraphEdit::set_selected);
 
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled");
@@ -1253,7 +1284,7 @@ GraphEdit::GraphEdit() {
 
 	zoom = 1;
 
-	HBoxContainer *zoom_hb = memnew(HBoxContainer);
+	zoom_hb = memnew(HBoxContainer);
 	top_layer->add_child(zoom_hb);
 	zoom_hb->set_position(Vector2(10, 10));
 

+ 8 - 0
scene/gui/graph_edit.h

@@ -31,6 +31,7 @@
 #ifndef GRAPH_EDIT_H
 #define GRAPH_EDIT_H
 
+#include "scene/gui/box_container.h"
 #include "scene/gui/graph_node.h"
 #include "scene/gui/scroll_bar.h"
 #include "scene/gui/slider.h"
@@ -62,6 +63,7 @@ public:
 		StringName to;
 		int from_port;
 		int to_port;
+		float activity;
 	};
 
 private:
@@ -157,6 +159,8 @@ private:
 	Set<int> valid_left_disconnect_types;
 	Set<int> valid_right_disconnect_types;
 
+	HBoxContainer *zoom_hb;
+
 	friend class GraphEditFilter;
 	bool _filter_input(const Point2 &p_point);
 	void _snap_toggled();
@@ -175,6 +179,8 @@ public:
 	void disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
 	void clear_connections();
 
+	void set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity);
+
 	void add_valid_connection_type(int p_type, int p_with_type);
 	void remove_valid_connection_type(int p_type, int p_with_type);
 	bool is_valid_connection_type(int p_type, int p_with_type) const;
@@ -206,6 +212,8 @@ public:
 	int get_snap() const;
 	void set_snap(int p_snap);
 
+	HBoxContainer *get_zoom_hbox();
+
 	GraphEdit();
 };
 

+ 7 - 3
scene/gui/line_edit.cpp

@@ -565,6 +565,9 @@ void LineEdit::_notification(int p_what) {
 #endif
 		case NOTIFICATION_RESIZED: {
 
+			if (expand_to_text_length) {
+				window_pos = 0; //force scroll back since it's expanding to text length
+			}
 			set_cursor_position(get_cursor_position());
 
 		} break;
@@ -1098,11 +1101,12 @@ void LineEdit::set_cursor_position(int p_pos) {
 			for (int i = cursor_pos; i >= window_pos; i--) {
 
 				if (i >= text.length()) {
-					accum_width = font->get_char_size(' ').width; //anything should do
+					//do not do this, because if the cursor is at the end, its just fine that it takes no space
+					//accum_width = font->get_char_size(' ').width; //anything should do
 				} else {
 					accum_width += font->get_char_size(text[i], i + 1 < text.length() ? text[i + 1] : 0).width; //anything should do
 				}
-				if (accum_width >= window_width)
+				if (accum_width > window_width)
 					break;
 
 				wp = i;
@@ -1169,7 +1173,7 @@ Size2 LineEdit::get_minimum_size() const {
 	int mstext = get_constant("minimum_spaces") * space_size;
 
 	if (expand_to_text_length) {
-		mstext = MAX(mstext, font->get_string_size(text).x + space_size); //add a spce because some fonts are too exact
+		mstext = MAX(mstext, font->get_string_size(text).x + space_size); //add a spce because some fonts are too exact, and because cursor needs a bit more when at the end
 	}
 
 	min.width += mstext;

+ 3 - 3
scene/gui/progress_bar.cpp

@@ -39,9 +39,9 @@ Size2 ProgressBar::get_minimum_size() const {
 	Size2 minimum_size = bg->get_minimum_size();
 	minimum_size.height = MAX(minimum_size.height, fg->get_minimum_size().height);
 	minimum_size.width = MAX(minimum_size.width, fg->get_minimum_size().width);
-	if (percent_visible) {
-		minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + font->get_height());
-	}
+	//if (percent_visible) { this is needed, else the progressbar will collapse
+	minimum_size.height = MAX(minimum_size.height, bg->get_minimum_size().height + font->get_height());
+	//}
 	return minimum_size;
 }
 

+ 15 - 0
scene/register_scene_types.cpp

@@ -63,6 +63,8 @@
 #include "scene/2d/tile_map.h"
 #include "scene/2d/visibility_notifier_2d.h"
 #include "scene/2d/y_sort.h"
+#include "scene/animation/animation_blend_tree.h"
+#include "scene/animation/animation_graph_player.h"
 #include "scene/animation/animation_player.h"
 #include "scene/animation/animation_tree_player.h"
 #include "scene/animation/tween.h"
@@ -382,6 +384,19 @@ void register_scene_types() {
 	ClassDB::register_class<NavigationMesh>();
 	ClassDB::register_class<Navigation>();
 
+	ClassDB::register_class<AnimationGraphPlayer>();
+	ClassDB::register_class<AnimationNode>();
+	ClassDB::register_class<AnimationNodeBlendTree>();
+	ClassDB::register_class<AnimationNodeOutput>();
+	ClassDB::register_class<AnimationNodeOneShot>();
+	ClassDB::register_class<AnimationNodeAnimation>();
+	ClassDB::register_class<AnimationNodeAdd>();
+	ClassDB::register_class<AnimationNodeBlend2>();
+	ClassDB::register_class<AnimationNodeBlend3>();
+	ClassDB::register_class<AnimationNodeTimeScale>();
+	ClassDB::register_class<AnimationNodeTimeSeek>();
+	ClassDB::register_class<AnimationNodeTransition>();
+
 	OS::get_singleton()->yield(); //may take time to init
 
 	ClassDB::register_virtual_class<CollisionObject>();

+ 1 - 0
scene/resources/default_theme/default_theme.cpp

@@ -874,6 +874,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
 	theme->set_stylebox("bg", "GraphEdit", make_stylebox(tree_bg_png, 4, 4, 4, 5));
 	theme->set_color("grid_minor", "GraphEdit", Color(1, 1, 1, 0.05));
 	theme->set_color("grid_major", "GraphEdit", Color(1, 1, 1, 0.2));
+	theme->set_color("activity", "GraphEdit", Color(1, 1, 1));
 	theme->set_constant("bezier_len_pos", "GraphEdit", 80 * scale);
 	theme->set_constant("bezier_len_neg", "GraphEdit", 160 * scale);
 

+ 2 - 0
scene/scene_string_names.cpp

@@ -187,6 +187,8 @@ SceneStringNames::SceneStringNames() {
 
 	node_configuration_warning_changed = StaticCString::create("node_configuration_warning_changed");
 
+	output = StaticCString::create("output");
+
 	path_pp = NodePath("..");
 
 	_default = StaticCString::create("default");

+ 2 - 0
scene/scene_string_names.h

@@ -199,6 +199,8 @@ public:
 
 	StringName node_configuration_warning_changed;
 
+	StringName output;
+
 	enum {
 		MAX_MATERIALS = 32
 	};