diff options
25 files changed, 4043 insertions, 49 deletions
diff --git a/core/resource.cpp b/core/resource.cpp index 179333aa14..f100f4447b 100644 --- a/core/resource.cpp +++ b/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); diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index d41d5c929a..4dde893c6d 100644 --- a/editor/editor_data.cpp +++ b/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()) diff --git a/editor/editor_data.h b/editor/editor_data.h index 0452867bf4..0ecef8ae31 100644 --- a/editor/editor_data.h +++ b/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; diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 848e0b30da..9a3174fb1a 100644 --- a/editor/editor_inspector.cpp +++ b/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); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 06426a30e6..2a88be656a 100644 --- a/editor/editor_inspector.h +++ b/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); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 72a7ce3534..a34aebc63a 100644 --- a/editor/editor_node.cpp +++ b/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 diff --git a/editor/editor_node.h b/editor/editor_node.h index a441440dcc..dedd947633 100644 --- a/editor/editor_node.h +++ b/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); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index c50187aa02..5abcae80e0 100644 --- a/editor/editor_properties.cpp +++ b/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); diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 03e72b4ec2..734ca2de82 100644 --- a/editor/editor_properties.h +++ b/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); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 30d439b1c1..78aa3da546 100644 --- a/editor/editor_themes.cpp +++ b/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")); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp new file mode 100644 index 0000000000..f466758917 --- /dev/null +++ b/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() { +} diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h new file mode 100644 index 0000000000..71efed6da6 --- /dev/null +++ b/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 <reduzio@gmail.com> +*/ + +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 diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp new file mode 100644 index 0000000000..a266e69bcb --- /dev/null +++ b/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; + } +} diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h new file mode 100644 index 0000000000..68afdf04b3 --- /dev/null +++ b/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 diff --git a/scene/animation/animation_graph_player.cpp b/scene/animation/animation_graph_player.cpp new file mode 100644 index 0000000000..ee9621dc1a --- /dev/null +++ b/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; + } +} diff --git a/scene/animation/animation_graph_player.h b/scene/animation/animation_graph_player.h new file mode 100644 index 0000000000..6487a48f34 --- /dev/null +++ b/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 diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 0aabc3b38c..06aaeddf18 100644 --- a/scene/animation/animation_player.cpp +++ b/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); diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 38ce91a4df..e2c730a56e 100644 --- a/scene/gui/graph_edit.cpp +++ b/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)); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 3bfde44854..14789001e4 100644 --- a/scene/gui/graph_edit.h +++ b/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(); }; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 2ab7149886..0cd5219f8f 100644 --- a/scene/gui/line_edit.cpp +++ b/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; diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 37e519e375..fc5d56237a 100644 --- a/scene/gui/progress_bar.cpp +++ b/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; } diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 2f3d4df329..b455fa3511 100644 --- a/scene/register_scene_types.cpp +++ b/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>(); diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 3ea856541e..d64e6970bf 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/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); diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp index 2dc32b893d..bf765385d0 100644 --- a/scene/scene_string_names.cpp +++ b/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"); diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h index 2e6da26d68..b88cf7d8d7 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -199,6 +199,8 @@ public: StringName node_configuration_warning_changed; + StringName output; + enum { MAX_MATERIALS = 32 }; |