-Added AnimationGraphPlayer (still missing features)

-Added ability to edit resources from built-in inspector (wip, needs testing and feedback)
This commit is contained in:
Juan Linietsky 2018-06-18 22:10:48 -03:00
parent 5c5aafabec
commit 0a1c1c660f
25 changed files with 4049 additions and 55 deletions

View file

@ -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);

View file

@ -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())

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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
if (!changing_scene) {
if (editor_plugin_screen)
editor_plugin_screen->make_visible(false);
editor_plugin_screen = main_plugin;
editor_plugin_screen->edit(current_obj);
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++) {
main_editor_buttons[i]->set_pressed(editor_table[i] == 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);
}
} else {
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
editor_plugin_screen->edit(current_obj);
if (!changing_scene) {
if (editor_plugin_screen)
editor_plugin_screen->make_visible(false);
editor_plugin_screen = main_plugin;
editor_plugin_screen->edit(current_obj);
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++) {
main_editor_buttons[i]->set_pressed(editor_table[i] == main_plugin);
}
}
} else {
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

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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"));

View file

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

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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);

View file

@ -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));

View file

@ -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();
};

View file

@ -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;

View file

@ -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;
}

View file

@ -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>();

View file

@ -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);

View file

@ -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");

View file

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