summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/method_ptrcall.h45
-rw-r--r--core/type_info.h1
-rw-r--r--core/variant.cpp24
-rw-r--r--core/variant.h2
-rw-r--r--editor/editor_autoload_settings.cpp8
-rw-r--r--editor/editor_node.cpp8
-rw-r--r--editor/editor_profiler.cpp19
-rw-r--r--editor/editor_profiler.h2
-rw-r--r--editor/icons/icon_animation_tree.svg5
-rw-r--r--editor/icons/icon_auto_end.svg68
-rw-r--r--editor/icons/icon_play_travel.svg85
-rw-r--r--editor/icons/icon_tool_add_node.svg80
-rw-r--r--editor/icons/icon_tool_connect.svg72
-rw-r--r--editor/icons/icon_transition_end.svg72
-rw-r--r--editor/icons/icon_transition_end_auto.svg74
-rw-r--r--editor/icons/icon_transition_end_auto_big.svg74
-rw-r--r--editor/icons/icon_transition_end_big.svg74
-rw-r--r--editor/icons/icon_transition_immediate.svg64
-rw-r--r--editor/icons/icon_transition_immediate_auto.svg64
-rw-r--r--editor/icons/icon_transition_immediate_auto_big.svg66
-rw-r--r--editor/icons/icon_transition_immediate_big.svg66
-rw-r--r--editor/icons/icon_transition_sync.svg72
-rw-r--r--editor/icons/icon_transition_sync_auto.svg74
-rw-r--r--editor/icons/icon_transition_sync_auto_big.svg74
-rw-r--r--editor/icons/icon_transition_sync_big.svg74
-rw-r--r--editor/plugins/animation_blend_space_1d_editor.cpp741
-rw-r--r--editor/plugins/animation_blend_space_1d_editor.h117
-rw-r--r--editor/plugins/animation_blend_space_2d_editor.cpp (renamed from editor/plugins/animation_blend_space_editor.cpp)132
-rw-r--r--editor/plugins/animation_blend_space_2d_editor.h (renamed from editor/plugins/animation_blend_space_editor.h)34
-rw-r--r--editor/plugins/animation_blend_tree_editor_plugin.cpp28
-rw-r--r--editor/plugins/animation_state_machine_editor.cpp1313
-rw-r--r--editor/plugins/animation_state_machine_editor.h167
-rw-r--r--platform/android/java/src/org/godotengine/godot/Godot.java15
-rw-r--r--platform/android/java_glue.cpp16
-rw-r--r--platform/android/os_android.cpp17
-rw-r--r--platform/android/os_android.h4
-rw-r--r--scene/animation/animation_blend_space_1d.cpp284
-rw-r--r--scene/animation/animation_blend_space_1d.h68
-rw-r--r--scene/animation/animation_blend_space_2d.cpp (renamed from scene/animation/animation_blend_space.cpp)150
-rw-r--r--scene/animation/animation_blend_space_2d.h (renamed from scene/animation/animation_blend_space.h)24
-rw-r--r--scene/animation/animation_blend_tree.cpp16
-rw-r--r--scene/animation/animation_blend_tree.h4
-rw-r--r--scene/animation/animation_node_state_machine.cpp790
-rw-r--r--scene/animation/animation_node_state_machine.h142
-rw-r--r--scene/animation/animation_tree.cpp (renamed from scene/animation/animation_graph_player.cpp)88
-rw-r--r--scene/animation/animation_tree.h (renamed from scene/animation/animation_graph_player.h)20
-rw-r--r--scene/register_scene_types.cpp13
47 files changed, 5171 insertions, 279 deletions
diff --git a/core/method_ptrcall.h b/core/method_ptrcall.h
index 2007c3def5..677e8e1fb2 100644
--- a/core/method_ptrcall.h
+++ b/core/method_ptrcall.h
@@ -214,6 +214,50 @@ struct PtrToArg<const T *> {
} \
}
+#define MAKE_VECARG_ALT(m_type, m_type_alt) \
+ template <> \
+ struct PtrToArg<Vector<m_type_alt> > { \
+ _FORCE_INLINE_ static Vector<m_type_alt> convert(const void *p_ptr) { \
+ const PoolVector<m_type> *dvs = reinterpret_cast<const PoolVector<m_type> *>(p_ptr); \
+ Vector<m_type_alt> ret; \
+ int len = dvs->size(); \
+ ret.resize(len); \
+ { \
+ PoolVector<m_type>::Read r = dvs->read(); \
+ for (int i = 0; i < len; i++) { \
+ ret[i] = r[i]; \
+ } \
+ } \
+ return ret; \
+ } \
+ _FORCE_INLINE_ static void encode(Vector<m_type_alt> p_vec, void *p_ptr) { \
+ PoolVector<m_type> *dv = reinterpret_cast<PoolVector<m_type> *>(p_ptr); \
+ int len = p_vec.size(); \
+ dv->resize(len); \
+ { \
+ PoolVector<m_type>::Write w = dv->write(); \
+ for (int i = 0; i < len; i++) { \
+ w[i] = p_vec[i]; \
+ } \
+ } \
+ } \
+ }; \
+ template <> \
+ struct PtrToArg<const Vector<m_type_alt> &> { \
+ _FORCE_INLINE_ static Vector<m_type_alt> convert(const void *p_ptr) { \
+ const PoolVector<m_type> *dvs = reinterpret_cast<const PoolVector<m_type> *>(p_ptr); \
+ Vector<m_type_alt> ret; \
+ int len = dvs->size(); \
+ ret.resize(len); \
+ { \
+ PoolVector<m_type>::Read r = dvs->read(); \
+ for (int i = 0; i < len; i++) { \
+ ret[i] = r[i]; \
+ } \
+ } \
+ return ret; \
+ } \
+ }
MAKE_VECARG(String);
MAKE_VECARG(uint8_t);
MAKE_VECARG(int);
@@ -221,6 +265,7 @@ MAKE_VECARG(float);
MAKE_VECARG(Vector2);
MAKE_VECARG(Vector3);
MAKE_VECARG(Color);
+MAKE_VECARG_ALT(String, StringName);
//for stuff that gets converted to Array vectors
#define MAKE_VECARR(m_type) \
diff --git a/core/type_info.h b/core/type_info.h
index c1af4fac69..bf497f1e5f 100644
--- a/core/type_info.h
+++ b/core/type_info.h
@@ -194,6 +194,7 @@ MAKE_TEMPLATE_TYPE_INFO(Vector, Color, Variant::POOL_COLOR_ARRAY)
MAKE_TEMPLATE_TYPE_INFO(Vector, Variant, Variant::ARRAY)
MAKE_TEMPLATE_TYPE_INFO(Vector, RID, Variant::ARRAY)
MAKE_TEMPLATE_TYPE_INFO(Vector, Plane, Variant::ARRAY)
+MAKE_TEMPLATE_TYPE_INFO(Vector, StringName, Variant::POOL_STRING_ARRAY)
MAKE_TEMPLATE_TYPE_INFO(PoolVector, Plane, Variant::ARRAY)
MAKE_TEMPLATE_TYPE_INFO(PoolVector, Face3, Variant::POOL_VECTOR3_ARRAY)
diff --git a/core/variant.cpp b/core/variant.cpp
index a6df95e310..c48aa57652 100644
--- a/core/variant.cpp
+++ b/core/variant.cpp
@@ -2012,6 +2012,19 @@ Variant::operator Vector<String>() const {
}
return to;
}
+Variant::operator Vector<StringName>() const {
+
+ PoolVector<String> from = operator PoolVector<String>();
+ Vector<StringName> to;
+ int len = from.size();
+ to.resize(len);
+ for (int i = 0; i < len; i++) {
+
+ to[i] = from[i];
+ }
+ return to;
+}
+
Variant::operator Vector<Vector3>() const {
PoolVector<Vector3> from = operator PoolVector<Vector3>();
@@ -2444,6 +2457,17 @@ Variant::Variant(const Vector<String> &p_array) {
*this = v;
}
+Variant::Variant(const Vector<StringName> &p_array) {
+
+ type = NIL;
+ PoolVector<String> v;
+ int len = p_array.size();
+ v.resize(len);
+ for (int i = 0; i < len; i++)
+ v.set(i, p_array[i]);
+ *this = v;
+}
+
Variant::Variant(const Vector<Vector3> &p_array) {
type = NIL;
diff --git a/core/variant.h b/core/variant.h
index f227e4bfdb..4b245d25e6 100644
--- a/core/variant.h
+++ b/core/variant.h
@@ -216,6 +216,7 @@ public:
operator Vector<int>() const;
operator Vector<real_t>() const;
operator Vector<String>() const;
+ operator Vector<StringName>() const;
operator Vector<Vector3>() const;
operator Vector<Color>() const;
operator Vector<RID>() const;
@@ -280,6 +281,7 @@ public:
Variant(const Vector<int> &p_int_array);
Variant(const Vector<real_t> &p_real_array);
Variant(const Vector<String> &p_string_array);
+ Variant(const Vector<StringName> &p_string_array);
Variant(const Vector<Vector3> &p_vector3_array);
Variant(const Vector<Color> &p_color_array);
Variant(const Vector<Plane> &p_array); // helper
diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp
index 30983ca149..2f0982e5d9 100644
--- a/editor/editor_autoload_settings.cpp
+++ b/editor/editor_autoload_settings.cpp
@@ -753,7 +753,6 @@ EditorAutoloadSettings::EditorAutoloadSettings() {
autoload_cache.push_back(info);
}
- List<Node *> to_add;
for (List<AutoLoadInfo>::Element *E = autoload_cache.front(); E; E = E->next()) {
AutoLoadInfo &info = E->get();
@@ -763,9 +762,6 @@ EditorAutoloadSettings::EditorAutoloadSettings() {
Ref<Script> scr = info.node->get_script();
info.in_editor = scr.is_valid() && scr->is_tool();
info.node->set_name(info.name);
- if (info.in_editor) {
- to_add.push_back(info.node);
- }
}
if (info.is_singleton) {
@@ -780,10 +776,6 @@ EditorAutoloadSettings::EditorAutoloadSettings() {
}
}
- for (List<Node *>::Element *E = to_add.front(); E; E = E->next()) {
- get_tree()->get_root()->add_child(E->get());
- }
-
autoload_changed = "autoload_changed";
updating_autoload = false;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 83f1cd40ba..67e1ca79f3 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -66,9 +66,11 @@
#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_space_editor.h"
+#include "editor/plugins/animation_blend_space_1d_editor.h"
+#include "editor/plugins/animation_blend_space_2d_editor.h"
#include "editor/plugins/animation_blend_tree_editor_plugin.h"
#include "editor/plugins/animation_player_editor_plugin.h"
+#include "editor/plugins/animation_state_machine_editor.h"
#include "editor/plugins/animation_tree_editor_plugin.h"
#include "editor/plugins/asset_library_editor_plugin.h"
#include "editor/plugins/baked_lightmap_editor_plugin.h"
@@ -5400,7 +5402,9 @@ EditorNode::EditorNode() {
// FIXME: Disabled for Godot 3.0 as made incompatible, it needs to be ported to the new API.
//add_editor_plugin(memnew(ShaderGraphEditorPlugin(this)));
add_editor_plugin(memnew(AnimationNodeBlendTreeEditorPlugin(this)));
- add_editor_plugin(memnew(AnimationNodeBlendSpaceEditorPlugin(this)));
+ add_editor_plugin(memnew(AnimationNodeBlendSpace1DEditorPlugin(this)));
+ add_editor_plugin(memnew(AnimationNodeBlendSpace2DEditorPlugin(this)));
+ add_editor_plugin(memnew(AnimationNodeStateMachineEditorPlugin(this)));
add_editor_plugin(memnew(CameraEditorPlugin(this)));
add_editor_plugin(memnew(ThemeEditorPlugin(this)));
diff --git a/editor/editor_profiler.cpp b/editor/editor_profiler.cpp
index fcd3dc2f68..d4a97b7095 100644
--- a/editor/editor_profiler.cpp
+++ b/editor/editor_profiler.cpp
@@ -424,20 +424,25 @@ void EditorProfiler::_update_frame() {
void EditorProfiler::_activate_pressed() {
if (activate->is_pressed()) {
- clear();
activate->set_icon(get_icon("Stop", "EditorIcons"));
- activate->set_text(TTR("Stop Profiling"));
+ activate->set_text(TTR("Stop"));
} else {
activate->set_icon(get_icon("Play", "EditorIcons"));
- activate->set_text(TTR("Start Profiling"));
+ activate->set_text(TTR("Start"));
}
emit_signal("enable_profiling", activate->is_pressed());
}
+void EditorProfiler::_clear_pressed() {
+
+ clear();
+}
+
void EditorProfiler::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE) {
activate->set_icon(get_icon("Play", "EditorIcons"));
+ clear_button->set_icon(get_icon("Clear", "EditorIcons"));
}
}
@@ -599,6 +604,7 @@ void EditorProfiler::_bind_methods() {
ClassDB::bind_method(D_METHOD("_update_frame"), &EditorProfiler::_update_frame);
ClassDB::bind_method(D_METHOD("_update_plot"), &EditorProfiler::_update_plot);
ClassDB::bind_method(D_METHOD("_activate_pressed"), &EditorProfiler::_activate_pressed);
+ ClassDB::bind_method(D_METHOD("_clear_pressed"), &EditorProfiler::_clear_pressed);
ClassDB::bind_method(D_METHOD("_graph_tex_draw"), &EditorProfiler::_graph_tex_draw);
ClassDB::bind_method(D_METHOD("_graph_tex_input"), &EditorProfiler::_graph_tex_input);
ClassDB::bind_method(D_METHOD("_graph_tex_mouse_exit"), &EditorProfiler::_graph_tex_mouse_exit);
@@ -625,10 +631,15 @@ EditorProfiler::EditorProfiler() {
add_child(hb);
activate = memnew(Button);
activate->set_toggle_mode(true);
- activate->set_text(TTR("Start Profiling"));
+ activate->set_text(TTR("Start"));
activate->connect("pressed", this, "_activate_pressed");
hb->add_child(activate);
+ clear_button = memnew(Button);
+ clear_button->set_text(TTR("Clear"));
+ clear_button->connect("pressed", this, "_clear_pressed");
+ hb->add_child(clear_button);
+
hb->add_child(memnew(Label(TTR("Measure:"))));
display_mode = memnew(OptionButton);
diff --git a/editor/editor_profiler.h b/editor/editor_profiler.h
index d902a97c5d..cb451475e7 100644
--- a/editor/editor_profiler.h
+++ b/editor/editor_profiler.h
@@ -100,6 +100,7 @@ public:
private:
Button *activate;
+ Button *clear_button;
TextureRect *graph;
Ref<ImageTexture> graph_texture;
PoolVector<uint8_t> graph_image;
@@ -133,6 +134,7 @@ private:
void _update_frame();
void _activate_pressed();
+ void _clear_pressed();
String _get_time_as_text(Metric &m, float p_time, int p_calls);
diff --git a/editor/icons/icon_animation_tree.svg b/editor/icons/icon_animation_tree.svg
new file mode 100644
index 0000000000..046506fa37
--- /dev/null
+++ b/editor/icons/icon_animation_tree.svg
@@ -0,0 +1,5 @@
+<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+<g transform="translate(0 -1036.4)">
+<path transform="translate(0 1036.4)" d="m1 1v14h1.166v-2h1.834v2h8v-2h2v2h1v-14h-1v2h-2v-2h-8v2h-1.834v-2h-1.166zm4 3h2v1 1h1 3v2h-2v1 1h1 1v2h-1-2a1.0001 1.0001 0 0 1 -1 -1v-1-2h-1a1.0001 1.0001 0 0 1 -1 -1v-1-1-1zm-2.834 1h1.834v2h-1.834v-2zm9.834 0h2v2h-2v-2zm-9.834 4h1.834v2h-1.834v-2zm9.834 0h2v2h-2v-2z" fill="#cea4f1"/>
+</g>
+</svg>
diff --git a/editor/icons/icon_auto_end.svg b/editor/icons/icon_auto_end.svg
new file mode 100644
index 0000000000..9e779c69f4
--- /dev/null
+++ b/editor/icons/icon_auto_end.svg
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg6"
+ sodipodi:docname="icon_auto_end.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs10" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1273"
+ inkscape:window-height="766"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="41.7193"
+ inkscape:cx="12.08616"
+ inkscape:cy="6.9898672"
+ inkscape:window-x="539"
+ inkscape:window-y="208"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg6" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path2"
+ style="color:#000000;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;white-space:normal;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+ d="m 13.999798,14 c 0.552262,-5.5e-5 0.999945,-0.447738 1,-1 V 3 c -5.5e-5,-0.5522619 -0.447738,-0.9999448 -1,-1 H 5.9997976 C 5.6959349,1.9998247 5.4084731,2.1378063 5.2185476,2.375 l -4,5 c -0.29139692,0.3649711 -0.29139692,0.8830289 0,1.248 l 4,5 c 0.189538,0.237924 0.4770584,0.376652 0.78125,0.37695 h 8.0000004 z m -1,-2 H 6.4802976 l -3.1992,-4 3.1992,-4 H 12.999798 Z M 6.9997976,10 V 6 l -2,2 z"
+ sodipodi:nodetypes="cccccccccccccccccccccc" />
+ <g
+ aria-label="E"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;line-height:1.25;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';letter-spacing:0px;word-spacing:0px;fill:#e0e0e0;fill-opacity:1;stroke:none"
+ id="text829"
+ transform="matrix(0.20475474,0,0,0.20475474,4.7903856,12.365563)">
+ <path
+ d="M 15.129502,-36.414393 H 35.422471 V -30.7308 H 22.649034 v 5.429688 h 12.011718 v 5.683594 H 22.649034 v 6.679687 h 13.203125 v 5.6835938 H 15.129502 Z"
+ style="fill:#e0e0e0;fill-opacity:1"
+ id="path831"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/editor/icons/icon_play_travel.svg b/editor/icons/icon_play_travel.svg
new file mode 100644
index 0000000000..5cd3e07e20
--- /dev/null
+++ b/editor/icons/icon_play_travel.svg
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg8"
+ sodipodi:docname="icon_play_travel.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1446"
+ inkscape:window-height="646"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="14.75"
+ inkscape:cx="8.2818541"
+ inkscape:cy="5.7694884"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg8" />
+ <g
+ transform="matrix(0.59321602,0,0,0.59321602,-1.2203136,-611.14809)"
+ id="g6">
+ <g
+ id="g4">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+ id="path2"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <g
+ transform="matrix(0.59321602,0,0,0.59321602,7.5254716,-610.94451)"
+ id="g6-3">
+ <g
+ id="g4-6">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+ id="path2-7"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1"
+ id="rect842"
+ width="9.5593224"
+ height="0.54237264"
+ x="3.0058463"
+ y="8.1280737"
+ ry="0.27118632" />
+</svg>
diff --git a/editor/icons/icon_tool_add_node.svg b/editor/icons/icon_tool_add_node.svg
new file mode 100644
index 0000000000..a4ff4d08a0
--- /dev/null
+++ b/editor/icons/icon_tool_add_node.svg
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg8"
+ sodipodi:docname="icon_tool_add_node.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1516"
+ inkscape:window-height="747"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="14.75"
+ inkscape:cx="8"
+ inkscape:cy="8"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4" />
+ <g
+ transform="translate(0 -1036.4)"
+ id="g6">
+ <g
+ transform="translate(-26.001 -9.8683)"
+ id="g4">
+ <path
+ style="fill:#e0e0e0;fill-opacity:1"
+ d="m 27.917081,1047.5557 c -0.422624,0 -0.763672,0.3411 -0.763672,0.7637 v 11.8301 c 0,0.4226 0.341048,0.7637 0.763672,0.7637 h 12.507813 c 0.422624,0 0.761719,-0.3411 0.761719,-0.7637 v -11.8301 c 0,-0.4226 -0.339095,-0.7637 -0.761719,-0.7637 z m 1.898438,1.6954 h 8.642578 c 0.422624,0 0.763672,0.341 0.763672,0.7636 v 8.5078 c 0,0.4227 -0.341048,0.7618 -0.763672,0.7618 h -8.642578 c -0.422625,0 -0.763672,-0.3391 -0.763672,-0.7618 v -8.5078 c 0,-0.4226 0.341047,-0.7636 0.763672,-0.7636 z"
+ id="rect821"
+ inkscape:connector-curvature="0" />
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1"
+ id="rect826"
+ width="7.7966104"
+ height="2.3728814"
+ x="30.20439"
+ y="1052.9802"
+ ry="0.76286" />
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1;stroke-width:0.88253576"
+ id="rect828"
+ width="2.3728814"
+ height="7.5254235"
+ x="32.916256"
+ y="1050.3361"
+ ry="0.72997814" />
+ </g>
+ </g>
+</svg>
diff --git a/editor/icons/icon_tool_connect.svg b/editor/icons/icon_tool_connect.svg
new file mode 100644
index 0000000000..91d5893163
--- /dev/null
+++ b/editor/icons/icon_tool_connect.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg8"
+ sodipodi:docname="icon_tool_connect.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1516"
+ inkscape:window-height="747"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="3.6875"
+ inkscape:cx="8.5909556"
+ inkscape:cy="7.8012075"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4" />
+ <g
+ transform="translate(0 -1036.4)"
+ id="g6">
+ <g
+ transform="translate(-26.001 -9.8683)"
+ id="g4">
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1"
+ id="rect849"
+ width="14.305085"
+ height="2.1694915"
+ x="26.766621"
+ y="1053.1389"
+ ry="0.76286" />
+ <path
+ style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:1.16725671px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 30.596131,1046.927 v 14.8861 l 8.228847,-7.5722 z"
+ id="path853"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+</svg>
diff --git a/editor/icons/icon_transition_end.svg b/editor/icons/icon_transition_end.svg
new file mode 100644
index 0000000000..8a1937670a
--- /dev/null
+++ b/editor/icons/icon_transition_end.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg8"
+ sodipodi:docname="icon_transition_end.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1403"
+ inkscape:window-height="782"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="20.85965"
+ inkscape:cx="10.204146"
+ inkscape:cy="5.3391396"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg8" />
+ <g
+ transform="translate(-2,-1036.4)"
+ id="g6">
+ <g
+ id="g4">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east_asian:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+ id="path2"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1"
+ id="rect862"
+ width="3.0681243"
+ height="10.067283"
+ x="11.16989"
+ y="3.0084109"
+ ry="0.76286" />
+</svg>
diff --git a/editor/icons/icon_transition_end_auto.svg b/editor/icons/icon_transition_end_auto.svg
new file mode 100644
index 0000000000..18927bc4ef
--- /dev/null
+++ b/editor/icons/icon_transition_end_auto.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg8"
+ sodipodi:docname="icon_transition_automatic.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1403"
+ inkscape:window-height="782"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="20.85965"
+ inkscape:cx="0.56831798"
+ inkscape:cy="5.1473818"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg8" />
+ <g
+ transform="translate(-2,-1036.4)"
+ id="g6"
+ style="fill:#77ce57;fill-opacity:1">
+ <g
+ id="g4"
+ style="fill:#77ce57;fill-opacity:1">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east_asian:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1"
+ id="path2"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <rect
+ style="fill:#77ce57;fill-opacity:1"
+ id="rect862"
+ width="3.0681243"
+ height="10.067283"
+ x="11.16989"
+ y="3.0084109"
+ ry="0.76286" />
+</svg>
diff --git a/editor/icons/icon_transition_end_auto_big.svg b/editor/icons/icon_transition_end_auto_big.svg
new file mode 100644
index 0000000000..aaedafaf04
--- /dev/null
+++ b/editor/icons/icon_transition_end_auto_big.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="20"
+ height="20"
+ version="1.1"
+ viewBox="0 0 20 20"
+ id="svg8"
+ sodipodi:docname="icon_transition_automatic_big.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1403"
+ inkscape:window-height="782"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="14.75"
+ inkscape:cx="0.3064671"
+ inkscape:cy="14.348448"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg8" />
+ <g
+ transform="matrix(1.4099529,0,0,1.4099529,-4.1975887,-1462.5094)"
+ id="g6"
+ style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-opacity:1">
+ <g
+ id="g4"
+ style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-opacity:1">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-opacity:1;fill-rule:evenodd;stroke:#41562e;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+ id="path2"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <rect
+ style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-width:1.409953;stroke-opacity:1"
+ id="rect862"
+ width="4.3259106"
+ height="14.194397"
+ x="14.371336"
+ y="3.0076122"
+ ry="1.0755967" />
+</svg>
diff --git a/editor/icons/icon_transition_end_big.svg b/editor/icons/icon_transition_end_big.svg
new file mode 100644
index 0000000000..46d42e95e3
--- /dev/null
+++ b/editor/icons/icon_transition_end_big.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="20"
+ height="20"
+ version="1.1"
+ viewBox="0 0 20 20"
+ id="svg8"
+ sodipodi:docname="icon_transition_end_big.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1403"
+ inkscape:window-height="782"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="14.75"
+ inkscape:cx="-1.1122019"
+ inkscape:cy="10.839132"
+ inkscape:window-x="517"
+ inkscape:window-y="261"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg8" />
+ <g
+ transform="matrix(1.4203458,0,0,1.4203458,-4.29479,-1473.1325)"
+ id="g6"
+ style="stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+ <g
+ id="g4"
+ style="stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+ id="path2"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#424242;stroke-width:1.42026603;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect862"
+ width="4.3577976"
+ height="14.299023"
+ x="14.411009"
+ y="3.1868868"
+ ry="1.0835251" />
+</svg>
diff --git a/editor/icons/icon_transition_immediate.svg b/editor/icons/icon_transition_immediate.svg
new file mode 100644
index 0000000000..ba16a33c91
--- /dev/null
+++ b/editor/icons/icon_transition_immediate.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg8"
+ sodipodi:docname="icon_transition_immediate.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1403"
+ inkscape:window-height="782"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="20.85965"
+ inkscape:cx="4.0199579"
+ inkscape:cy="5.3391396"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4" />
+ <g
+ transform="translate(-2,-1036.4)"
+ id="g6">
+ <g
+ id="g4">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east_asian:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1"
+ id="path2"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+</svg>
diff --git a/editor/icons/icon_transition_immediate_auto.svg b/editor/icons/icon_transition_immediate_auto.svg
new file mode 100644
index 0000000000..c127560145
--- /dev/null
+++ b/editor/icons/icon_transition_immediate_auto.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg8"
+ sodipodi:docname="icon_transition_immediate_auto.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1403"
+ inkscape:window-height="782"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="20.85965"
+ inkscape:cx="-9.2592678"
+ inkscape:cy="5.3391396"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4" />
+ <g
+ transform="translate(-2,-1036.4)"
+ id="g6">
+ <g
+ id="g4">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east_asian:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1"
+ id="path2"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+</svg>
diff --git a/editor/icons/icon_transition_immediate_auto_big.svg b/editor/icons/icon_transition_immediate_auto_big.svg
new file mode 100644
index 0000000000..80d35a36f3
--- /dev/null
+++ b/editor/icons/icon_transition_immediate_auto_big.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="20"
+ height="20"
+ version="1.1"
+ viewBox="0 0 20 20"
+ id="svg8"
+ sodipodi:docname="icon_transition_immediate_auto_big.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1403"
+ inkscape:window-height="782"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="20.85965"
+ inkscape:cx="11.321237"
+ inkscape:cy="3.5752171"
+ inkscape:window-x="517"
+ inkscape:window-y="261"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4" />
+ <g
+ transform="matrix(1.571031,0,0,1.571031,-2.7257681,-1630.6239)"
+ id="g6"
+ style="stroke:#404040;stroke-opacity:1">
+ <g
+ id="g4"
+ style="stroke:#404040;stroke-opacity:1">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-rule:evenodd;stroke:#41562e;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1"
+ id="path2"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+</svg>
diff --git a/editor/icons/icon_transition_immediate_big.svg b/editor/icons/icon_transition_immediate_big.svg
new file mode 100644
index 0000000000..108dcdd500
--- /dev/null
+++ b/editor/icons/icon_transition_immediate_big.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="20"
+ height="20"
+ version="1.1"
+ viewBox="0 0 20 20"
+ id="svg8"
+ sodipodi:docname="icon_transition_immediate_big.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1403"
+ inkscape:window-height="782"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="20.85965"
+ inkscape:cx="0.19928629"
+ inkscape:cy="4.534006"
+ inkscape:window-x="517"
+ inkscape:window-y="261"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g4" />
+ <g
+ transform="matrix(1.571031,0,0,1.571031,-2.7257681,-1630.6239)"
+ id="g6"
+ style="stroke:#404040;stroke-opacity:1">
+ <g
+ id="g4"
+ style="stroke:#404040;stroke-opacity:1">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;stroke:#404040;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;fill-opacity:1"
+ id="path2"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+</svg>
diff --git a/editor/icons/icon_transition_sync.svg b/editor/icons/icon_transition_sync.svg
new file mode 100644
index 0000000000..267d806615
--- /dev/null
+++ b/editor/icons/icon_transition_sync.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg8"
+ sodipodi:docname="icon_transition_sync.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1403"
+ inkscape:window-height="782"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="20.85965"
+ inkscape:cx="10.204146"
+ inkscape:cy="5.3391396"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg8" />
+ <g
+ transform="translate(2.5542471,-1036.4)"
+ id="g6">
+ <g
+ id="g4">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+ id="path2"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1"
+ id="rect862"
+ width="3.0681243"
+ height="10.067283"
+ x="1.9655174"
+ y="3.0084109"
+ ry="0.76286" />
+</svg>
diff --git a/editor/icons/icon_transition_sync_auto.svg b/editor/icons/icon_transition_sync_auto.svg
new file mode 100644
index 0000000000..5ce61e3a6a
--- /dev/null
+++ b/editor/icons/icon_transition_sync_auto.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ version="1.1"
+ viewBox="0 0 16 16"
+ id="svg8"
+ sodipodi:docname="icon_transition_sync_auto.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1403"
+ inkscape:window-height="782"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="20.85965"
+ inkscape:cx="0.56831798"
+ inkscape:cy="5.1473818"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg8" />
+ <g
+ transform="translate(3.0815809,-1036.4)"
+ id="g6"
+ style="fill:#77ce57;fill-opacity:1">
+ <g
+ id="g4"
+ style="fill:#77ce57;fill-opacity:1">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-opacity:1;fill-rule:evenodd;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+ id="path2"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <rect
+ style="fill:#77ce57;fill-opacity:1"
+ id="rect862"
+ width="3.0681243"
+ height="10.067283"
+ x="1.9655174"
+ y="3.0084109"
+ ry="0.76286" />
+</svg>
diff --git a/editor/icons/icon_transition_sync_auto_big.svg b/editor/icons/icon_transition_sync_auto_big.svg
new file mode 100644
index 0000000000..3e84d76398
--- /dev/null
+++ b/editor/icons/icon_transition_sync_auto_big.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="20"
+ height="20"
+ version="1.1"
+ viewBox="0 0 20 20"
+ id="svg8"
+ sodipodi:docname="icon_transition_sync_auto_big.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1403"
+ inkscape:window-height="782"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="14.75"
+ inkscape:cx="0.3064671"
+ inkscape:cy="14.348448"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg8" />
+ <g
+ transform="matrix(1.4099529,0,0,1.4099529,2.1752927,-1462.5094)"
+ id="g6"
+ style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-opacity:1">
+ <g
+ id="g4"
+ style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-opacity:1">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#77ce57;fill-opacity:1;fill-rule:evenodd;stroke:#41562e;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+ id="path2"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <rect
+ style="fill:#77ce57;fill-opacity:1;stroke:#41562e;stroke-width:1.409953;stroke-opacity:1"
+ id="rect862"
+ width="4.3259106"
+ height="14.194397"
+ x="1.6255733"
+ y="3.0076122"
+ ry="1.0755967" />
+</svg>
diff --git a/editor/icons/icon_transition_sync_big.svg b/editor/icons/icon_transition_sync_big.svg
new file mode 100644
index 0000000000..e7cf63e0b3
--- /dev/null
+++ b/editor/icons/icon_transition_sync_big.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="20"
+ height="20"
+ version="1.1"
+ viewBox="0 0 20 20"
+ id="svg8"
+ sodipodi:docname="icon_transition_sync_big.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)">
+ <metadata
+ id="metadata14">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs12" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1403"
+ inkscape:window-height="782"
+ id="namedview10"
+ showgrid="false"
+ inkscape:zoom="14.75"
+ inkscape:cx="19.226781"
+ inkscape:cy="9.27981"
+ inkscape:window-x="302"
+ inkscape:window-y="226"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg8" />
+ <g
+ transform="matrix(1.4203458,0,0,1.4203458,1.8747015,-1473.1325)"
+ id="g6"
+ style="stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+ <g
+ id="g4"
+ style="stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
+ <path
+ d="m 4.9883,1039.4 c -0.5469,0.01 -0.98717,0.4511 -0.98828,0.998 v 8 c 1.163e-4,0.7986 0.89011,1.275 1.5547,0.8321 l 6,-4 c 0.59362,-0.3959 0.59362,-1.2682 0,-1.6641 l -6,-4 c -0.1678,-0.1111 -0.3652,-0.1689 -0.56641,-0.166 z"
+ dominant-baseline="auto"
+ style="color:#000000;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;dominant-baseline:auto;white-space:normal;shape-padding:0;isolation:auto;mix-blend-mode:normal;solid-color:#000000;fill:#e0e0e0;fill-rule:evenodd;stroke:#424242;stroke-width:0.99994373;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto"
+ id="path2"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <rect
+ style="fill:#e0e0e0;fill-opacity:1;stroke:#424242;stroke-width:1.42026603;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect862"
+ width="4.3577976"
+ height="14.299023"
+ x="1.4618562"
+ y="3.1868868"
+ ry="1.0835251" />
+</svg>
diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp
new file mode 100644
index 0000000000..2e128db883
--- /dev/null
+++ b/editor/plugins/animation_blend_space_1d_editor.cpp
@@ -0,0 +1,741 @@
+#include "animation_blend_space_1d_editor.h"
+
+#include "os/keyboard.h"
+#include "scene/animation/animation_blend_tree.h"
+
+void AnimationNodeBlendSpace1DEditorPlugin::edit(Object *p_object) {
+ anim_tree_editor->edit(Object::cast_to<AnimationNodeBlendSpace1D>(p_object));
+}
+
+bool AnimationNodeBlendSpace1DEditorPlugin::handles(Object *p_object) const {
+ return p_object->is_class("AnimationNodeBlendSpace1D");
+}
+
+void AnimationNodeBlendSpace1DEditorPlugin::make_visible(bool p_visible) {
+
+ if (p_visible) {
+ 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);
+ }
+}
+
+AnimationNodeBlendSpace1DEditorPlugin::AnimationNodeBlendSpace1DEditorPlugin(EditorNode *p_node) {
+ editor = p_node;
+ anim_tree_editor = memnew(AnimationNodeBlendSpace1DEditor);
+ anim_tree_editor->set_custom_minimum_size(Size2(0, 150 * EDSCALE));
+
+ button = editor->add_bottom_panel_item(TTR("BlendSpace1D"), anim_tree_editor);
+ button->hide();
+}
+
+AnimationNodeBlendSpace1DEditorPlugin::~AnimationNodeBlendSpace1DEditorPlugin() {
+}
+
+void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEvent> &p_event) {
+ Ref<InputEventKey> k = p_event;
+
+ if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_scancode() == KEY_DELETE && !k->is_echo()) {
+ if (selected_point != -1) {
+ _erase_selected();
+ accept_event();
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+
+ if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) || (mb->get_button_index() == BUTTON_LEFT && tool_create->is_pressed()))) {
+ menu->clear();
+ animations_menu->clear();
+ animations_to_add.clear();
+
+ List<StringName> classes;
+ ClassDB::get_inheriters_from_class("AnimationRootNode", &classes);
+ classes.sort_custom<StringName::AlphCompare>();
+
+ menu->add_submenu_item(TTR("Add Animation"), "animations");
+
+ AnimationTree *gp = blend_space->get_tree();
+ ERR_FAIL_COND(!gp);
+
+ if (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);
+
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ animations_menu->add_icon_item(get_icon("Animation", "Editoricons"), E->get());
+ animations_to_add.push_back(E->get());
+ }
+ }
+ }
+
+ for (List<StringName>::Element *E = classes.front(); E; E = E->next()) {
+ String name = String(E->get()).replace_first("AnimationNode", "");
+ if (name == "Animation")
+ continue;
+
+ int idx = menu->get_item_count();
+ menu->add_item(vformat("Add %s", name));
+ menu->set_item_metadata(idx, E->get());
+ }
+
+ menu->set_global_position(blend_space_draw->get_global_transform().xform(mb->get_position()));
+ menu->popup();
+
+ add_point_pos = (mb->get_position() / blend_space_draw->get_size()).x;
+ add_point_pos *= (blend_space->get_max_space() - blend_space->get_min_space());
+ add_point_pos += blend_space->get_min_space();
+
+ if (snap->is_pressed()) {
+ add_point_pos = Math::stepify(add_point_pos, blend_space->get_snap());
+ }
+ }
+
+ if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+ blend_space_draw->update(); // why not
+
+ // try to see if a point can be selected
+ selected_point = -1;
+ _update_tool_erase();
+
+ for (int i = 0; i < points.size(); i++) {
+
+ if (Math::abs(float(points[i] - mb->get_position().x)) < 10 * EDSCALE) {
+ selected_point = i;
+
+ Ref<AnimationNode> node = blend_space->get_blend_point_node(i);
+ EditorNode::get_singleton()->push_item(node.ptr(), "", true);
+ dragging_selected_attempt = true;
+ drag_from = mb->get_position();
+ _update_tool_erase();
+ _update_edited_point_pos();
+ return;
+ }
+ }
+ }
+
+ if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == BUTTON_LEFT) {
+ if (dragging_selected) {
+ // move
+ float point = blend_space->get_blend_point_position(selected_point);
+ point += drag_ofs.x;
+
+ if (snap->is_pressed()) {
+ point = Math::stepify(point, blend_space->get_snap());
+ }
+
+ updating = true;
+ undo_redo->create_action("Move Node Point");
+ undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, point);
+ undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point));
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->add_do_method(this, "_update_edited_point_pos");
+ undo_redo->add_undo_method(this, "_update_edited_point_pos");
+ undo_redo->commit_action();
+ updating = false;
+ _update_edited_point_pos();
+ }
+
+ dragging_selected_attempt = false;
+ dragging_selected = false;
+ blend_space_draw->update();
+ }
+
+ // *set* the blend
+ if (mb.is_valid() && !mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+ float blend_pos = mb->get_position().x / blend_space_draw->get_size().x;
+ blend_pos *= blend_space->get_max_space() - blend_space->get_min_space();
+ blend_pos += blend_space->get_min_space();
+
+ blend_space->set_blend_pos(blend_pos);
+ blend_space_draw->update();
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+
+ if (mm.is_valid() && !blend_space_draw->has_focus()) {
+ blend_space_draw->grab_focus();
+ blend_space_draw->update();
+ }
+
+ if (mm.is_valid() && dragging_selected_attempt) {
+ dragging_selected = true;
+ drag_ofs = ((mm->get_position() - drag_from) / blend_space_draw->get_size()) * ((blend_space->get_max_space() - blend_space->get_min_space()) * Vector2(1, 0));
+ blend_space_draw->update();
+ _update_edited_point_pos();
+ }
+
+ if (mm.is_valid() && tool_blend->is_pressed() && mm->get_button_mask() & BUTTON_MASK_LEFT) {
+ float blend_pos = mm->get_position().x / blend_space_draw->get_size().x;
+ blend_pos *= blend_space->get_max_space() - blend_space->get_min_space();
+ blend_pos += blend_space->get_min_space();
+
+ blend_space->set_blend_pos(blend_pos);
+ blend_space_draw->update();
+ }
+}
+
+void AnimationNodeBlendSpace1DEditor::_blend_space_draw() {
+
+ Color linecolor = get_color("font_color", "Label");
+ Color linecolor_soft = linecolor;
+ linecolor_soft.a *= 0.5;
+
+ Ref<Font> font = get_font("font", "Label");
+ Ref<Texture> icon = get_icon("KeyValue", "EditorIcons");
+ Ref<Texture> icon_selected = get_icon("KeySelected", "EditorIcons");
+
+ Size2 s = blend_space_draw->get_size();
+
+ if (blend_space_draw->has_focus()) {
+ Color color = get_color("accent_color", "Editor");
+ blend_space_draw->draw_rect(Rect2(Point2(), s), color, false);
+ }
+
+ blend_space_draw->draw_line(Point2(1, s.height - 1), Point2(s.width - 1, s.height - 1), linecolor);
+
+ if (blend_space->get_min_space() < 0) {
+ float point = 0.0;
+ point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space());
+ point *= s.width;
+
+ float x = point;
+
+ blend_space_draw->draw_line(Point2(x, s.height - 1), Point2(x, s.height - 5 * EDSCALE), linecolor);
+ blend_space_draw->draw_string(font, Point2(x + 2 * EDSCALE, s.height - 2 * EDSCALE - font->get_height() + font->get_ascent()), "0", linecolor);
+ blend_space_draw->draw_line(Point2(x, s.height - 5 * EDSCALE), Point2(x, 0), linecolor_soft);
+ }
+
+ if (snap->is_pressed()) {
+
+ linecolor_soft.a = linecolor.a * 0.1;
+
+ if (blend_space->get_snap() > 0) {
+ int prev_idx = -1;
+
+ for (int i = 0; i < s.x; i++) {
+ float v = blend_space->get_min_space() + i * (blend_space->get_max_space() - blend_space->get_min_space()) / s.x;
+ int idx = int(v / blend_space->get_snap());
+
+ if (i > 0 && prev_idx != idx) {
+ blend_space_draw->draw_line(Point2(i, 0), Point2(i, s.height), linecolor_soft);
+ }
+
+ prev_idx = idx;
+ }
+ }
+ }
+
+ points.clear();
+
+ for (int i = 0; i < blend_space->get_blend_point_count(); i++) {
+ float point = blend_space->get_blend_point_position(i);
+
+ if (dragging_selected && selected_point == i) {
+ point += drag_ofs.x;
+ if (snap->is_pressed()) {
+ point = Math::stepify(point, blend_space->get_snap());
+ }
+ }
+
+ point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space());
+ point *= s.width;
+
+ points.push_back(point);
+
+ Vector2 gui_point = Vector2(point, s.height / 2.0);
+
+ gui_point -= (icon->get_size() / 2.0);
+
+ gui_point = gui_point.floor();
+
+ if (i == selected_point) {
+ blend_space_draw->draw_texture(icon_selected, gui_point);
+ } else {
+ blend_space_draw->draw_texture(icon, gui_point);
+ }
+ }
+
+ // blend position
+ {
+ Color color;
+ if (tool_blend->is_pressed()) {
+ color = get_color("accent_color", "Editor");
+ } else {
+ color = linecolor;
+ color.a *= 0.5;
+ }
+
+ float point = blend_space->get_blend_pos();
+ point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space());
+ point *= s.width;
+
+ Vector2 gui_point = Vector2(point, s.height / 2.0);
+
+ float mind = 5 * EDSCALE;
+ float maxd = 15 * EDSCALE;
+ blend_space_draw->draw_line(gui_point + Vector2(mind, 0), gui_point + Vector2(maxd, 0), color, 2);
+ blend_space_draw->draw_line(gui_point + Vector2(-mind, 0), gui_point + Vector2(-maxd, 0), color, 2);
+ blend_space_draw->draw_line(gui_point + Vector2(0, mind), gui_point + Vector2(0, maxd), color, 2);
+ blend_space_draw->draw_line(gui_point + Vector2(0, -mind), gui_point + Vector2(0, -maxd), color, 2);
+ }
+}
+
+void AnimationNodeBlendSpace1DEditor::_update_space() {
+
+ if (updating)
+ return;
+
+ updating = true;
+
+ if (blend_space->get_parent().is_valid()) {
+ goto_parent_hb->show();
+ } else {
+ goto_parent_hb->hide();
+ }
+
+ max_value->set_value(blend_space->get_max_space());
+ min_value->set_value(blend_space->get_min_space());
+
+ label_value->set_text(blend_space->get_value_label());
+
+ snap_value->set_value(blend_space->get_snap());
+
+ blend_space_draw->update();
+
+ updating = false;
+}
+
+void AnimationNodeBlendSpace1DEditor::_config_changed(double) {
+ if (updating)
+ return;
+
+ updating = true;
+ undo_redo->create_action("Change BlendSpace1D Limits");
+ undo_redo->add_do_method(blend_space.ptr(), "set_max_space", max_value->get_value());
+ undo_redo->add_undo_method(blend_space.ptr(), "set_max_space", blend_space->get_max_space());
+ undo_redo->add_do_method(blend_space.ptr(), "set_min_space", min_value->get_value());
+ undo_redo->add_undo_method(blend_space.ptr(), "set_min_space", blend_space->get_min_space());
+ undo_redo->add_do_method(blend_space.ptr(), "set_snap", snap_value->get_value());
+ undo_redo->add_undo_method(blend_space.ptr(), "set_snap", blend_space->get_snap());
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace1DEditor::_labels_changed(String) {
+ if (updating)
+ return;
+
+ updating = true;
+ undo_redo->create_action("Change BlendSpace1D Labels", UndoRedo::MERGE_ENDS);
+ undo_redo->add_do_method(blend_space.ptr(), "set_value_label", label_value->get_text());
+ undo_redo->add_undo_method(blend_space.ptr(), "set_value_label", blend_space->get_value_label());
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+ updating = false;
+}
+
+void AnimationNodeBlendSpace1DEditor::_snap_toggled() {
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) {
+ String type = menu->get_item_metadata(p_index);
+
+ Object *obj = ClassDB::instance(type);
+ ERR_FAIL_COND(!obj);
+ AnimationNode *an = Object::cast_to<AnimationNode>(obj);
+ ERR_FAIL_COND(!an);
+
+ Ref<AnimationNode> node(an);
+
+ updating = true;
+ undo_redo->create_action("Add Node Point");
+ undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", node, add_point_pos);
+ undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count());
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace1DEditor::_add_animation_type(int p_index) {
+ Ref<AnimationNodeAnimation> anim;
+ anim.instance();
+
+ anim->set_animation(animations_to_add[p_index]);
+
+ updating = true;
+ undo_redo->create_action("Add Animation Point");
+ undo_redo->add_do_method(blend_space.ptr(), "add_blend_point", anim, add_point_pos);
+ undo_redo->add_undo_method(blend_space.ptr(), "remove_blend_point", blend_space->get_blend_point_count());
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace1DEditor::_tool_switch(int p_tool) {
+
+ if (p_tool == 0) {
+ tool_erase->show();
+ tool_erase_sep->show();
+ } else {
+ tool_erase->hide();
+ tool_erase_sep->hide();
+ }
+
+ _update_tool_erase();
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace1DEditor::_update_edited_point_pos() {
+ if (updating)
+ return;
+
+ if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) {
+ float pos = blend_space->get_blend_point_position(selected_point);
+
+ if (dragging_selected) {
+ pos += drag_ofs.x;
+
+ if (snap->is_pressed()) {
+ pos = Math::stepify(pos, blend_space->get_snap());
+ }
+ }
+
+ updating = true;
+ edit_value->set_value(pos);
+ updating = false;
+ }
+}
+
+void AnimationNodeBlendSpace1DEditor::_update_tool_erase() {
+
+ bool point_valid = selected_point >= 0 && selected_point < blend_space->get_blend_point_count();
+ tool_erase->set_disabled(!point_valid);
+
+ if (point_valid) {
+ Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point);
+
+ if (EditorNode::get_singleton()->item_has_editor(an.ptr())) {
+ open_editor->show();
+ } else {
+ open_editor->hide();
+ }
+
+ edit_hb->show();
+ } else {
+ edit_hb->hide();
+ }
+}
+
+void AnimationNodeBlendSpace1DEditor::_erase_selected() {
+ if (selected_point != -1) {
+ updating = true;
+
+ undo_redo->create_action("Remove BlendSpace1D Point");
+ undo_redo->add_do_method(blend_space.ptr(), "remove_blend_point", selected_point);
+ undo_redo->add_undo_method(blend_space.ptr(), "add_blend_point", blend_space->get_blend_point_node(selected_point), blend_space->get_blend_point_position(selected_point), selected_point);
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->commit_action();
+
+ updating = false;
+
+ blend_space_draw->update();
+ }
+}
+
+void AnimationNodeBlendSpace1DEditor::_edit_point_pos(double) {
+ if (updating)
+ return;
+
+ updating = true;
+ undo_redo->create_action("Move BlendSpace1D Node Point");
+ undo_redo->add_do_method(blend_space.ptr(), "set_blend_point_position", selected_point, edit_value->get_value());
+ undo_redo->add_undo_method(blend_space.ptr(), "set_blend_point_position", selected_point, blend_space->get_blend_point_position(selected_point));
+ undo_redo->add_do_method(this, "_update_space");
+ undo_redo->add_undo_method(this, "_update_space");
+ undo_redo->add_do_method(this, "_update_edited_point_pos");
+ undo_redo->add_undo_method(this, "_update_edited_point_pos");
+ undo_redo->commit_action();
+ updating = false;
+
+ blend_space_draw->update();
+}
+
+void AnimationNodeBlendSpace1DEditor::_open_editor() {
+
+ if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) {
+ Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point);
+ ERR_FAIL_COND(an.is_null());
+ EditorNode::get_singleton()->edit_item(an.ptr());
+ }
+}
+
+void AnimationNodeBlendSpace1DEditor::_goto_parent() {
+ EditorNode::get_singleton()->edit_item(blend_space->get_parent().ptr());
+}
+
+void AnimationNodeBlendSpace1DEditor::_removed_from_graph() {
+ EditorNode::get_singleton()->edit_item(NULL);
+}
+
+void AnimationNodeBlendSpace1DEditor::_notification(int p_what) {
+ if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
+ error_panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+ error_label->add_color_override("font_color", get_color("error_color", "Editor"));
+ panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+ tool_blend->set_icon(get_icon("EditPivot", "EditorIcons"));
+ tool_select->set_icon(get_icon("ToolSelect", "EditorIcons"));
+ tool_create->set_icon(get_icon("EditKey", "EditorIcons"));
+ tool_erase->set_icon(get_icon("Remove", "EditorIcons"));
+ snap->set_icon(get_icon("SnapGrid", "EditorIcons"));
+ open_editor->set_icon(get_icon("Edit", "EditorIcons"));
+ goto_parent->set_icon(get_icon("MoveUp", "EditorIcons"));
+ }
+
+ if (p_what == NOTIFICATION_PROCESS) {
+ String error;
+
+ if (!blend_space->get_tree()) {
+ error = TTR("BlendSpace1D does not belong to an AnimationTree node.");
+ } else if (!blend_space->get_tree()->is_active()) {
+ error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails.");
+ } else if (blend_space->get_tree()->is_state_invalid()) {
+ error = blend_space->get_tree()->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();
+ }
+ }
+ }
+}
+
+void AnimationNodeBlendSpace1DEditor::_bind_methods() {
+ ClassDB::bind_method("_blend_space_gui_input", &AnimationNodeBlendSpace1DEditor::_blend_space_gui_input);
+ ClassDB::bind_method("_blend_space_draw", &AnimationNodeBlendSpace1DEditor::_blend_space_draw);
+ ClassDB::bind_method("_config_changed", &AnimationNodeBlendSpace1DEditor::_config_changed);
+ ClassDB::bind_method("_labels_changed", &AnimationNodeBlendSpace1DEditor::_labels_changed);
+ ClassDB::bind_method("_update_space", &AnimationNodeBlendSpace1DEditor::_update_space);
+ ClassDB::bind_method("_snap_toggled", &AnimationNodeBlendSpace1DEditor::_snap_toggled);
+ ClassDB::bind_method("_tool_switch", &AnimationNodeBlendSpace1DEditor::_tool_switch);
+ ClassDB::bind_method("_erase_selected", &AnimationNodeBlendSpace1DEditor::_erase_selected);
+ ClassDB::bind_method("_update_tool_erase", &AnimationNodeBlendSpace1DEditor::_update_tool_erase);
+ ClassDB::bind_method("_edit_point_pos", &AnimationNodeBlendSpace1DEditor::_edit_point_pos);
+
+ ClassDB::bind_method("_add_menu_type", &AnimationNodeBlendSpace1DEditor::_add_menu_type);
+ ClassDB::bind_method("_add_animation_type", &AnimationNodeBlendSpace1DEditor::_add_animation_type);
+
+ ClassDB::bind_method("_update_edited_point_pos", &AnimationNodeBlendSpace1DEditor::_update_edited_point_pos);
+
+ ClassDB::bind_method("_open_editor", &AnimationNodeBlendSpace1DEditor::_open_editor);
+ ClassDB::bind_method("_goto_parent", &AnimationNodeBlendSpace1DEditor::_goto_parent);
+
+ ClassDB::bind_method("_removed_from_graph", &AnimationNodeBlendSpace1DEditor::_removed_from_graph);
+}
+
+void AnimationNodeBlendSpace1DEditor::edit(AnimationNodeBlendSpace1D *p_blend_space) {
+
+ if (blend_space.is_valid()) {
+ blend_space->disconnect("removed_from_graph", this, "_removed_from_graph");
+ }
+
+ if (p_blend_space) {
+ blend_space = Ref<AnimationNodeBlendSpace1D>(p_blend_space);
+ } else {
+ blend_space.unref();
+ }
+
+ if (blend_space.is_null()) {
+ hide();
+ } else {
+ blend_space->connect("removed_from_graph", this, "_removed_from_graph");
+
+ _update_space();
+ }
+}
+
+AnimationNodeBlendSpace1DEditor *AnimationNodeBlendSpace1DEditor::singleton = NULL;
+
+AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() {
+ singleton = this;
+ updating = false;
+
+ HBoxContainer *top_hb = memnew(HBoxContainer);
+ add_child(top_hb);
+
+ Ref<ButtonGroup> bg;
+ bg.instance();
+
+ goto_parent_hb = memnew(HBoxContainer);
+ top_hb->add_child(goto_parent_hb);
+
+ goto_parent = memnew(ToolButton);
+ goto_parent->connect("pressed", this, "_goto_parent", varray(), CONNECT_DEFERRED);
+ goto_parent_hb->add_child(goto_parent);
+ goto_parent_hb->add_child(memnew(VSeparator));
+ goto_parent_hb->hide();
+
+ tool_blend = memnew(ToolButton);
+ tool_blend->set_toggle_mode(true);
+ tool_blend->set_button_group(bg);
+ top_hb->add_child(tool_blend);
+ tool_blend->set_pressed(true);
+ tool_blend->set_tooltip(TTR("Set the blending position within the space"));
+ tool_blend->connect("pressed", this, "_tool_switch", varray(3));
+
+ tool_select = memnew(ToolButton);
+ tool_select->set_toggle_mode(true);
+ tool_select->set_button_group(bg);
+ top_hb->add_child(tool_select);
+ tool_select->set_tooltip(TTR("Select and move points, create points with RMB."));
+ tool_select->connect("pressed", this, "_tool_switch", varray(0));
+
+ tool_create = memnew(ToolButton);
+ tool_create->set_toggle_mode(true);
+ tool_create->set_button_group(bg);
+ top_hb->add_child(tool_create);
+ tool_create->set_tooltip(TTR("Create points."));
+ tool_create->connect("pressed", this, "_tool_switch", varray(1));
+
+ tool_erase_sep = memnew(VSeparator);
+ top_hb->add_child(tool_erase_sep);
+ tool_erase = memnew(ToolButton);
+ top_hb->add_child(tool_erase);
+ tool_erase->set_tooltip(TTR("Erase points."));
+ tool_erase->connect("pressed", this, "_erase_selected");
+
+ top_hb->add_child(memnew(VSeparator));
+
+ snap = memnew(ToolButton);
+ snap->set_toggle_mode(true);
+ top_hb->add_child(snap);
+ snap->set_pressed(true);
+ snap->connect("pressed", this, "_snap_toggled");
+
+ snap_value = memnew(SpinBox);
+ top_hb->add_child(snap_value);
+ snap_value->set_min(0.01);
+ snap_value->set_step(0.01);
+ snap_value->set_max(1000);
+
+ edit_hb = memnew(HBoxContainer);
+ top_hb->add_child(edit_hb);
+ edit_hb->add_child(memnew(VSeparator));
+ edit_hb->add_child(memnew(Label(TTR("Point"))));
+
+ edit_value = memnew(SpinBox);
+ edit_hb->add_child(edit_value);
+ edit_value->set_min(-1000);
+ edit_value->set_max(1000);
+ edit_value->set_step(0.01);
+ edit_value->connect("value_changed", this, "_edit_point_pos");
+
+ open_editor = memnew(Button);
+ edit_hb->add_child(open_editor);
+ open_editor->set_text(TTR("Open Editor"));
+ open_editor->connect("pressed", this, "_open_editor", varray(), CONNECT_DEFERRED);
+
+ edit_hb->hide();
+ open_editor->hide();
+
+ VBoxContainer *main_vb = memnew(VBoxContainer);
+ add_child(main_vb);
+ main_vb->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ panel = memnew(PanelContainer);
+ panel->set_clip_contents(true);
+ main_vb->add_child(panel);
+ panel->set_h_size_flags(SIZE_EXPAND_FILL);
+ panel->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ blend_space_draw = memnew(Control);
+ blend_space_draw->connect("gui_input", this, "_blend_space_gui_input");
+ blend_space_draw->connect("draw", this, "_blend_space_draw");
+ blend_space_draw->set_focus_mode(FOCUS_ALL);
+
+ panel->add_child(blend_space_draw);
+
+ {
+ HBoxContainer *bottom_hb = memnew(HBoxContainer);
+ main_vb->add_child(bottom_hb);
+ bottom_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ min_value = memnew(SpinBox);
+ min_value->set_max(0);
+ min_value->set_min(-10000);
+ min_value->set_step(0.01);
+
+ max_value = memnew(SpinBox);
+ max_value->set_max(10000);
+ max_value->set_min(0.01);
+ max_value->set_step(0.01);
+
+ label_value = memnew(LineEdit);
+ label_value->set_expand_to_text_length(true);
+
+ // now add
+
+ bottom_hb->add_child(min_value);
+ bottom_hb->add_spacer();
+ bottom_hb->add_child(label_value);
+ bottom_hb->add_spacer();
+ bottom_hb->add_child(max_value);
+ }
+
+ snap_value->connect("value_changed", this, "_config_changed");
+ min_value->connect("value_changed", this, "_config_changed");
+ max_value->connect("value_changed", this, "_config_changed");
+ label_value->connect("text_changed", this, "_labels_changed");
+
+ error_panel = memnew(PanelContainer);
+ add_child(error_panel);
+
+ error_label = memnew(Label);
+ error_panel->add_child(error_label);
+ error_label->set_text("hmmm");
+
+ undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+ menu = memnew(PopupMenu);
+ add_child(menu);
+ menu->connect("index_pressed", this, "_add_menu_type");
+
+ animations_menu = memnew(PopupMenu);
+ menu->add_child(animations_menu);
+ animations_menu->set_name("animations");
+ animations_menu->connect("index_pressed", this, "_add_animation_type");
+
+ selected_point = -1;
+ dragging_selected = false;
+ dragging_selected_attempt = false;
+
+ set_custom_minimum_size(Size2(0, 150 * EDSCALE));
+}
diff --git a/editor/plugins/animation_blend_space_1d_editor.h b/editor/plugins/animation_blend_space_1d_editor.h
new file mode 100644
index 0000000000..52139626e6
--- /dev/null
+++ b/editor/plugins/animation_blend_space_1d_editor.h
@@ -0,0 +1,117 @@
+#ifndef ANIMATION_BLEND_SPACE_1D_EDITOR_H
+#define ANIMATION_BLEND_SPACE_1D_EDITOR_H
+
+#include "editor/editor_node.h"
+#include "editor/editor_plugin.h"
+#include "editor/property_editor.h"
+#include "scene/animation/animation_blend_space_1d.h"
+#include "scene/gui/button.h"
+#include "scene/gui/graph_edit.h"
+#include "scene/gui/popup.h"
+#include "scene/gui/tree.h"
+
+class AnimationNodeBlendSpace1DEditor : public VBoxContainer {
+
+ GDCLASS(AnimationNodeBlendSpace1DEditor, VBoxContainer)
+
+ Ref<AnimationNodeBlendSpace1D> blend_space;
+
+ HBoxContainer *goto_parent_hb;
+ ToolButton *goto_parent;
+
+ PanelContainer *panel;
+ ToolButton *tool_blend;
+ ToolButton *tool_select;
+ ToolButton *tool_create;
+ VSeparator *tool_erase_sep;
+ ToolButton *tool_erase;
+ ToolButton *snap;
+ SpinBox *snap_value;
+
+ LineEdit *label_value;
+ SpinBox *max_value;
+ SpinBox *min_value;
+
+ HBoxContainer *edit_hb;
+ SpinBox *edit_value;
+ Button *open_editor;
+
+ int selected_point;
+
+ Control *blend_space_draw;
+
+ PanelContainer *error_panel;
+ Label *error_label;
+
+ bool updating;
+
+ UndoRedo *undo_redo;
+
+ static AnimationNodeBlendSpace1DEditor *singleton;
+
+ void _blend_space_gui_input(const Ref<InputEvent> &p_event);
+ void _blend_space_draw();
+
+ void _update_space();
+
+ void _config_changed(double);
+ void _labels_changed(String);
+ void _snap_toggled();
+
+ PopupMenu *menu;
+ PopupMenu *animations_menu;
+ Vector<String> animations_to_add;
+ float add_point_pos;
+ Vector<float> points;
+
+ bool dragging_selected_attempt;
+ bool dragging_selected;
+ Vector2 drag_from;
+ Vector2 drag_ofs;
+
+ void _add_menu_type(int p_index);
+ void _add_animation_type(int p_index);
+
+ void _tool_switch(int p_tool);
+ void _update_edited_point_pos();
+ void _update_tool_erase();
+ void _erase_selected();
+ void _edit_point_pos(double);
+ void _open_editor();
+
+ void _goto_parent();
+
+ void _removed_from_graph();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ static AnimationNodeBlendSpace1DEditor *get_singleton() { return singleton; }
+ void edit(AnimationNodeBlendSpace1D *p_blend_space);
+ AnimationNodeBlendSpace1DEditor();
+};
+
+class AnimationNodeBlendSpace1DEditorPlugin : public EditorPlugin {
+
+ GDCLASS(AnimationNodeBlendSpace1DEditorPlugin, EditorPlugin)
+
+ AnimationNodeBlendSpace1DEditor *anim_tree_editor;
+ EditorNode *editor;
+ Button *button;
+
+public:
+ virtual String get_name() const { return "BlendSpace1D"; }
+
+ 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);
+
+ AnimationNodeBlendSpace1DEditorPlugin(EditorNode *p_node);
+ ~AnimationNodeBlendSpace1DEditorPlugin();
+};
+
+#endif // ANIMATION_BLEND_SPACE_1D_EDITOR_H
diff --git a/editor/plugins/animation_blend_space_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp
index 9118211bf2..8d17062248 100644
--- a/editor/plugins/animation_blend_space_editor.cpp
+++ b/editor/plugins/animation_blend_space_2d_editor.cpp
@@ -1,4 +1,4 @@
-#include "animation_blend_space_editor.h"
+#include "animation_blend_space_2d_editor.h"
#include "core/io/resource_loader.h"
#include "core/project_settings.h"
@@ -11,14 +11,14 @@
#include "scene/gui/panel.h"
#include "scene/main/viewport.h"
-void AnimationNodeBlendSpaceEditor::edit(AnimationNodeBlendSpace *p_blend_space) {
+void AnimationNodeBlendSpace2DEditor::edit(AnimationNodeBlendSpace2D *p_blend_space) {
if (blend_space.is_valid()) {
blend_space->disconnect("removed_from_graph", this, "_removed_from_graph");
}
if (p_blend_space) {
- blend_space = Ref<AnimationNodeBlendSpace>(p_blend_space);
+ blend_space = Ref<AnimationNodeBlendSpace2D>(p_blend_space);
} else {
blend_space.unref();
}
@@ -32,7 +32,7 @@ void AnimationNodeBlendSpaceEditor::edit(AnimationNodeBlendSpace *p_blend_space)
}
}
-void AnimationNodeBlendSpaceEditor::_blend_space_gui_input(const Ref<InputEvent> &p_event) {
+void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_scancode() == KEY_DELETE && !k->is_echo()) {
@@ -54,7 +54,7 @@ void AnimationNodeBlendSpaceEditor::_blend_space_gui_input(const Ref<InputEvent>
ClassDB::get_inheriters_from_class("AnimationRootNode", &classes);
menu->add_submenu_item(TTR("Add Animation"), "animations");
- AnimationGraphPlayer *gp = blend_space->get_graph_player();
+ AnimationTree *gp = blend_space->get_tree();
ERR_FAIL_COND(!gp);
if (gp && gp->has_node(gp->get_animation_player())) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player()));
@@ -203,7 +203,7 @@ void AnimationNodeBlendSpaceEditor::_blend_space_gui_input(const Ref<InputEvent>
blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space());
blend_pos += blend_space->get_min_space();
- blend_space->set_blend_pos(blend_pos);
+ blend_space->set_blend_position(blend_pos);
blend_space_draw->update();
}
@@ -237,12 +237,12 @@ void AnimationNodeBlendSpaceEditor::_blend_space_gui_input(const Ref<InputEvent>
blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space());
blend_pos += blend_space->get_min_space();
- blend_space->set_blend_pos(blend_pos);
+ blend_space->set_blend_position(blend_pos);
blend_space_draw->update();
}
}
-void AnimationNodeBlendSpaceEditor::_add_menu_type(int p_index) {
+void AnimationNodeBlendSpace2DEditor::_add_menu_type(int p_index) {
String type = menu->get_item_metadata(p_index);
@@ -265,7 +265,7 @@ void AnimationNodeBlendSpaceEditor::_add_menu_type(int p_index) {
blend_space_draw->update();
}
-void AnimationNodeBlendSpaceEditor::_add_animation_type(int p_index) {
+void AnimationNodeBlendSpace2DEditor::_add_animation_type(int p_index) {
Ref<AnimationNodeAnimation> anim;
anim.instance();
@@ -284,7 +284,7 @@ void AnimationNodeBlendSpaceEditor::_add_animation_type(int p_index) {
blend_space_draw->update();
}
-void AnimationNodeBlendSpaceEditor::_update_tool_erase() {
+void AnimationNodeBlendSpace2DEditor::_update_tool_erase() {
tool_erase->set_disabled(!(selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) && !(selected_triangle >= 0 && selected_triangle < blend_space->get_triangle_count()));
if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) {
Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point);
@@ -299,7 +299,7 @@ void AnimationNodeBlendSpaceEditor::_update_tool_erase() {
}
}
-void AnimationNodeBlendSpaceEditor::_tool_switch(int p_tool) {
+void AnimationNodeBlendSpace2DEditor::_tool_switch(int p_tool) {
making_triangle.clear();
if (p_tool == 2) {
@@ -325,7 +325,7 @@ void AnimationNodeBlendSpaceEditor::_tool_switch(int p_tool) {
blend_space_draw->update();
}
-void AnimationNodeBlendSpaceEditor::_blend_space_draw() {
+void AnimationNodeBlendSpace2DEditor::_blend_space_draw() {
Color linecolor = get_color("font_color", "Label");
Color linecolor_soft = linecolor;
@@ -352,7 +352,7 @@ void AnimationNodeBlendSpaceEditor::_blend_space_draw() {
}
if (blend_space->get_min_space().x < 0) {
- int x = (blend_space->get_max_space().x / (blend_space->get_max_space().x - blend_space->get_min_space().x)) * s.width;
+ int x = (-blend_space->get_min_space().x / (blend_space->get_max_space().x - blend_space->get_min_space().x)) * s.width;
blend_space_draw->draw_line(Point2(x, s.height - 1), Point2(x, s.height - 5 * EDSCALE), linecolor);
blend_space_draw->draw_string(font, Point2(x + 2 * EDSCALE, s.height - 2 * EDSCALE - font->get_height() + font->get_ascent()), "0", linecolor);
blend_space_draw->draw_line(Point2(x, s.height - 5 * EDSCALE), Point2(x, 0), linecolor_soft);
@@ -490,13 +490,13 @@ void AnimationNodeBlendSpaceEditor::_blend_space_draw() {
color.a *= 0.5;
}
- Vector2 point = blend_space->get_blend_pos();
+ Vector2 point = blend_space->get_blend_position();
point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space());
point *= s;
point.y = s.height - point.y;
if (blend_space->get_triangle_count()) {
- Vector2 closest = blend_space->get_closest_point(blend_space->get_blend_pos());
+ Vector2 closest = blend_space->get_closest_point(blend_space->get_blend_position());
closest = (closest - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space());
closest *= s;
closest.y = s.height - closest.y;
@@ -515,12 +515,12 @@ void AnimationNodeBlendSpaceEditor::_blend_space_draw() {
}
}
-void AnimationNodeBlendSpaceEditor::_snap_toggled() {
+void AnimationNodeBlendSpace2DEditor::_snap_toggled() {
blend_space_draw->update();
}
-void AnimationNodeBlendSpaceEditor::_update_space() {
+void AnimationNodeBlendSpace2DEditor::_update_space() {
if (updating)
return;
@@ -558,12 +558,12 @@ void AnimationNodeBlendSpaceEditor::_update_space() {
updating = false;
}
-void AnimationNodeBlendSpaceEditor::_config_changed(double) {
+void AnimationNodeBlendSpace2DEditor::_config_changed(double) {
if (updating)
return;
updating = true;
- undo_redo->create_action("Change BlendSpace Limits");
+ undo_redo->create_action("Change BlendSpace2D Limits");
undo_redo->add_do_method(blend_space.ptr(), "set_max_space", Vector2(max_x_value->get_value(), max_y_value->get_value()));
undo_redo->add_undo_method(blend_space.ptr(), "set_max_space", blend_space->get_max_space());
undo_redo->add_do_method(blend_space.ptr(), "set_min_space", Vector2(min_x_value->get_value(), min_y_value->get_value()));
@@ -578,12 +578,12 @@ void AnimationNodeBlendSpaceEditor::_config_changed(double) {
blend_space_draw->update();
}
-void AnimationNodeBlendSpaceEditor::_labels_changed(String) {
+void AnimationNodeBlendSpace2DEditor::_labels_changed(String) {
if (updating)
return;
updating = true;
- undo_redo->create_action("Change BlendSpace Labels", UndoRedo::MERGE_ENDS);
+ undo_redo->create_action("Change BlendSpace2D Labels", UndoRedo::MERGE_ENDS);
undo_redo->add_do_method(blend_space.ptr(), "set_x_label", label_x->get_text());
undo_redo->add_undo_method(blend_space.ptr(), "set_x_label", blend_space->get_x_label());
undo_redo->add_do_method(blend_space.ptr(), "set_y_label", label_y->get_text());
@@ -594,12 +594,12 @@ void AnimationNodeBlendSpaceEditor::_labels_changed(String) {
updating = false;
}
-void AnimationNodeBlendSpaceEditor::_erase_selected() {
+void AnimationNodeBlendSpace2DEditor::_erase_selected() {
if (selected_point != -1) {
updating = true;
- undo_redo->create_action("Remove BlendSpace Point");
+ undo_redo->create_action("Remove BlendSpace2D Point");
undo_redo->add_do_method(blend_space.ptr(), "remove_blend_point", selected_point);
undo_redo->add_undo_method(blend_space.ptr(), "add_blend_point", blend_space->get_blend_point_node(selected_point), blend_space->get_blend_point_position(selected_point), selected_point);
@@ -622,7 +622,7 @@ void AnimationNodeBlendSpaceEditor::_erase_selected() {
} else if (selected_triangle != -1) {
updating = true;
- undo_redo->create_action("Remove BlendSpace Triangle");
+ undo_redo->create_action("Remove BlendSpace2D Triangle");
undo_redo->add_do_method(blend_space.ptr(), "remove_triangle", selected_triangle);
undo_redo->add_undo_method(blend_space.ptr(), "add_triangle", blend_space->get_triangle_point(selected_triangle, 0), blend_space->get_triangle_point(selected_triangle, 1), blend_space->get_triangle_point(selected_triangle, 2), selected_triangle);
@@ -635,7 +635,7 @@ void AnimationNodeBlendSpaceEditor::_erase_selected() {
}
}
-void AnimationNodeBlendSpaceEditor::_update_edited_point_pos() {
+void AnimationNodeBlendSpace2DEditor::_update_edited_point_pos() {
if (updating)
return;
@@ -655,7 +655,7 @@ void AnimationNodeBlendSpaceEditor::_update_edited_point_pos() {
}
}
-void AnimationNodeBlendSpaceEditor::_edit_point_pos(double) {
+void AnimationNodeBlendSpace2DEditor::_edit_point_pos(double) {
if (updating)
return;
updating = true;
@@ -672,7 +672,7 @@ void AnimationNodeBlendSpaceEditor::_edit_point_pos(double) {
blend_space_draw->update();
}
-void AnimationNodeBlendSpaceEditor::_notification(int p_what) {
+void AnimationNodeBlendSpace2DEditor::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
error_panel->add_style_override("panel", get_stylebox("bg", "Tree"));
@@ -693,12 +693,12 @@ void AnimationNodeBlendSpaceEditor::_notification(int p_what) {
String error;
- if (!blend_space->get_graph_player()) {
- error = TTR("BlendSpace does not belong to an AnimationGraphPlayer node.");
- } else if (!blend_space->get_graph_player()->is_active()) {
- error = TTR("AnimationGraphPlayer is inactive.\nActivate to enable playback, check node warnings if activation fails.");
- } else if (blend_space->get_graph_player()->is_state_invalid()) {
- error = blend_space->get_graph_player()->get_invalid_state_reason();
+ if (!blend_space->get_tree()) {
+ error = TTR("BlendSpace2D does not belong to an AnimationTree node.");
+ } else if (!blend_space->get_tree()->is_active()) {
+ error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails.");
+ } else if (blend_space->get_tree()->is_state_invalid()) {
+ error = blend_space->get_tree()->get_invalid_state_reason();
} else if (blend_space->get_triangle_count() == 0) {
error = TTR("No triangles exist, so no blending can take place.");
}
@@ -714,7 +714,7 @@ void AnimationNodeBlendSpaceEditor::_notification(int p_what) {
}
}
-void AnimationNodeBlendSpaceEditor::_open_editor() {
+void AnimationNodeBlendSpace2DEditor::_open_editor() {
if (selected_point >= 0 && selected_point < blend_space->get_blend_point_count()) {
Ref<AnimationNode> an = blend_space->get_blend_point_node(selected_point);
@@ -723,16 +723,16 @@ void AnimationNodeBlendSpaceEditor::_open_editor() {
}
}
-void AnimationNodeBlendSpaceEditor::_goto_parent() {
+void AnimationNodeBlendSpace2DEditor::_goto_parent() {
EditorNode::get_singleton()->edit_item(blend_space->get_parent().ptr());
}
-void AnimationNodeBlendSpaceEditor::_removed_from_graph() {
+void AnimationNodeBlendSpace2DEditor::_removed_from_graph() {
EditorNode::get_singleton()->edit_item(NULL);
}
-void AnimationNodeBlendSpaceEditor::_auto_triangles_toggled() {
+void AnimationNodeBlendSpace2DEditor::_auto_triangles_toggled() {
undo_redo->create_action("Toggle Auto Triangles");
undo_redo->add_do_method(blend_space.ptr(), "set_auto_triangles", auto_triangles->is_pressed());
@@ -742,35 +742,35 @@ void AnimationNodeBlendSpaceEditor::_auto_triangles_toggled() {
undo_redo->commit_action();
}
-void AnimationNodeBlendSpaceEditor::_bind_methods() {
+void AnimationNodeBlendSpace2DEditor::_bind_methods() {
- ClassDB::bind_method("_blend_space_gui_input", &AnimationNodeBlendSpaceEditor::_blend_space_gui_input);
- ClassDB::bind_method("_blend_space_draw", &AnimationNodeBlendSpaceEditor::_blend_space_draw);
- ClassDB::bind_method("_config_changed", &AnimationNodeBlendSpaceEditor::_config_changed);
- ClassDB::bind_method("_labels_changed", &AnimationNodeBlendSpaceEditor::_labels_changed);
- ClassDB::bind_method("_update_space", &AnimationNodeBlendSpaceEditor::_update_space);
- ClassDB::bind_method("_snap_toggled", &AnimationNodeBlendSpaceEditor::_snap_toggled);
- ClassDB::bind_method("_tool_switch", &AnimationNodeBlendSpaceEditor::_tool_switch);
- ClassDB::bind_method("_erase_selected", &AnimationNodeBlendSpaceEditor::_erase_selected);
- ClassDB::bind_method("_update_tool_erase", &AnimationNodeBlendSpaceEditor::_update_tool_erase);
- ClassDB::bind_method("_edit_point_pos", &AnimationNodeBlendSpaceEditor::_edit_point_pos);
+ ClassDB::bind_method("_blend_space_gui_input", &AnimationNodeBlendSpace2DEditor::_blend_space_gui_input);
+ ClassDB::bind_method("_blend_space_draw", &AnimationNodeBlendSpace2DEditor::_blend_space_draw);
+ ClassDB::bind_method("_config_changed", &AnimationNodeBlendSpace2DEditor::_config_changed);
+ ClassDB::bind_method("_labels_changed", &AnimationNodeBlendSpace2DEditor::_labels_changed);
+ ClassDB::bind_method("_update_space", &AnimationNodeBlendSpace2DEditor::_update_space);
+ ClassDB::bind_method("_snap_toggled", &AnimationNodeBlendSpace2DEditor::_snap_toggled);
+ ClassDB::bind_method("_tool_switch", &AnimationNodeBlendSpace2DEditor::_tool_switch);
+ ClassDB::bind_method("_erase_selected", &AnimationNodeBlendSpace2DEditor::_erase_selected);
+ ClassDB::bind_method("_update_tool_erase", &AnimationNodeBlendSpace2DEditor::_update_tool_erase);
+ ClassDB::bind_method("_edit_point_pos", &AnimationNodeBlendSpace2DEditor::_edit_point_pos);
- ClassDB::bind_method("_add_menu_type", &AnimationNodeBlendSpaceEditor::_add_menu_type);
- ClassDB::bind_method("_add_animation_type", &AnimationNodeBlendSpaceEditor::_add_animation_type);
+ ClassDB::bind_method("_add_menu_type", &AnimationNodeBlendSpace2DEditor::_add_menu_type);
+ ClassDB::bind_method("_add_animation_type", &AnimationNodeBlendSpace2DEditor::_add_animation_type);
- ClassDB::bind_method("_update_edited_point_pos", &AnimationNodeBlendSpaceEditor::_update_edited_point_pos);
+ ClassDB::bind_method("_update_edited_point_pos", &AnimationNodeBlendSpace2DEditor::_update_edited_point_pos);
- ClassDB::bind_method("_open_editor", &AnimationNodeBlendSpaceEditor::_open_editor);
- ClassDB::bind_method("_goto_parent", &AnimationNodeBlendSpaceEditor::_goto_parent);
+ ClassDB::bind_method("_open_editor", &AnimationNodeBlendSpace2DEditor::_open_editor);
+ ClassDB::bind_method("_goto_parent", &AnimationNodeBlendSpace2DEditor::_goto_parent);
- ClassDB::bind_method("_removed_from_graph", &AnimationNodeBlendSpaceEditor::_removed_from_graph);
+ ClassDB::bind_method("_removed_from_graph", &AnimationNodeBlendSpace2DEditor::_removed_from_graph);
- ClassDB::bind_method("_auto_triangles_toggled", &AnimationNodeBlendSpaceEditor::_auto_triangles_toggled);
+ ClassDB::bind_method("_auto_triangles_toggled", &AnimationNodeBlendSpace2DEditor::_auto_triangles_toggled);
}
-AnimationNodeBlendSpaceEditor *AnimationNodeBlendSpaceEditor::singleton = NULL;
+AnimationNodeBlendSpace2DEditor *AnimationNodeBlendSpace2DEditor::singleton = NULL;
-AnimationNodeBlendSpaceEditor::AnimationNodeBlendSpaceEditor() {
+AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() {
singleton = this;
updating = false;
@@ -982,17 +982,17 @@ AnimationNodeBlendSpaceEditor::AnimationNodeBlendSpaceEditor() {
dragging_selected_attempt = false;
}
-void AnimationNodeBlendSpaceEditorPlugin::edit(Object *p_object) {
+void AnimationNodeBlendSpace2DEditorPlugin::edit(Object *p_object) {
- anim_tree_editor->edit(Object::cast_to<AnimationNodeBlendSpace>(p_object));
+ anim_tree_editor->edit(Object::cast_to<AnimationNodeBlendSpace2D>(p_object));
}
-bool AnimationNodeBlendSpaceEditorPlugin::handles(Object *p_object) const {
+bool AnimationNodeBlendSpace2DEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("AnimationNodeBlendSpace");
+ return p_object->is_class("AnimationNodeBlendSpace2D");
}
-void AnimationNodeBlendSpaceEditorPlugin::make_visible(bool p_visible) {
+void AnimationNodeBlendSpace2DEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
//editor->hide_animation_player_editors();
@@ -1009,15 +1009,15 @@ void AnimationNodeBlendSpaceEditorPlugin::make_visible(bool p_visible) {
}
}
-AnimationNodeBlendSpaceEditorPlugin::AnimationNodeBlendSpaceEditorPlugin(EditorNode *p_node) {
+AnimationNodeBlendSpace2DEditorPlugin::AnimationNodeBlendSpace2DEditorPlugin(EditorNode *p_node) {
editor = p_node;
- anim_tree_editor = memnew(AnimationNodeBlendSpaceEditor);
+ anim_tree_editor = memnew(AnimationNodeBlendSpace2DEditor);
anim_tree_editor->set_custom_minimum_size(Size2(0, 300));
- button = editor->add_bottom_panel_item(TTR("BlendSpace"), anim_tree_editor);
+ button = editor->add_bottom_panel_item(TTR("BlendSpace2D"), anim_tree_editor);
button->hide();
}
-AnimationNodeBlendSpaceEditorPlugin::~AnimationNodeBlendSpaceEditorPlugin() {
+AnimationNodeBlendSpace2DEditorPlugin::~AnimationNodeBlendSpace2DEditorPlugin() {
}
diff --git a/editor/plugins/animation_blend_space_editor.h b/editor/plugins/animation_blend_space_2d_editor.h
index 66d2ec6cae..a0e497804e 100644
--- a/editor/plugins/animation_blend_space_editor.h
+++ b/editor/plugins/animation_blend_space_2d_editor.h
@@ -1,10 +1,10 @@
-#ifndef ANIMATION_BLEND_SPACE_EDITOR_H
-#define ANIMATION_BLEND_SPACE_EDITOR_H
+#ifndef ANIMATION_BLEND_SPACE_2D_EDITOR_H
+#define ANIMATION_BLEND_SPACE_2D_EDITOR_H
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
#include "editor/property_editor.h"
-#include "scene/animation/animation_blend_space.h"
+#include "scene/animation/animation_blend_space_2d.h"
#include "scene/gui/button.h"
#include "scene/gui/graph_edit.h"
#include "scene/gui/popup.h"
@@ -13,11 +13,11 @@
@author Juan Linietsky <reduzio@gmail.com>
*/
-class AnimationNodeBlendSpaceEditor : public VBoxContainer {
+class AnimationNodeBlendSpace2DEditor : public VBoxContainer {
- GDCLASS(AnimationNodeBlendSpaceEditor, VBoxContainer);
+ GDCLASS(AnimationNodeBlendSpace2DEditor, VBoxContainer);
- Ref<AnimationNodeBlendSpace> blend_space;
+ Ref<AnimationNodeBlendSpace2D> blend_space;
HBoxContainer *goto_parent_hb;
ToolButton *goto_parent;
@@ -59,7 +59,7 @@ class AnimationNodeBlendSpaceEditor : public VBoxContainer {
UndoRedo *undo_redo;
- static AnimationNodeBlendSpaceEditor *singleton;
+ static AnimationNodeBlendSpace2DEditor *singleton;
void _blend_space_gui_input(const Ref<InputEvent> &p_event);
void _blend_space_draw();
@@ -104,27 +104,27 @@ protected:
static void _bind_methods();
public:
- static AnimationNodeBlendSpaceEditor *get_singleton() { return singleton; }
- void edit(AnimationNodeBlendSpace *p_blend_space);
- AnimationNodeBlendSpaceEditor();
+ static AnimationNodeBlendSpace2DEditor *get_singleton() { return singleton; }
+ void edit(AnimationNodeBlendSpace2D *p_blend_space);
+ AnimationNodeBlendSpace2DEditor();
};
-class AnimationNodeBlendSpaceEditorPlugin : public EditorPlugin {
+class AnimationNodeBlendSpace2DEditorPlugin : public EditorPlugin {
- GDCLASS(AnimationNodeBlendSpaceEditorPlugin, EditorPlugin);
+ GDCLASS(AnimationNodeBlendSpace2DEditorPlugin, EditorPlugin);
- AnimationNodeBlendSpaceEditor *anim_tree_editor;
+ AnimationNodeBlendSpace2DEditor *anim_tree_editor;
EditorNode *editor;
Button *button;
public:
- virtual String get_name() const { return "BlendSpace"; }
+ virtual String get_name() const { return "BlendSpace2D"; }
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);
- AnimationNodeBlendSpaceEditorPlugin(EditorNode *p_node);
- ~AnimationNodeBlendSpaceEditorPlugin();
+ AnimationNodeBlendSpace2DEditorPlugin(EditorNode *p_node);
+ ~AnimationNodeBlendSpace2DEditorPlugin();
};
-#endif // ANIMATION_BLEND_SPACE_EDITOR_H
+#endif // ANIMATION_BLEND_SPACE_2D_EDITOR_H
diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp
index 0875c93a16..7d0e2929bd 100644
--- a/editor/plugins/animation_blend_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp
@@ -169,7 +169,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() {
ProgressBar *pb = memnew(ProgressBar);
- AnimationGraphPlayer *player = anim->get_graph_player();
+ AnimationTree *player = anim->get_tree();
if (player->has_node(player->get_animation_player())) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(player->get_node(player->get_animation_player()));
if (ap) {
@@ -417,14 +417,14 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano
if (updating || _filter_edit != anode)
return false;
- NodePath player_path = anode->get_graph_player()->get_animation_player();
+ NodePath player_path = anode->get_tree()->get_animation_player();
- if (!anode->get_graph_player()->has_node(player_path)) {
+ if (!anode->get_tree()->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));
+ AnimationPlayer *player = Object::cast_to<AnimationPlayer>(anode->get_tree()->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;
@@ -603,12 +603,12 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) {
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 (!blend_tree->get_tree()) {
+ error = TTR("BlendTree does not belong to an AnimationTree node.");
+ } else if (!blend_tree->get_tree()->is_active()) {
+ error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails.");
+ } else if (blend_tree->get_tree()->is_state_invalid()) {
+ error = blend_tree->get_tree()->get_invalid_state_reason();
}
if (error != error_label->get_text()) {
@@ -624,13 +624,13 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) {
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()) {
+ if (blend_tree->get_tree() && !blend_tree->get_tree()->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();
+ AnimationTree *graph_player = blend_tree->get_tree();
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()));
@@ -774,7 +774,9 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() {
add_options.push_back(AddOption("TimeScale", "AnimationNodeTimeScale"));
add_options.push_back(AddOption("Transition", "AnimationNodeTransition"));
add_options.push_back(AddOption("BlendTree", "AnimationNodeBlendTree"));
- add_options.push_back(AddOption("BlendSpace", "AnimationNodeBlendSpace"));
+ add_options.push_back(AddOption("BlendSpace1D", "AnimationNodeBlendSpace1D"));
+ add_options.push_back(AddOption("BlendSpace2D", "AnimationNodeBlendSpace2D"));
+ add_options.push_back(AddOption("StateMachine", "AnimationNodeStateMachine"));
_update_options_menu();
error_panel = memnew(PanelContainer);
diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp
new file mode 100644
index 0000000000..04bd5f0cec
--- /dev/null
+++ b/editor/plugins/animation_state_machine_editor.cpp
@@ -0,0 +1,1313 @@
+#include "animation_state_machine_editor.h"
+
+#include "core/io/resource_loader.h"
+#include "core/project_settings.h"
+#include "math/delaunay.h"
+#include "os/input.h"
+#include "os/keyboard.h"
+#include "scene/animation/animation_blend_tree.h"
+#include "scene/animation/animation_player.h"
+#include "scene/gui/menu_button.h"
+#include "scene/gui/panel.h"
+#include "scene/main/viewport.h"
+
+void AnimationNodeStateMachineEditor::edit(AnimationNodeStateMachine *p_state_machine) {
+
+ if (state_machine.is_valid()) {
+ state_machine->disconnect("removed_from_graph", this, "_removed_from_graph");
+ }
+
+ if (p_state_machine) {
+ state_machine = Ref<AnimationNodeStateMachine>(p_state_machine);
+ } else {
+ state_machine.unref();
+ }
+
+ if (state_machine.is_null()) {
+ hide();
+ } else {
+ state_machine->connect("removed_from_graph", this, "_removed_from_graph");
+
+ selected_transition_from = StringName();
+ selected_transition_to = StringName();
+ selected_node = StringName();
+ _update_mode();
+ _update_graph();
+ }
+}
+
+void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEvent> &p_event) {
+
+ Ref<InputEventKey> k = p_event;
+ if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_scancode() == KEY_DELETE && !k->is_echo()) {
+ if (selected_node != StringName() || selected_transition_to != StringName() || selected_transition_from != StringName()) {
+ _erase_selected();
+ accept_event();
+ }
+ }
+
+ Ref<InputEventMouseButton> mb = p_event;
+
+ //Add new node
+ if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == BUTTON_RIGHT) || (tool_create->is_pressed() && mb->get_button_index() == BUTTON_LEFT))) {
+ menu->clear();
+ animations_menu->clear();
+ animations_to_add.clear();
+ List<StringName> classes;
+ classes.sort_custom<StringName::AlphCompare>();
+
+ ClassDB::get_inheriters_from_class("AnimationRootNode", &classes);
+ menu->add_submenu_item(TTR("Add Animation"), "animations");
+
+ AnimationTree *gp = state_machine->get_tree();
+ ERR_FAIL_COND(!gp);
+ 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);
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ animations_menu->add_icon_item(get_icon("Animation", "EditorIcons"), E->get());
+ animations_to_add.push_back(E->get());
+ }
+ }
+ }
+
+ for (List<StringName>::Element *E = classes.front(); E; E = E->next()) {
+
+ String name = String(E->get()).replace_first("AnimationNode", "");
+ if (name == "Animation")
+ continue; // nope
+ int idx = menu->get_item_count();
+ menu->add_item(vformat("Add %s", name));
+ menu->set_item_metadata(idx, E->get());
+ }
+
+ menu->set_global_position(state_machine_draw->get_global_transform().xform(mb->get_position()));
+ menu->popup();
+ add_node_pos = mb->get_position() / EDSCALE + state_machine->get_graph_offset();
+ }
+
+ // select node or push a field inside
+ if (mb.is_valid() && !mb->get_shift() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == BUTTON_LEFT) {
+
+ selected_transition_from = StringName();
+ selected_transition_to = StringName();
+ selected_node = StringName();
+
+ for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
+
+ if (node_rects[i].play.has_point(mb->get_position())) { //edit name
+ if (play_mode->get_selected() == 1 || !state_machine->is_playing()) {
+ //start
+ state_machine->start(node_rects[i].node_name);
+ } else {
+ //travel
+ if (!state_machine->travel(node_rects[i].node_name)) {
+
+ state_machine->start(node_rects[i].node_name);
+ //removing this due to usability..
+ //error_time = 5;
+ //error_text = vformat(TTR("No path found from '%s' to '%s'."), state_machine->get_current_node(), node_rects[i].node_name);
+ }
+ }
+ state_machine_draw->update();
+ return;
+ }
+
+ if (node_rects[i].name.has_point(mb->get_position())) { //edit name
+
+ Ref<StyleBox> line_sb = get_stylebox("normal", "LineEdit");
+
+ Rect2 edit_rect = node_rects[i].name;
+ edit_rect.position -= line_sb->get_offset();
+ edit_rect.size += line_sb->get_minimum_size();
+
+ name_edit->set_global_position(state_machine_draw->get_global_transform().xform(edit_rect.position));
+ name_edit->set_size(edit_rect.size);
+ name_edit->set_text(node_rects[i].node_name);
+ name_edit->show_modal();
+ name_edit->grab_focus();
+ name_edit->select_all();
+
+ prev_name = node_rects[i].node_name;
+ return;
+ }
+
+ if (node_rects[i].edit.has_point(mb->get_position())) { //edit name
+ call_deferred("_open_editor", node_rects[i].node_name);
+ return;
+ }
+
+ if (node_rects[i].node.has_point(mb->get_position())) { //select node since nothing else was selected
+ selected_node = node_rects[i].node_name;
+
+ Ref<AnimationNode> anode = state_machine->get_node(selected_node);
+ EditorNode::get_singleton()->push_item(anode.ptr(), "", true);
+ state_machine_draw->update();
+ dragging_selected_attempt = true;
+ dragging_selected = false;
+ drag_from = mb->get_position();
+ snap_x = StringName();
+ snap_y = StringName();
+ _update_mode();
+ return;
+ }
+ }
+
+ //test the lines now
+ int closest = -1;
+ float closest_d = 1e20;
+ for (int i = 0; i < transition_lines.size(); i++) {
+
+ Vector2 s[2] = {
+ transition_lines[i].from,
+ transition_lines[i].to
+ };
+ Vector2 cpoint = Geometry::get_closest_point_to_segment_2d(mb->get_position(), s);
+ float d = cpoint.distance_to(mb->get_position());
+ if (d > transition_lines[i].width) {
+ continue;
+ }
+
+ if (d < closest_d) {
+ closest = i;
+ closest_d = d;
+ }
+ }
+
+ if (closest >= 0) {
+ selected_transition_from = transition_lines[closest].from_node;
+ selected_transition_to = transition_lines[closest].to_node;
+
+ Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(closest);
+ EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
+ }
+
+ state_machine_draw->update();
+ _update_mode();
+ }
+
+ //end moving node
+ if (mb.is_valid() && dragging_selected_attempt && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) {
+
+ if (dragging_selected) {
+
+ Ref<AnimationNode> an = state_machine->get_node(selected_node);
+ updating = true;
+ undo_redo->create_action("Move Node");
+ undo_redo->add_do_method(an.ptr(), "set_position", an->get_position() + drag_ofs / EDSCALE);
+ undo_redo->add_undo_method(an.ptr(), "set_position", an->get_position());
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+ }
+ snap_x = StringName();
+ snap_y = StringName();
+
+ dragging_selected_attempt = false;
+ dragging_selected = false;
+ state_machine_draw->update();
+ }
+
+ //connect nodes
+ if (mb.is_valid() && ((tool_select->is_pressed() && mb->get_shift()) || tool_connect->is_pressed()) && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) {
+
+ for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
+ if (node_rects[i].node.has_point(mb->get_position())) { //select node since nothing else was selected
+ connecting = true;
+ connecting_from = node_rects[i].node_name;
+ connecting_to = mb->get_position();
+ connecting_to_node = StringName();
+ return;
+ }
+ }
+ }
+
+ //end connecting nodes
+ if (mb.is_valid() && connecting && mb->get_button_index() == BUTTON_LEFT && !mb->is_pressed()) {
+
+ if (connecting_to_node != StringName()) {
+
+ if (state_machine->has_transition(connecting_from, connecting_to_node)) {
+ EditorNode::get_singleton()->show_warning("Transition exists!");
+
+ } else {
+
+ Ref<AnimationNodeStateMachineTransition> tr;
+ tr.instance();
+ tr->set_switch_mode(AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected()));
+
+ updating = true;
+ undo_redo->create_action("Add Transition");
+ undo_redo->add_do_method(state_machine.ptr(), "add_transition", connecting_from, connecting_to_node, tr);
+ undo_redo->add_undo_method(state_machine.ptr(), "remove_transition", connecting_from, connecting_to_node);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+
+ selected_transition_from = connecting_from;
+ selected_transition_to = connecting_to_node;
+
+ EditorNode::get_singleton()->push_item(tr.ptr(), "", true);
+ _update_mode();
+ }
+ }
+ connecting_to_node = StringName();
+ connecting = false;
+ state_machine_draw->update();
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+
+ //pan window
+ if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_MIDDLE) {
+
+ h_scroll->set_value(h_scroll->get_value() - mm->get_relative().x);
+ v_scroll->set_value(v_scroll->get_value() - mm->get_relative().y);
+ }
+
+ //move mouse while connecting
+ if (mm.is_valid() && connecting) {
+
+ connecting_to = mm->get_position();
+ connecting_to_node = StringName();
+ state_machine_draw->update();
+
+ for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
+ if (node_rects[i].node_name != connecting_from && node_rects[i].node.has_point(connecting_to)) { //select node since nothing else was selected
+ connecting_to_node = node_rects[i].node_name;
+ return;
+ }
+ }
+ }
+
+ //move mouse while moving a node
+ if (mm.is_valid() && dragging_selected_attempt) {
+
+ dragging_selected = true;
+ drag_ofs = mm->get_position() - drag_from;
+ snap_x = StringName();
+ snap_y = StringName();
+ {
+ //snap
+ Vector2 cpos = state_machine->get_node(selected_node)->get_position() + drag_ofs / EDSCALE;
+ List<StringName> nodes;
+ state_machine->get_node_list(&nodes);
+
+ float best_d_x = 1e20;
+ float best_d_y = 1e20;
+
+ for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
+ if (E->get() == selected_node)
+ continue;
+ Vector2 npos = state_machine->get_node(E->get())->get_position();
+
+ float d_x = ABS(npos.x - cpos.x);
+ if (d_x < MIN(5, best_d_x)) {
+ drag_ofs.x -= cpos.x - npos.x;
+ best_d_x = d_x;
+ snap_x = E->get();
+ }
+
+ float d_y = ABS(npos.y - cpos.y);
+ if (d_y < MIN(5, best_d_y)) {
+ drag_ofs.y -= cpos.y - npos.y;
+ best_d_y = d_y;
+ snap_y = E->get();
+ }
+ }
+ }
+
+ state_machine_draw->update();
+ }
+
+ //put ibeam (text cursor) over names to make it clearer that they are editable
+ if (mm.is_valid()) {
+
+ state_machine_draw->grab_focus();
+
+ bool over_text_now = false;
+ String new_over_node = StringName();
+ int new_over_node_what = -1;
+ if (tool_select->is_pressed()) {
+
+ for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order
+
+ if (node_rects[i].name.has_point(mm->get_position())) {
+ over_text_now = true;
+ break;
+ }
+
+ if (node_rects[i].node.has_point(mm->get_position())) {
+ new_over_node = node_rects[i].node_name;
+ if (node_rects[i].play.has_point(mm->get_position())) {
+ new_over_node_what = 0;
+ }
+ if (node_rects[i].edit.has_point(mm->get_position())) {
+ new_over_node_what = 1;
+ }
+ }
+ }
+ }
+
+ if (new_over_node != over_node || new_over_node_what != over_node_what) {
+ over_node = new_over_node;
+ over_node_what = new_over_node_what;
+ state_machine_draw->update();
+ }
+
+ if (over_text != over_text_now) {
+
+ if (over_text_now) {
+ state_machine_draw->set_default_cursor_shape(CURSOR_IBEAM);
+ } else {
+ state_machine_draw->set_default_cursor_shape(CURSOR_ARROW);
+ }
+
+ over_text = over_text_now;
+ }
+ }
+}
+
+void AnimationNodeStateMachineEditor::_add_menu_type(int p_index) {
+
+ String type = menu->get_item_metadata(p_index);
+
+ Object *obj = ClassDB::instance(type);
+ ERR_FAIL_COND(!obj);
+ AnimationNode *an = Object::cast_to<AnimationNode>(obj);
+ ERR_FAIL_COND(!an);
+
+ Ref<AnimationNode> node(an);
+ node->set_position(add_node_pos);
+
+ String base_name = type.replace_first("AnimationNode", "");
+ int base = 1;
+ String name = base_name;
+ while (state_machine->has_node(name)) {
+ base++;
+ name = base_name + " " + itos(base);
+ }
+
+ updating = true;
+ undo_redo->create_action("Add Node");
+ undo_redo->add_do_method(state_machine.ptr(), "add_node", name, node);
+ undo_redo->add_undo_method(state_machine.ptr(), "remove_node", name);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+
+ state_machine_draw->update();
+}
+
+void AnimationNodeStateMachineEditor::_add_animation_type(int p_index) {
+
+ Ref<AnimationNodeAnimation> anim;
+ anim.instance();
+
+ anim->set_animation(animations_to_add[p_index]);
+
+ String base_name = animations_to_add[p_index];
+ int base = 1;
+ String name = base_name;
+ while (state_machine->has_node(name)) {
+ base++;
+ name = base_name + " " + itos(base);
+ }
+
+ anim->set_position(add_node_pos);
+
+ updating = true;
+ undo_redo->create_action("Add Node");
+ undo_redo->add_do_method(state_machine.ptr(), "add_node", name, anim);
+ undo_redo->add_undo_method(state_machine.ptr(), "remove_node", name);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+
+ state_machine_draw->update();
+}
+
+void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, bool p_auto_advance) {
+
+ Color linecolor = get_color("font_color", "Label");
+ Color icon_color(1, 1, 1);
+ Color accent = get_color("accent_color", "Editor");
+
+ if (!p_enabled) {
+ linecolor.a *= 0.2;
+ icon_color.a *= 0.2;
+ accent.a *= 0.6;
+ }
+
+ Ref<Texture> icons[6] = {
+ get_icon("TransitionImmediateBig", "EditorIcons"),
+ get_icon("TransitionSyncBig", "EditorIcons"),
+ get_icon("TransitionEndBig", "EditorIcons"),
+ get_icon("TransitionImmediateAutoBig", "EditorIcons"),
+ get_icon("TransitionSyncAutoBig", "EditorIcons"),
+ get_icon("TransitionEndAutoBig", "EditorIcons")
+ };
+
+ if (p_selected) {
+ state_machine_draw->draw_line(p_from, p_to, accent, 6, true);
+ }
+
+ if (p_travel) {
+ linecolor = accent;
+ linecolor.set_hsv(1.0, linecolor.get_s(), linecolor.get_v());
+ }
+ state_machine_draw->draw_line(p_from, p_to, linecolor, 2, true);
+
+ Ref<Texture> icon = icons[p_mode + (p_auto_advance ? 3 : 0)];
+
+ Transform2D xf;
+ xf.elements[0] = (p_to - p_from).normalized();
+ xf.elements[1] = xf.elements[0].tangent();
+ xf.elements[2] = (p_from + p_to) * 0.5 - xf.elements[1] * icon->get_height() * 0.5 - xf.elements[0] * icon->get_height() * 0.5;
+
+ state_machine_draw->draw_set_transform_matrix(xf);
+ state_machine_draw->draw_texture(icon, Vector2(), icon_color);
+ state_machine_draw->draw_set_transform_matrix(Transform2D());
+}
+
+void AnimationNodeStateMachineEditor::_clip_src_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect) {
+
+ if (r_to == r_from)
+ return;
+
+ //this could be optimized...
+ Vector2 n = (r_to - r_from).normalized();
+ while (p_rect.has_point(r_from)) {
+ r_from += n;
+ }
+}
+
+void AnimationNodeStateMachineEditor::_clip_dst_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect) {
+
+ if (r_to == r_from)
+ return;
+
+ //this could be optimized...
+ Vector2 n = (r_to - r_from).normalized();
+ while (p_rect.has_point(r_to)) {
+ r_to -= n;
+ }
+}
+
+void AnimationNodeStateMachineEditor::_state_machine_draw() {
+
+ Ref<StyleBox> style = get_stylebox("frame", "GraphNode");
+ Ref<StyleBox> style_selected = get_stylebox("selectedframe", "GraphNode");
+
+ Ref<Font> font = get_font("title_font", "GraphNode");
+ Color font_color = get_color("title_color", "GraphNode");
+ Ref<Texture> play = get_icon("Play", "EditorIcons");
+ Ref<Texture> auto_play = get_icon("AutoPlay", "EditorIcons");
+ Ref<Texture> edit = get_icon("Edit", "EditorIcons");
+ Color accent = get_color("accent_color", "Editor");
+ Color linecolor = get_color("font_color", "Label");
+ linecolor.a *= 0.3;
+ Ref<StyleBox> playing_overlay = get_stylebox("position", "GraphNode");
+
+ bool playing = state_machine->is_playing();
+ StringName current = state_machine->get_current_node();
+ StringName blend_from = state_machine->get_blend_from_node();
+ Vector<StringName> travel_path = state_machine->get_travel_path();
+
+ if (state_machine_draw->has_focus()) {
+ state_machine_draw->draw_rect(Rect2(Point2(), state_machine_draw->get_size()), accent, false);
+ }
+ int sep = 3 * EDSCALE;
+
+ List<StringName> nodes;
+ state_machine->get_node_list(&nodes);
+
+ node_rects.clear();
+ Rect2 scroll_range(Point2(), state_machine_draw->get_size());
+
+ //snap lines
+ if (dragging_selected) {
+
+ Vector2 from = (state_machine->get_node(selected_node)->get_position() * EDSCALE) + drag_ofs - state_machine->get_graph_offset() * EDSCALE;
+ if (snap_x != StringName()) {
+ Vector2 to = (state_machine->get_node(snap_x)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE;
+ state_machine_draw->draw_line(from, to, linecolor, 2);
+ }
+ if (snap_y != StringName()) {
+ Vector2 to = (state_machine->get_node(snap_y)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE;
+ state_machine_draw->draw_line(from, to, linecolor, 2);
+ }
+ }
+
+ //pre pass nodes so we know the rectangles
+ for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
+
+ Ref<AnimationNode> anode = state_machine->get_node(E->get());
+ String name = E->get();
+ bool needs_editor = EditorNode::get_singleton()->item_has_editor(anode.ptr());
+ Ref<StyleBox> sb = E->get() == selected_node ? style_selected : style;
+
+ Size2 s = sb->get_minimum_size();
+ int strsize = font->get_string_size(name).width;
+ s.width += strsize;
+ s.height += MAX(font->get_height(), play->get_height());
+ s.width += sep + play->get_width();
+ if (needs_editor) {
+ s.width += sep + edit->get_width();
+ }
+
+ Vector2 offset;
+ offset += anode->get_position() * EDSCALE;
+ if (selected_node == E->get() && dragging_selected) {
+ offset += drag_ofs;
+ }
+ offset -= s / 2;
+ offset = offset.floor();
+
+ //prepre rect
+
+ NodeRect nr;
+ nr.node = Rect2(offset, s);
+ nr.node_name = E->get();
+
+ scroll_range = scroll_range.merge(nr.node); //merge with range
+
+ //now scroll it to draw
+ nr.node.position -= state_machine->get_graph_offset() * EDSCALE;
+
+ node_rects.push_back(nr);
+ }
+
+ transition_lines.clear();
+
+ //draw conecting line for potential new transition
+ if (connecting) {
+ Vector2 from = (state_machine->get_node(connecting_from)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE;
+ Vector2 to;
+ if (connecting_to_node != StringName()) {
+ to = (state_machine->get_node(connecting_to_node)->get_position() * EDSCALE) - state_machine->get_graph_offset() * EDSCALE;
+ } else {
+ to = connecting_to;
+ }
+
+ for (int i = 0; i < node_rects.size(); i++) {
+ if (node_rects[i].node_name == connecting_from) {
+ _clip_src_line_to_rect(from, to, node_rects[i].node);
+ }
+ if (node_rects[i].node_name == connecting_to_node) {
+ _clip_dst_line_to_rect(from, to, node_rects[i].node);
+ }
+ }
+
+ _connection_draw(from, to, AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected()), true, false, false, false);
+ }
+
+ Ref<Texture> tr_reference_icon = get_icon("TransitionImmediateBig", "EditorIcons");
+ float tr_bidi_offset = int(tr_reference_icon->get_height() * 0.8);
+
+ //draw transition lines
+ for (int i = 0; i < state_machine->get_transition_count(); i++) {
+
+ TransitionLine tl;
+ tl.from_node = state_machine->get_transition_from(i);
+ Vector2 ofs_from = (dragging_selected && tl.from_node == selected_node) ? drag_ofs : Vector2();
+ tl.from = (state_machine->get_node(tl.from_node)->get_position() * EDSCALE) + ofs_from - state_machine->get_graph_offset() * EDSCALE;
+
+ tl.to_node = state_machine->get_transition_to(i);
+ Vector2 ofs_to = (dragging_selected && tl.to_node == selected_node) ? drag_ofs : Vector2();
+ tl.to = (state_machine->get_node(tl.to_node)->get_position() * EDSCALE) + ofs_to - state_machine->get_graph_offset() * EDSCALE;
+
+ Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(i);
+ tl.disabled = tr->is_disabled();
+ tl.auto_advance = tr->has_auto_advance();
+ tl.mode = tr->get_switch_mode();
+ tl.width = tr_bidi_offset;
+
+ if (state_machine->has_transition(tl.to_node, tl.from_node)) { //offset if same exists
+ Vector2 offset = -(tl.from - tl.to).normalized().tangent() * tr_bidi_offset;
+ tl.from += offset;
+ tl.to += offset;
+ }
+
+ for (int i = 0; i < node_rects.size(); i++) {
+ if (node_rects[i].node_name == tl.from_node) {
+ _clip_src_line_to_rect(tl.from, tl.to, node_rects[i].node);
+ }
+ if (node_rects[i].node_name == tl.to_node) {
+ _clip_dst_line_to_rect(tl.from, tl.to, node_rects[i].node);
+ }
+ }
+
+ bool selected = selected_transition_from == tl.from_node && selected_transition_to == tl.to_node;
+
+ bool travel = false;
+
+ if (blend_from == tl.from_node && current == tl.to_node) {
+ travel = true;
+ }
+
+ if (travel_path.size()) {
+
+ if (current == tl.from_node && travel_path[0] == tl.to_node) {
+ travel = true;
+ } else {
+ for (int j = 0; j < travel_path.size() - 1; j++) {
+ if (travel_path[j] == tl.from_node && travel_path[j + 1] == tl.to_node) {
+ travel = true;
+ break;
+ }
+ }
+ }
+ }
+ _connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, selected, travel, tl.auto_advance);
+
+ transition_lines.push_back(tl);
+ }
+
+ //draw actual nodes
+ for (int i = 0; i < node_rects.size(); i++) {
+
+ String name = node_rects[i].node_name;
+ Ref<AnimationNode> anode = state_machine->get_node(name);
+ bool needs_editor = EditorNode::get_singleton()->item_has_editor(anode.ptr());
+ Ref<StyleBox> sb = name == selected_node ? style_selected : style;
+ int strsize = font->get_string_size(name).width;
+
+ NodeRect &nr = node_rects[i];
+
+ Vector2 offset = nr.node.position;
+ int h = nr.node.size.height;
+
+ //prepre rect
+
+ //now scroll it to draw
+ state_machine_draw->draw_style_box(sb, nr.node);
+
+ if (playing && (blend_from == name || current == name || travel_path.find(name) != -1)) {
+ state_machine_draw->draw_style_box(playing_overlay, nr.node);
+ }
+
+ bool onstart = state_machine->get_start_node() == name;
+ if (onstart) {
+ state_machine_draw->draw_string(font, offset + Vector2(0, -font->get_height() - 3 * EDSCALE + font->get_ascent()), TTR("Start"), font_color);
+ }
+
+ if (state_machine->get_end_node() == name) {
+
+ int endofs = nr.node.size.x - font->get_string_size(TTR("End")).x;
+ state_machine_draw->draw_string(font, offset + Vector2(endofs, -font->get_height() - 3 * EDSCALE + font->get_ascent()), TTR("End"), font_color);
+ }
+
+ offset.x += sb->get_offset().x;
+
+ nr.play.position = offset + Vector2(0, (h - play->get_height()) / 2).floor();
+ nr.play.size = play->get_size();
+
+ Ref<Texture> play_tex = onstart ? auto_play : play;
+
+ if (over_node == name && over_node_what == 0) {
+ state_machine_draw->draw_texture(play_tex, nr.play.position, accent);
+ } else {
+ state_machine_draw->draw_texture(play_tex, nr.play.position);
+ }
+ offset.x += sep + play->get_width();
+
+ nr.name.position = offset + Vector2(0, (h - font->get_height()) / 2).floor();
+ nr.name.size = Vector2(strsize, font->get_height());
+
+ state_machine_draw->draw_string(font, nr.name.position + Vector2(0, font->get_ascent()), name, font_color);
+ offset.x += strsize + sep;
+
+ if (needs_editor) {
+ nr.edit.position = offset + Vector2(0, (h - edit->get_height()) / 2).floor();
+ nr.edit.size = edit->get_size();
+
+ if (over_node == name && over_node_what == 1) {
+ state_machine_draw->draw_texture(edit, nr.edit.position, accent);
+ } else {
+ state_machine_draw->draw_texture(edit, nr.edit.position);
+ }
+ offset.x += sep + edit->get_width();
+ }
+ }
+
+ scroll_range = scroll_range.grow(200 * EDSCALE);
+
+ //adjust scrollbars
+ updating = true;
+ h_scroll->set_min(scroll_range.position.x);
+ h_scroll->set_max(scroll_range.position.x + scroll_range.size.x);
+ h_scroll->set_page(state_machine_draw->get_size().x);
+ h_scroll->set_value(state_machine->get_graph_offset().x);
+
+ v_scroll->set_min(scroll_range.position.y);
+ v_scroll->set_max(scroll_range.position.y + scroll_range.size.y);
+ v_scroll->set_page(state_machine_draw->get_size().y);
+ v_scroll->set_value(state_machine->get_graph_offset().y);
+ updating = false;
+
+ state_machine_play_pos->update();
+}
+
+void AnimationNodeStateMachineEditor::_state_machine_pos_draw() {
+
+ if (!state_machine->is_playing())
+ return;
+
+ int idx = -1;
+ for (int i = 0; node_rects.size(); i++) {
+ if (node_rects[i].node_name == state_machine->get_current_node()) {
+ idx = i;
+ break;
+ }
+ }
+
+ if (idx == -1)
+ return;
+
+ NodeRect &nr = node_rects[idx];
+
+ Vector2 from;
+ from.x = nr.play.position.x;
+ from.y = (nr.play.position.y + nr.play.size.y + nr.node.position.y + nr.node.size.y) * 0.5;
+
+ Vector2 to;
+ if (nr.edit.size.x) {
+ to.x = nr.edit.position.x + nr.edit.size.x;
+ } else {
+ to.x = nr.name.position.x + nr.name.size.x;
+ }
+ to.y = from.y;
+
+ float len = MAX(0.0001, state_machine->get_current_length());
+
+ float pos = CLAMP(state_machine->get_current_play_pos(), 0, len);
+ float c = pos / len;
+ Color fg = get_color("font_color", "Label");
+ Color bg = fg;
+ bg.a *= 0.3;
+
+ state_machine_play_pos->draw_line(from, to, bg, 2);
+
+ to = from.linear_interpolate(to, c);
+
+ state_machine_play_pos->draw_line(from, to, fg, 2);
+}
+
+void AnimationNodeStateMachineEditor::_update_graph() {
+
+ if (updating)
+ return;
+
+ updating = true;
+
+ if (state_machine->get_parent().is_valid()) {
+ goto_parent_hbox->show();
+ } else {
+ goto_parent_hbox->hide();
+ }
+
+ state_machine_draw->update();
+
+ updating = false;
+}
+
+void AnimationNodeStateMachineEditor::_notification(int p_what) {
+
+ if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
+ error_panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+ error_label->add_color_override("font_color", get_color("error_color", "Editor"));
+ panel->add_style_override("panel", get_stylebox("bg", "Tree"));
+ goto_parent->set_icon(get_icon("MoveUp", "EditorIcons"));
+
+ tool_select->set_icon(get_icon("ToolSelect", "EditorIcons"));
+ tool_create->set_icon(get_icon("ToolAddNode", "EditorIcons"));
+ tool_connect->set_icon(get_icon("ToolConnect", "EditorIcons"));
+
+ transition_mode->clear();
+ transition_mode->add_icon_item(get_icon("TransitionImmediate", "EditorIcons"), TTR("Immediate"));
+ transition_mode->add_icon_item(get_icon("TransitionSync", "EditorIcons"), TTR("Sync"));
+ transition_mode->add_icon_item(get_icon("TransitionEnd", "EditorIcons"), TTR("At End"));
+
+ //force filter on those, so they deform better
+ get_icon("TransitionImmediateBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+ get_icon("TransitionEndBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+ get_icon("TransitionSyncBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+ get_icon("TransitionImmediateAutoBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+ get_icon("TransitionEndAutoBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+ get_icon("TransitionSyncAutoBig", "EditorIcons")->set_flags(Texture::FLAG_FILTER);
+
+ tool_erase->set_icon(get_icon("Remove", "EditorIcons"));
+ tool_autoplay->set_icon(get_icon("AutoPlay", "EditorIcons"));
+ tool_end->set_icon(get_icon("AutoEnd", "EditorIcons"));
+
+ play_mode->clear();
+ play_mode->add_icon_item(get_icon("PlayTravel", "EditorIcons"), TTR("Travel"));
+ play_mode->add_icon_item(get_icon("Play", "EditorIcons"), TTR("Immediate"));
+ }
+
+ if (p_what == NOTIFICATION_PROCESS) {
+
+ String error;
+
+ if (error_time > 0) {
+ error = error_text;
+ error_time -= get_process_delta_time();
+ } else if (!state_machine->get_tree()) {
+ error = TTR("StateMachine does not belong to an AnimationTree node.");
+ } else if (!state_machine->get_tree()->is_active()) {
+ error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails.");
+ } else if (state_machine->get_tree()->is_state_invalid()) {
+ error = state_machine->get_tree()->get_invalid_state_reason();
+ } else if (state_machine->get_parent().is_valid() && state_machine->get_parent()->is_class("AnimationNodeStateMachine")) {
+ if (state_machine->get_start_node() == StringName() || state_machine->get_end_node() == StringName()) {
+ error = TTR("Start and end nodes are needed for a sub-transition.");
+ }
+ }
+
+ if (error != error_label->get_text()) {
+ error_label->set_text(error);
+ if (error != String()) {
+ error_panel->show();
+ } else {
+ error_panel->hide();
+ }
+ }
+
+ for (int i = 0; i < transition_lines.size(); i++) {
+ int tidx = -1;
+ for (int j = 0; j < state_machine->get_transition_count(); j++) {
+ if (transition_lines[i].from_node == state_machine->get_transition_from(j) && transition_lines[i].to_node == state_machine->get_transition_to(j)) {
+ tidx = j;
+ break;
+ }
+ }
+
+ if (tidx == -1) { //missing transition, should redraw
+ state_machine_draw->update();
+ break;
+ }
+
+ if (transition_lines[i].disabled != state_machine->get_transition(tidx)->is_disabled()) {
+ state_machine_draw->update();
+ break;
+ }
+
+ if (transition_lines[i].auto_advance != state_machine->get_transition(tidx)->has_auto_advance()) {
+ state_machine_draw->update();
+ break;
+ }
+
+ if (transition_lines[i].mode != state_machine->get_transition(tidx)->get_switch_mode()) {
+ state_machine_draw->update();
+ break;
+ }
+ }
+
+ bool same_travel_path = true;
+ Vector<StringName> tp = state_machine->get_travel_path();
+
+ {
+
+ if (last_travel_path.size() != tp.size()) {
+ same_travel_path = false;
+ } else {
+ for (int i = 0; i < last_travel_path.size(); i++) {
+ if (last_travel_path[i] != tp[i]) {
+ same_travel_path = false;
+ break;
+ }
+ }
+ }
+ }
+
+ //update if travel state changed
+ if (!same_travel_path || last_active != state_machine->is_playing() || last_current_node != state_machine->get_current_node() || last_blend_from_node != state_machine->get_blend_from_node()) {
+
+ state_machine_draw->update();
+ last_travel_path = tp;
+ last_current_node = state_machine->get_current_node();
+ last_active = state_machine->is_playing();
+ last_blend_from_node = state_machine->get_blend_from_node();
+ state_machine_play_pos->update();
+ }
+
+ if (last_play_pos != state_machine->get_current_play_pos()) {
+
+ last_play_pos = state_machine->get_current_play_pos();
+ state_machine_play_pos->update();
+ }
+ }
+
+ if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
+ over_node = StringName();
+ }
+}
+
+void AnimationNodeStateMachineEditor::_open_editor(const String &p_name) {
+ Ref<AnimationNode> an = state_machine->get_node(p_name);
+ ERR_FAIL_COND(!an.is_valid());
+ EditorNode::get_singleton()->edit_item(an.ptr());
+}
+
+void AnimationNodeStateMachineEditor::_goto_parent() {
+
+ EditorNode::get_singleton()->edit_item(state_machine->get_parent().ptr());
+}
+
+void AnimationNodeStateMachineEditor::_removed_from_graph() {
+ EditorNode::get_singleton()->edit_item(NULL);
+}
+
+void AnimationNodeStateMachineEditor::_name_edited(const String &p_text) {
+
+ 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 (state_machine->has_node(name)) {
+ base++;
+ name = base_name + " " + itos(base);
+ }
+
+ updating = true;
+ undo_redo->create_action("Node Renamed");
+ undo_redo->add_do_method(state_machine.ptr(), "rename_node", prev_name, name);
+ undo_redo->add_undo_method(state_machine.ptr(), "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;
+
+ state_machine_draw->update();
+
+ name_edit->hide();
+}
+
+void AnimationNodeStateMachineEditor::_scroll_changed(double) {
+ if (updating)
+ return;
+
+ state_machine->set_graph_offset(Vector2(h_scroll->get_value(), v_scroll->get_value()));
+ state_machine_draw->update();
+}
+
+void AnimationNodeStateMachineEditor::_erase_selected() {
+
+ if (selected_node != StringName() && state_machine->has_node(selected_node)) {
+ updating = true;
+ undo_redo->create_action("Node Removed");
+ undo_redo->add_do_method(state_machine.ptr(), "remove_node", selected_node);
+ undo_redo->add_undo_method(state_machine.ptr(), "add_node", selected_node, state_machine->get_node(selected_node));
+ for (int i = 0; i < state_machine->get_transition_count(); i++) {
+ String from = state_machine->get_transition_from(i);
+ String to = state_machine->get_transition_to(i);
+ if (from == selected_node || to == selected_node) {
+ undo_redo->add_undo_method(state_machine.ptr(), "add_transition", from, to, state_machine->get_transition(i));
+ }
+ }
+ if (String(state_machine->get_start_node()) == selected_node) {
+ undo_redo->add_undo_method(state_machine.ptr(), "set_start_node", selected_node);
+ }
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+ selected_node = StringName();
+ }
+
+ if (selected_transition_to != StringName() && selected_transition_from != StringName() && state_machine->has_transition(selected_transition_from, selected_transition_to)) {
+
+ Ref<AnimationNodeStateMachineTransition> tr = state_machine->get_transition(state_machine->find_transition(selected_transition_from, selected_transition_to));
+ updating = true;
+ undo_redo->create_action("Transition Removed");
+ undo_redo->add_do_method(state_machine.ptr(), "remove_transition", selected_transition_from, selected_transition_to);
+ undo_redo->add_undo_method(state_machine.ptr(), "add_transition", selected_transition_from, selected_transition_to, tr);
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+ selected_transition_from = StringName();
+ selected_transition_to = StringName();
+ }
+
+ state_machine_draw->update();
+}
+
+void AnimationNodeStateMachineEditor::_autoplay_selected() {
+
+ if (selected_node != StringName() && state_machine->has_node(selected_node)) {
+
+ StringName new_start_node;
+ if (state_machine->get_start_node() == selected_node) { //toggle it
+ new_start_node = StringName();
+ } else {
+ new_start_node = selected_node;
+ }
+
+ updating = true;
+ undo_redo->create_action("Set Start Node (Autoplay)");
+ undo_redo->add_do_method(state_machine.ptr(), "set_start_node", new_start_node);
+ undo_redo->add_undo_method(state_machine.ptr(), "set_start_node", state_machine->get_start_node());
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+ state_machine_draw->update();
+ }
+}
+
+void AnimationNodeStateMachineEditor::_end_selected() {
+
+ if (selected_node != StringName() && state_machine->has_node(selected_node)) {
+
+ StringName new_end_node;
+ if (state_machine->get_end_node() == selected_node) { //toggle it
+ new_end_node = StringName();
+ } else {
+ new_end_node = selected_node;
+ }
+
+ updating = true;
+ undo_redo->create_action("Set Start Node (Autoplay)");
+ undo_redo->add_do_method(state_machine.ptr(), "set_end_node", new_end_node);
+ undo_redo->add_undo_method(state_machine.ptr(), "set_end_node", state_machine->get_end_node());
+ undo_redo->add_do_method(this, "_update_graph");
+ undo_redo->add_undo_method(this, "_update_graph");
+ undo_redo->commit_action();
+ updating = false;
+ state_machine_draw->update();
+ }
+}
+void AnimationNodeStateMachineEditor::_update_mode() {
+
+ if (tool_select->is_pressed()) {
+ tool_erase_hb->show();
+ tool_erase->set_disabled(selected_node == StringName() && selected_transition_from == StringName() && selected_transition_to == StringName());
+ tool_autoplay->set_disabled(selected_node == StringName());
+ tool_end->set_disabled(selected_node == StringName());
+ } else {
+ tool_erase_hb->hide();
+ }
+}
+
+void AnimationNodeStateMachineEditor::_bind_methods() {
+
+ ClassDB::bind_method("_state_machine_gui_input", &AnimationNodeStateMachineEditor::_state_machine_gui_input);
+ ClassDB::bind_method("_state_machine_draw", &AnimationNodeStateMachineEditor::_state_machine_draw);
+ ClassDB::bind_method("_state_machine_pos_draw", &AnimationNodeStateMachineEditor::_state_machine_pos_draw);
+ ClassDB::bind_method("_update_graph", &AnimationNodeStateMachineEditor::_update_graph);
+
+ ClassDB::bind_method("_add_menu_type", &AnimationNodeStateMachineEditor::_add_menu_type);
+ ClassDB::bind_method("_add_animation_type", &AnimationNodeStateMachineEditor::_add_animation_type);
+
+ ClassDB::bind_method("_name_edited", &AnimationNodeStateMachineEditor::_name_edited);
+
+ ClassDB::bind_method("_goto_parent", &AnimationNodeStateMachineEditor::_goto_parent);
+ ClassDB::bind_method("_removed_from_graph", &AnimationNodeStateMachineEditor::_removed_from_graph);
+
+ ClassDB::bind_method("_open_editor", &AnimationNodeStateMachineEditor::_open_editor);
+ ClassDB::bind_method("_scroll_changed", &AnimationNodeStateMachineEditor::_scroll_changed);
+
+ ClassDB::bind_method("_erase_selected", &AnimationNodeStateMachineEditor::_erase_selected);
+ ClassDB::bind_method("_autoplay_selected", &AnimationNodeStateMachineEditor::_autoplay_selected);
+ ClassDB::bind_method("_end_selected", &AnimationNodeStateMachineEditor::_end_selected);
+ ClassDB::bind_method("_update_mode", &AnimationNodeStateMachineEditor::_update_mode);
+}
+
+AnimationNodeStateMachineEditor *AnimationNodeStateMachineEditor::singleton = NULL;
+
+AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
+
+ singleton = this;
+ updating = false;
+
+ HBoxContainer *top_hb = memnew(HBoxContainer);
+ add_child(top_hb);
+
+ goto_parent_hbox = memnew(HBoxContainer);
+ goto_parent = memnew(ToolButton);
+ goto_parent->connect("pressed", this, "_goto_parent", varray(), CONNECT_DEFERRED);
+ goto_parent_hbox->add_child(goto_parent);
+ goto_parent_hbox->add_child(memnew(VSeparator));
+ top_hb->add_child(goto_parent_hbox);
+
+ Ref<ButtonGroup> bg;
+ bg.instance();
+
+ tool_select = memnew(ToolButton);
+ top_hb->add_child(tool_select);
+ tool_select->set_toggle_mode(true);
+ tool_select->set_button_group(bg);
+ tool_select->set_pressed(true);
+ tool_select->set_tooltip(TTR("Select and move nodes.\nRMB to add new nodes.\nShift+LMB to create connections."));
+ tool_select->connect("pressed", this, "_update_mode", varray(), CONNECT_DEFERRED);
+
+ tool_create = memnew(ToolButton);
+ top_hb->add_child(tool_create);
+ tool_create->set_toggle_mode(true);
+ tool_create->set_button_group(bg);
+ tool_create->set_tooltip(TTR("Create new nodes."));
+ tool_create->connect("pressed", this, "_update_mode", varray(), CONNECT_DEFERRED);
+
+ tool_connect = memnew(ToolButton);
+ top_hb->add_child(tool_connect);
+ tool_connect->set_toggle_mode(true);
+ tool_connect->set_button_group(bg);
+ tool_connect->set_tooltip(TTR("Connect nodes."));
+ tool_connect->connect("pressed", this, "_update_mode", varray(), CONNECT_DEFERRED);
+
+ tool_erase_hb = memnew(HBoxContainer);
+ top_hb->add_child(tool_erase_hb);
+ tool_erase_hb->add_child(memnew(VSeparator));
+ tool_erase = memnew(ToolButton);
+ tool_erase->set_tooltip(TTR("Remove selected node or transition"));
+ tool_erase_hb->add_child(tool_erase);
+ tool_erase->connect("pressed", this, "_erase_selected");
+ tool_erase->set_disabled(true);
+
+ tool_erase_hb->add_child(memnew(VSeparator));
+
+ tool_autoplay = memnew(ToolButton);
+ tool_autoplay->set_tooltip(TTR("Toggle autoplay this animation on start, restart or seek to zero."));
+ tool_erase_hb->add_child(tool_autoplay);
+ tool_autoplay->connect("pressed", this, "_autoplay_selected");
+ tool_autoplay->set_disabled(true);
+
+ tool_end = memnew(ToolButton);
+ tool_end->set_tooltip(TTR("Set the end animation. This is useful for sub-transitions."));
+ tool_erase_hb->add_child(tool_end);
+ tool_end->connect("pressed", this, "_end_selected");
+ tool_end->set_disabled(true);
+
+ top_hb->add_child(memnew(VSeparator));
+ top_hb->add_child(memnew(Label(TTR("Transition: "))));
+ transition_mode = memnew(OptionButton);
+ top_hb->add_child(transition_mode);
+
+ top_hb->add_spacer();
+
+ top_hb->add_child(memnew(Label("Play Mode:")));
+ play_mode = memnew(OptionButton);
+ top_hb->add_child(play_mode);
+
+ GridContainer *main_grid = memnew(GridContainer);
+ main_grid->set_columns(2);
+ add_child(main_grid);
+ main_grid->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ panel = memnew(PanelContainer);
+ panel->set_clip_contents(true);
+ main_grid->add_child(panel);
+ panel->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ state_machine_draw = memnew(Control);
+ state_machine_draw->connect("gui_input", this, "_state_machine_gui_input");
+ state_machine_draw->connect("draw", this, "_state_machine_draw");
+ state_machine_draw->set_focus_mode(FOCUS_ALL);
+
+ state_machine_play_pos = memnew(Control);
+ state_machine_draw->add_child(state_machine_play_pos);
+ state_machine_play_pos->set_mouse_filter(MOUSE_FILTER_PASS); //pass all to parent
+ state_machine_play_pos->set_anchors_and_margins_preset(PRESET_WIDE);
+ state_machine_play_pos->connect("draw", this, "_state_machine_pos_draw");
+
+ panel->add_child(state_machine_draw);
+ panel->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ v_scroll = memnew(VScrollBar);
+ main_grid->add_child(v_scroll);
+ v_scroll->connect("value_changed", this, "_scroll_changed");
+
+ h_scroll = memnew(HScrollBar);
+ main_grid->add_child(h_scroll);
+ h_scroll->connect("value_changed", this, "_scroll_changed");
+
+ main_grid->add_child(memnew(Control)); //empty bottom right
+
+ error_panel = memnew(PanelContainer);
+ add_child(error_panel);
+ error_label = memnew(Label);
+ error_panel->add_child(error_label);
+ error_label->set_text("eh");
+
+ undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+ set_custom_minimum_size(Size2(0, 300 * EDSCALE));
+
+ menu = memnew(PopupMenu);
+ add_child(menu);
+ menu->connect("index_pressed", this, "_add_menu_type");
+
+ animations_menu = memnew(PopupMenu);
+ menu->add_child(animations_menu);
+ animations_menu->set_name("animations");
+ animations_menu->connect("index_pressed", this, "_add_animation_type");
+
+ name_edit = memnew(LineEdit);
+ state_machine_draw->add_child(name_edit);
+ name_edit->hide();
+ name_edit->connect("text_entered", this, "_name_edited");
+ name_edit->set_as_toplevel(true);
+
+ over_text = false;
+
+ over_node_what = -1;
+ dragging_selected_attempt = false;
+ connecting = false;
+
+ last_active = false;
+
+ error_time = 0;
+}
+
+void AnimationNodeStateMachineEditorPlugin::edit(Object *p_object) {
+
+ anim_tree_editor->edit(Object::cast_to<AnimationNodeStateMachine>(p_object));
+}
+
+bool AnimationNodeStateMachineEditorPlugin::handles(Object *p_object) const {
+
+ return p_object->is_class("AnimationNodeStateMachine");
+}
+
+void AnimationNodeStateMachineEditorPlugin::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);
+ }
+}
+
+AnimationNodeStateMachineEditorPlugin::AnimationNodeStateMachineEditorPlugin(EditorNode *p_node) {
+
+ editor = p_node;
+ anim_tree_editor = memnew(AnimationNodeStateMachineEditor);
+ anim_tree_editor->set_custom_minimum_size(Size2(0, 300));
+
+ button = editor->add_bottom_panel_item(TTR("StateMachine"), anim_tree_editor);
+ button->hide();
+}
+
+AnimationNodeStateMachineEditorPlugin::~AnimationNodeStateMachineEditorPlugin() {
+}
diff --git a/editor/plugins/animation_state_machine_editor.h b/editor/plugins/animation_state_machine_editor.h
new file mode 100644
index 0000000000..efd3de7415
--- /dev/null
+++ b/editor/plugins/animation_state_machine_editor.h
@@ -0,0 +1,167 @@
+#ifndef ANIMATION_STATE_MACHINE_EDITOR_H
+#define ANIMATION_STATE_MACHINE_EDITOR_H
+
+#include "editor/editor_node.h"
+#include "editor/editor_plugin.h"
+#include "editor/property_editor.h"
+#include "scene/animation/animation_node_state_machine.h"
+#include "scene/gui/button.h"
+#include "scene/gui/graph_edit.h"
+#include "scene/gui/popup.h"
+#include "scene/gui/tree.h"
+
+class AnimationNodeStateMachineEditor : public VBoxContainer {
+
+ GDCLASS(AnimationNodeStateMachineEditor, VBoxContainer);
+
+ Ref<AnimationNodeStateMachine> state_machine;
+
+ ToolButton *tool_select;
+ ToolButton *tool_create;
+ ToolButton *tool_connect;
+ LineEdit *name_edit;
+
+ HBoxContainer *tool_erase_hb;
+ ToolButton *tool_erase;
+ ToolButton *tool_autoplay;
+ ToolButton *tool_end;
+
+ OptionButton *transition_mode;
+ OptionButton *play_mode;
+
+ HBoxContainer *goto_parent_hbox;
+ ToolButton *goto_parent;
+
+ PanelContainer *panel;
+
+ StringName selected_node;
+
+ HScrollBar *h_scroll;
+ VScrollBar *v_scroll;
+
+ Control *state_machine_draw;
+ Control *state_machine_play_pos;
+
+ PanelContainer *error_panel;
+ Label *error_label;
+
+ bool updating;
+
+ UndoRedo *undo_redo;
+
+ static AnimationNodeStateMachineEditor *singleton;
+
+ void _state_machine_gui_input(const Ref<InputEvent> &p_event);
+ void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, bool p_auto_advance);
+ void _state_machine_draw();
+ void _state_machine_pos_draw();
+
+ void _update_graph();
+
+ PopupMenu *menu;
+ PopupMenu *animations_menu;
+ Vector<String> animations_to_add;
+
+ Vector2 add_node_pos;
+
+ bool dragging_selected_attempt;
+ bool dragging_selected;
+ Vector2 drag_from;
+ Vector2 drag_ofs;
+ StringName snap_x;
+ StringName snap_y;
+
+ bool connecting;
+ StringName connecting_from;
+ Vector2 connecting_to;
+ StringName connecting_to_node;
+
+ void _add_menu_type(int p_index);
+ void _add_animation_type(int p_index);
+
+ void _goto_parent();
+
+ void _removed_from_graph();
+
+ struct NodeRect {
+ StringName node_name;
+ Rect2 node;
+ Rect2 play;
+ Rect2 name;
+ Rect2 edit;
+ };
+
+ Vector<NodeRect> node_rects;
+
+ struct TransitionLine {
+ StringName from_node;
+ StringName to_node;
+ Vector2 from;
+ Vector2 to;
+ AnimationNodeStateMachineTransition::SwitchMode mode;
+ bool disabled;
+ bool auto_advance;
+ float width;
+ };
+
+ Vector<TransitionLine> transition_lines;
+
+ StringName selected_transition_from;
+ StringName selected_transition_to;
+
+ bool over_text;
+ StringName over_node;
+ int over_node_what;
+
+ String prev_name;
+ void _name_edited(const String &p_text);
+ void _open_editor(const String &p_name);
+ void _scroll_changed(double);
+
+ void _clip_src_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect);
+ void _clip_dst_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect);
+
+ void _erase_selected();
+ void _update_mode();
+ void _autoplay_selected();
+ void _end_selected();
+
+ bool last_active;
+ StringName last_blend_from_node;
+ StringName last_current_node;
+ Vector<StringName> last_travel_path;
+ float last_play_pos;
+
+ float error_time;
+ String error_text;
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ static AnimationNodeStateMachineEditor *get_singleton() { return singleton; }
+ void edit(AnimationNodeStateMachine *p_state_machine);
+ AnimationNodeStateMachineEditor();
+};
+
+class AnimationNodeStateMachineEditorPlugin : public EditorPlugin {
+
+ GDCLASS(AnimationNodeStateMachineEditorPlugin, EditorPlugin);
+
+ AnimationNodeStateMachineEditor *anim_tree_editor;
+ EditorNode *editor;
+ Button *button;
+
+public:
+ virtual String get_name() const { return "StateMachine"; }
+ 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);
+
+ AnimationNodeStateMachineEditorPlugin(EditorNode *p_node);
+ ~AnimationNodeStateMachineEditorPlugin();
+};
+
+#endif // ANIMATION_STATE_MACHINE_EDITOR_H
diff --git a/platform/android/java/src/org/godotengine/godot/Godot.java b/platform/android/java/src/org/godotengine/godot/Godot.java
index 90848e6a90..8a2d789dc5 100644
--- a/platform/android/java/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/src/org/godotengine/godot/Godot.java
@@ -32,6 +32,7 @@ package org.godotengine.godot;
import android.R;
import android.app.Activity;
+import android.content.pm.ConfigurationInfo;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
@@ -246,9 +247,11 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC
}
};
- public void onVideoInit(boolean use_gl2) {
+ public void onVideoInit() {
- //mView = new GodotView(getApplication(),io,use_gl2);
+ boolean use_gl3 = getGLESVersionCode() >= 0x00030000;
+
+ //mView = new GodotView(getApplication(),io,use_gl3);
//setContentView(mView);
layout = new FrameLayout(this);
@@ -261,7 +264,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC
// ...add to FrameLayout
layout.addView(edittext);
- mView = new GodotView(getApplication(), io, use_gl2, use_32_bits, this);
+ mView = new GodotView(getApplication(), io, use_gl3, use_32_bits, this);
layout.addView(mView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
edittext.setView(mView);
io.setEdit(edittext);
@@ -338,6 +341,12 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC
return Godot._self;
}
+ public int getGLESVersionCode() {
+ ActivityManager am = (ActivityManager)Godot.getInstance().getSystemService(Context.ACTIVITY_SERVICE);
+ ConfigurationInfo deviceInfo = am.getDeviceConfigurationInfo();
+ return deviceInfo.reqGlEsVersion;
+ }
+
private String[] getCommandLine() {
InputStream is;
try {
diff --git a/platform/android/java_glue.cpp b/platform/android/java_glue.cpp
index 579c06f76b..e6240ad9e9 100644
--- a/platform/android/java_glue.cpp
+++ b/platform/android/java_glue.cpp
@@ -614,6 +614,7 @@ static jmethodID _hideKeyboard = 0;
static jmethodID _setScreenOrientation = 0;
static jmethodID _getUniqueID = 0;
static jmethodID _getSystemDir = 0;
+static jmethodID _getGLESVersionCode = 0;
static jmethodID _playVideo = 0;
static jmethodID _isVideoPlaying = 0;
static jmethodID _pauseVideo = 0;
@@ -685,6 +686,11 @@ static String _get_system_dir(int p_dir) {
return String(env->GetStringUTFChars(s, NULL));
}
+static int _get_gles_version_code() {
+ JNIEnv *env = ThreadAndroid::get_env();
+ return env->CallIntMethod(_godot_instance, _getGLESVersionCode);
+}
+
static void _hide_vk() {
JNIEnv *env = ThreadAndroid::get_env();
@@ -764,9 +770,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en
godot_io = gob;
- _on_video_init = env->GetMethodID(cls, "onVideoInit", "(Z)V");
+ _on_video_init = env->GetMethodID(cls, "onVideoInit", "()V");
_setKeepScreenOn = env->GetMethodID(cls, "setKeepScreenOn", "(Z)V");
_alertDialog = env->GetMethodID(cls, "alert", "(Ljava/lang/String;Ljava/lang/String;)V");
+ _getGLESVersionCode = env->GetMethodID(cls, "getGLESVersionCode", "()I");
jclass clsio = env->FindClass("org/godotengine/godot/Godot");
if (cls) {
@@ -800,16 +807,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en
AudioDriverAndroid::setup(gob);
}
- os_android = new OS_Android(_gfx_init_func, env, _open_uri, _get_user_data_dir, _get_locale, _get_model, _get_screen_dpi, _show_vk, _hide_vk, _get_vk_height, _set_screen_orient, _get_unique_id, _get_system_dir, _play_video, _is_video_playing, _pause_video, _stop_video, _set_keep_screen_on, _alert, p_use_apk_expansion);
+ os_android = new OS_Android(_gfx_init_func, env, _open_uri, _get_user_data_dir, _get_locale, _get_model, _get_screen_dpi, _show_vk, _hide_vk, _get_vk_height, _set_screen_orient, _get_unique_id, _get_system_dir, _get_gles_version_code, _play_video, _is_video_playing, _pause_video, _stop_video, _set_keep_screen_on, _alert, p_use_apk_expansion);
os_android->set_need_reload_hooks(p_need_reload_hook);
char wd[500];
getcwd(wd, 500);
- //video driver is determined here, because once initialized, it can't be changed
- // String vd = ProjectSettings::get_singleton()->get("display/driver");
-
- env->CallVoidMethod(_godot_instance, _on_video_init, (jboolean) true);
+ env->CallVoidMethod(_godot_instance, _on_video_init);
}
static void _initialize_java_modules() {
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index fc41adeb76..9188f09f21 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -32,6 +32,7 @@
#include "core/io/file_access_buffered_fa.h"
#include "core/project_settings.h"
+#include "drivers/gles2/rasterizer_gles2.h"
#include "drivers/gles3/rasterizer_gles3.h"
#include "drivers/unix/dir_access_unix.h"
#include "drivers/unix/file_access_unix.h"
@@ -125,13 +126,20 @@ void OS_Android::set_opengl_extensions(const char *p_gl_extensions) {
Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
- use_gl2 = p_video_driver != 1;
+ bool use_gl3 = get_gl_version_code_func() >= 0x00030000;
+ use_gl3 = use_gl3 && (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES3");
+ use_gl2 = !use_gl3;
if (gfx_init_func)
gfx_init_func(gfx_init_ud, use_gl2);
- RasterizerGLES3::register_config();
- RasterizerGLES3::make_current();
+ if (use_gl2) {
+ RasterizerGLES2::register_config();
+ RasterizerGLES2::make_current();
+ } else {
+ RasterizerGLES3::register_config();
+ RasterizerGLES3::make_current();
+ }
visual_server = memnew(VisualServerRaster);
/* if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
@@ -684,7 +692,7 @@ bool OS_Android::_check_internal_feature_support(const String &p_feature) {
return false;
}
-OS_Android::OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetUserDataDirFunc p_get_user_data_dir_func, GetLocaleFunc p_get_locale_func, GetModelFunc p_get_model_func, GetScreenDPIFunc p_get_screen_dpi_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, VirtualKeyboardHeightFunc p_vk_height_func, SetScreenOrientationFunc p_screen_orient, GetUniqueIDFunc p_get_unique_id, GetSystemDirFunc p_get_sdir_func, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func, SetKeepScreenOnFunc p_set_keep_screen_on_func, AlertFunc p_alert_func, bool p_use_apk_expansion) {
+OS_Android::OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetUserDataDirFunc p_get_user_data_dir_func, GetLocaleFunc p_get_locale_func, GetModelFunc p_get_model_func, GetScreenDPIFunc p_get_screen_dpi_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, VirtualKeyboardHeightFunc p_vk_height_func, SetScreenOrientationFunc p_screen_orient, GetUniqueIDFunc p_get_unique_id, GetSystemDirFunc p_get_sdir_func, GetGLVersionCodeFunc p_get_gl_version_func, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func, SetKeepScreenOnFunc p_set_keep_screen_on_func, AlertFunc p_alert_func, bool p_use_apk_expansion) {
use_apk_expansion = p_use_apk_expansion;
default_videomode.width = 800;
@@ -706,6 +714,7 @@ OS_Android::OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURI
get_screen_dpi_func = p_get_screen_dpi_func;
get_unique_id_func = p_get_unique_id;
get_system_dir_func = p_get_sdir_func;
+ get_gl_version_code_func = p_get_gl_version_func;
video_play_func = p_video_play_func;
video_is_playing_func = p_video_is_playing_func;
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index d2457e538d..ac901d4832 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -58,6 +58,7 @@ typedef void (*ShowVirtualKeyboardFunc)(const String &);
typedef void (*HideVirtualKeyboardFunc)();
typedef void (*SetScreenOrientationFunc)(int);
typedef String (*GetSystemDirFunc)(int);
+typedef int (*GetGLVersionCodeFunc)();
typedef void (*VideoPlayFunc)(const String &);
typedef bool (*VideoIsPlayingFunc)();
@@ -126,6 +127,7 @@ private:
SetScreenOrientationFunc set_screen_orientation_func;
GetUniqueIDFunc get_unique_id_func;
GetSystemDirFunc get_system_dir_func;
+ GetGLVersionCodeFunc get_gl_version_code_func;
VideoPlayFunc video_play_func;
VideoIsPlayingFunc video_is_playing_func;
@@ -239,7 +241,7 @@ public:
void joy_connection_changed(int p_device, bool p_connected, String p_name);
virtual bool _check_internal_feature_support(const String &p_feature);
- OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetUserDataDirFunc p_get_user_data_dir_func, GetLocaleFunc p_get_locale_func, GetModelFunc p_get_model_func, GetScreenDPIFunc p_get_screen_dpi_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, VirtualKeyboardHeightFunc p_vk_height_func, SetScreenOrientationFunc p_screen_orient, GetUniqueIDFunc p_get_unique_id, GetSystemDirFunc p_get_sdir_func, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func, SetKeepScreenOnFunc p_set_keep_screen_on_func, AlertFunc p_alert_func, bool p_use_apk_expansion);
+ OS_Android(GFXInitFunc p_gfx_init_func, void *p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetUserDataDirFunc p_get_user_data_dir_func, GetLocaleFunc p_get_locale_func, GetModelFunc p_get_model_func, GetScreenDPIFunc p_get_screen_dpi_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, VirtualKeyboardHeightFunc p_vk_height_func, SetScreenOrientationFunc p_screen_orient, GetUniqueIDFunc p_get_unique_id, GetSystemDirFunc p_get_sdir_func, GetGLVersionCodeFunc p_get_gl_version_func, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func, SetKeepScreenOnFunc p_set_keep_screen_on_func, AlertFunc p_alert_func, bool p_use_apk_expansion);
~OS_Android();
};
diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp
new file mode 100644
index 0000000000..6993c0a785
--- /dev/null
+++ b/scene/animation/animation_blend_space_1d.cpp
@@ -0,0 +1,284 @@
+#include "animation_blend_space_1d.h"
+
+void AnimationNodeBlendSpace1D::_validate_property(PropertyInfo &property) const {
+ if (property.name.begins_with("blend_point_")) {
+ String left = property.name.get_slicec('/', 0);
+ int idx = left.get_slicec('_', 2).to_int();
+ if (idx >= blend_points_used) {
+ property.usage = 0;
+ }
+ }
+ AnimationRootNode::_validate_property(property);
+}
+
+void AnimationNodeBlendSpace1D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace1D::add_blend_point, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace1D::set_blend_point_position);
+ ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace1D::get_blend_point_position);
+ ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace1D::set_blend_point_node);
+ ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace1D::get_blend_point_node);
+ ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace1D::remove_blend_point);
+ ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace1D::get_blend_point_count);
+
+ ClassDB::bind_method(D_METHOD("set_min_space", "min_space"), &AnimationNodeBlendSpace1D::set_min_space);
+ ClassDB::bind_method(D_METHOD("get_min_space"), &AnimationNodeBlendSpace1D::get_min_space);
+
+ ClassDB::bind_method(D_METHOD("set_max_space", "max_space"), &AnimationNodeBlendSpace1D::set_max_space);
+ ClassDB::bind_method(D_METHOD("get_max_space"), &AnimationNodeBlendSpace1D::get_max_space);
+
+ ClassDB::bind_method(D_METHOD("set_snap", "snap"), &AnimationNodeBlendSpace1D::set_snap);
+ ClassDB::bind_method(D_METHOD("get_snap"), &AnimationNodeBlendSpace1D::get_snap);
+
+ ClassDB::bind_method(D_METHOD("set_blend_pos", "pos"), &AnimationNodeBlendSpace1D::set_blend_pos);
+ ClassDB::bind_method(D_METHOD("get_blend_pos"), &AnimationNodeBlendSpace1D::get_blend_pos);
+
+ ClassDB::bind_method(D_METHOD("set_value_label", "text"), &AnimationNodeBlendSpace1D::set_value_label);
+ ClassDB::bind_method(D_METHOD("get_value_label"), &AnimationNodeBlendSpace1D::get_value_label);
+
+ ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace1D::_add_blend_point);
+
+ for (int i = 0; i < MAX_BLEND_POINTS; i++) {
+ ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "blend_point_" + itos(i) + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE), "_add_blend_point", "get_blend_point_node", i);
+ ADD_PROPERTYI(PropertyInfo(Variant::REAL, "blend_point_" + itos(i) + "/pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "set_blend_point_position", "get_blend_point_position", i);
+ }
+
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_min_space", "get_min_space");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_space", "get_max_space");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_snap", "get_snap");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "blend_pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_blend_pos", "get_blend_pos");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "value_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_value_label", "get_value_label");
+}
+
+void AnimationNodeBlendSpace1D::add_blend_point(const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index) {
+ ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS);
+ ERR_FAIL_COND(p_node.is_null());
+ ERR_FAIL_COND(p_node->get_parent().is_valid());
+ ERR_FAIL_COND(p_at_index < -1 || p_at_index > blend_points_used);
+
+ if (p_at_index == -1 || p_at_index == blend_points_used) {
+ p_at_index = blend_points_used;
+ } else {
+ for (int i = blend_points_used - 1; i > p_at_index; i++) {
+ blend_points[i] = blend_points[i - 1];
+ }
+ }
+
+ blend_points[p_at_index].node = p_node;
+ blend_points[p_at_index].position = p_position;
+
+ blend_points[p_at_index].node->set_parent(this);
+ blend_points[p_at_index].node->set_tree(get_tree());
+
+ blend_points_used++;
+}
+
+void AnimationNodeBlendSpace1D::set_blend_point_position(int p_point, float p_position) {
+ ERR_FAIL_INDEX(p_point, blend_points_used);
+
+ blend_points[p_point].position = p_position;
+}
+
+void AnimationNodeBlendSpace1D::set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node) {
+ ERR_FAIL_INDEX(p_point, blend_points_used);
+ ERR_FAIL_COND(p_node.is_null());
+
+ if (blend_points[p_point].node.is_valid()) {
+ blend_points[p_point].node->set_parent(NULL);
+ blend_points[p_point].node->set_tree(NULL);
+ }
+
+ blend_points[p_point].node = p_node;
+ blend_points[p_point].node->set_parent(this);
+ blend_points[p_point].node->set_tree(get_tree());
+}
+
+float AnimationNodeBlendSpace1D::get_blend_point_position(int p_point) const {
+ ERR_FAIL_INDEX_V(p_point, blend_points_used, 0);
+ return blend_points[p_point].position;
+}
+
+Ref<AnimationRootNode> AnimationNodeBlendSpace1D::get_blend_point_node(int p_point) const {
+ ERR_FAIL_INDEX_V(p_point, blend_points_used, Ref<AnimationRootNode>());
+ return blend_points[p_point].node;
+}
+
+void AnimationNodeBlendSpace1D::remove_blend_point(int p_point) {
+ ERR_FAIL_INDEX(p_point, blend_points_used);
+
+ blend_points[p_point].node->set_parent(NULL);
+ blend_points[p_point].node->set_tree(NULL);
+
+ for (int i = p_point; i < blend_points_used - 1; i++) {
+ blend_points[i] = blend_points[i + 1];
+ }
+
+ blend_points_used--;
+}
+
+int AnimationNodeBlendSpace1D::get_blend_point_count() const {
+
+ return blend_points_used;
+}
+
+void AnimationNodeBlendSpace1D::set_min_space(float p_min) {
+ min_space = p_min;
+
+ if (min_space >= max_space) {
+ min_space = max_space - 1;
+ }
+}
+
+float AnimationNodeBlendSpace1D::get_min_space() const {
+ return min_space;
+}
+
+void AnimationNodeBlendSpace1D::set_max_space(float p_max) {
+ max_space = p_max;
+
+ if (max_space <= min_space) {
+ max_space = min_space + 1;
+ }
+}
+
+float AnimationNodeBlendSpace1D::get_max_space() const {
+ return max_space;
+}
+
+void AnimationNodeBlendSpace1D::set_snap(float p_snap) {
+ snap = p_snap;
+}
+
+float AnimationNodeBlendSpace1D::get_snap() const {
+ return snap;
+}
+
+void AnimationNodeBlendSpace1D::set_blend_pos(float p_pos) {
+ blend_pos = p_pos;
+}
+
+float AnimationNodeBlendSpace1D::get_blend_pos() const {
+ return blend_pos;
+}
+
+void AnimationNodeBlendSpace1D::set_value_label(const String &p_label) {
+ value_label = p_label;
+}
+
+String AnimationNodeBlendSpace1D::get_value_label() const {
+ return value_label;
+}
+
+void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node) {
+ if (p_index == blend_points_used) {
+ add_blend_point(p_node, 0);
+ } else {
+ set_blend_point_node(p_index, p_node);
+ }
+}
+
+float AnimationNodeBlendSpace1D::process(float p_time, bool p_seek) {
+
+ if (blend_points_used == 0) {
+ return 0.0;
+ }
+
+ if (blend_points_used == 1) {
+ // only one point available, just play that animation
+ return blend_node(blend_points[0].node, p_time, p_seek, 1.0, FILTER_IGNORE, false);
+ }
+
+ float weights[MAX_BLEND_POINTS] = {};
+
+ int point_lower = -1;
+ float pos_lower = 0.0;
+ int point_higher = -1;
+ float pos_higher = 0.0;
+
+ // find the closest two points to blend between
+ for (int i = 0; i < blend_points_used; i++) {
+
+ float pos = blend_points[i].position;
+
+ if (pos <= blend_pos) {
+ if (point_lower == -1) {
+ point_lower = i;
+ pos_lower = pos;
+ } else if ((blend_pos - pos) < (blend_pos - pos_lower)) {
+ point_lower = i;
+ pos_lower = pos;
+ }
+ } else {
+ if (point_higher == -1) {
+ point_higher = i;
+ pos_higher = pos;
+ } else if ((pos - blend_pos) < (pos_higher - blend_pos)) {
+ point_higher = i;
+ pos_higher = pos;
+ }
+ }
+ }
+
+ // fill in weights
+
+ if (point_lower == -1) {
+ // we are on the left side, no other point to the left
+ // we just play the next point.
+
+ weights[point_higher] = 1.0;
+ } else if (point_higher == -1) {
+ // we are on the right side, no other point to the right
+ // we just play the previous point
+
+ weights[point_lower] = 1.0;
+ } else {
+
+ // we are between two points.
+ // figure out weights, then blend the animations
+
+ float distance_between_points = pos_higher - pos_lower;
+
+ float current_pos_inbetween = blend_pos - pos_lower;
+
+ float blend_percentage = current_pos_inbetween / distance_between_points;
+
+ float blend_lower = 1.0 - blend_percentage;
+ float blend_higher = blend_percentage;
+
+ weights[point_lower] = blend_lower;
+ weights[point_higher] = blend_higher;
+ }
+
+ // actually blend the animations now
+
+ float max_time_remaining = 0.0;
+
+ for (int i = 0; i < blend_points_used; i++) {
+ float remaining = blend_node(blend_points[i].node, p_time, p_seek, weights[i], FILTER_IGNORE, false);
+
+ max_time_remaining = MAX(max_time_remaining, remaining);
+ }
+
+ return max_time_remaining;
+}
+
+String AnimationNodeBlendSpace1D::get_caption() const {
+ return "BlendSpace1D";
+}
+
+AnimationNodeBlendSpace1D::AnimationNodeBlendSpace1D() {
+
+ blend_points_used = 0;
+ max_space = 1;
+ min_space = -1;
+
+ snap = 0.1;
+ value_label = "value";
+}
+
+AnimationNodeBlendSpace1D::~AnimationNodeBlendSpace1D() {
+
+ for (int i = 0; i < blend_points_used; i++) {
+ blend_points[i].node->set_parent(this);
+ blend_points[i].node->set_tree(get_tree());
+ }
+}
diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h
new file mode 100644
index 0000000000..6e264076c4
--- /dev/null
+++ b/scene/animation/animation_blend_space_1d.h
@@ -0,0 +1,68 @@
+#ifndef ANIMATION_BLEND_SPACE_1D_H
+#define ANIMATION_BLEND_SPACE_1D_H
+
+#include "scene/animation/animation_tree.h"
+
+class AnimationNodeBlendSpace1D : public AnimationRootNode {
+ GDCLASS(AnimationNodeBlendSpace1D, AnimationRootNode)
+
+ enum {
+ MAX_BLEND_POINTS = 64
+ };
+
+ struct BlendPoint {
+ Ref<AnimationRootNode> node;
+ float position;
+ };
+
+ BlendPoint blend_points[MAX_BLEND_POINTS];
+ int blend_points_used;
+
+ float blend_pos;
+
+ float max_space;
+ float min_space;
+
+ float snap;
+
+ String value_label;
+
+ void _add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node);
+
+protected:
+ virtual void _validate_property(PropertyInfo &property) const;
+ static void _bind_methods();
+
+public:
+ void add_blend_point(const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index = -1);
+ void set_blend_point_position(int p_point, float p_position);
+ void set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node);
+
+ float get_blend_point_position(int p_point) const;
+ Ref<AnimationRootNode> get_blend_point_node(int p_point) const;
+ void remove_blend_point(int p_point);
+ int get_blend_point_count() const;
+
+ void set_min_space(float p_min);
+ float get_min_space() const;
+
+ void set_max_space(float p_max);
+ float get_max_space() const;
+
+ void set_snap(float p_snap);
+ float get_snap() const;
+
+ void set_blend_pos(float p_pos);
+ float get_blend_pos() const;
+
+ void set_value_label(const String &p_label);
+ String get_value_label() const;
+
+ float process(float p_time, bool p_seek);
+ String get_caption() const;
+
+ AnimationNodeBlendSpace1D();
+ ~AnimationNodeBlendSpace1D();
+};
+
+#endif // ANIMATION_BLEND_SPACE_1D_H
diff --git a/scene/animation/animation_blend_space.cpp b/scene/animation/animation_blend_space_2d.cpp
index 44b2d980e2..28e4ca4eda 100644
--- a/scene/animation/animation_blend_space.cpp
+++ b/scene/animation/animation_blend_space_2d.cpp
@@ -1,7 +1,7 @@
-#include "animation_blend_space.h"
+#include "animation_blend_space_2d.h"
#include "math/delaunay.h"
-void AnimationNodeBlendSpace::add_blend_point(const Ref<AnimationRootNode> &p_node, const Vector2 &p_position, int p_at_index) {
+void AnimationNodeBlendSpace2D::add_blend_point(const Ref<AnimationRootNode> &p_node, const Vector2 &p_position, int p_at_index) {
ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS);
ERR_FAIL_COND(p_node.is_null());
ERR_FAIL_COND(p_node->get_parent().is_valid());
@@ -10,7 +10,7 @@ void AnimationNodeBlendSpace::add_blend_point(const Ref<AnimationRootNode> &p_no
if (p_at_index == -1 || p_at_index == blend_points_used) {
p_at_index = blend_points_used;
} else {
- for (int i = blend_points_used; i > p_at_index; i--) {
+ for (int i = blend_points_used - 1; i > p_at_index; i--) {
blend_points[i] = blend_points[i - 1];
}
for (int i = 0; i < triangles.size(); i++) {
@@ -25,7 +25,7 @@ void AnimationNodeBlendSpace::add_blend_point(const Ref<AnimationRootNode> &p_no
blend_points[p_at_index].position = p_position;
blend_points[p_at_index].node->set_parent(this);
- blend_points[p_at_index].node->set_graph_player(get_graph_player());
+ blend_points[p_at_index].node->set_tree(get_tree());
blend_points_used++;
if (auto_triangles) {
@@ -33,38 +33,38 @@ void AnimationNodeBlendSpace::add_blend_point(const Ref<AnimationRootNode> &p_no
}
}
-void AnimationNodeBlendSpace::set_blend_point_position(int p_point, const Vector2 &p_position) {
+void AnimationNodeBlendSpace2D::set_blend_point_position(int p_point, const Vector2 &p_position) {
ERR_FAIL_INDEX(p_point, blend_points_used);
blend_points[p_point].position = p_position;
if (auto_triangles) {
trianges_dirty = true;
}
}
-void AnimationNodeBlendSpace::set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node) {
+void AnimationNodeBlendSpace2D::set_blend_point_node(int p_point, const Ref<AnimationRootNode> &p_node) {
ERR_FAIL_INDEX(p_point, blend_points_used);
ERR_FAIL_COND(p_node.is_null());
if (blend_points[p_point].node.is_valid()) {
blend_points[p_point].node->set_parent(NULL);
- blend_points[p_point].node->set_graph_player(NULL);
+ blend_points[p_point].node->set_tree(NULL);
}
blend_points[p_point].node = p_node;
blend_points[p_point].node->set_parent(this);
- blend_points[p_point].node->set_graph_player(get_graph_player());
+ blend_points[p_point].node->set_tree(get_tree());
}
-Vector2 AnimationNodeBlendSpace::get_blend_point_position(int p_point) const {
+Vector2 AnimationNodeBlendSpace2D::get_blend_point_position(int p_point) const {
ERR_FAIL_INDEX_V(p_point, blend_points_used, Vector2());
return blend_points[p_point].position;
}
-Ref<AnimationRootNode> AnimationNodeBlendSpace::get_blend_point_node(int p_point) const {
+Ref<AnimationRootNode> AnimationNodeBlendSpace2D::get_blend_point_node(int p_point) const {
ERR_FAIL_INDEX_V(p_point, blend_points_used, Ref<AnimationRootNode>());
return blend_points[p_point].node;
}
-void AnimationNodeBlendSpace::remove_blend_point(int p_point) {
+void AnimationNodeBlendSpace2D::remove_blend_point(int p_point) {
ERR_FAIL_INDEX(p_point, blend_points_used);
blend_points[p_point].node->set_parent(NULL);
- blend_points[p_point].node->set_graph_player(NULL);
+ blend_points[p_point].node->set_tree(NULL);
for (int i = 0; i < triangles.size(); i++) {
bool erase = false;
@@ -89,12 +89,12 @@ void AnimationNodeBlendSpace::remove_blend_point(int p_point) {
blend_points_used--;
}
-int AnimationNodeBlendSpace::get_blend_point_count() const {
+int AnimationNodeBlendSpace2D::get_blend_point_count() const {
return blend_points_used;
}
-bool AnimationNodeBlendSpace::has_triangle(int p_x, int p_y, int p_z) const {
+bool AnimationNodeBlendSpace2D::has_triangle(int p_x, int p_y, int p_z) const {
ERR_FAIL_INDEX_V(p_x, blend_points_used, false);
ERR_FAIL_INDEX_V(p_y, blend_points_used, false);
@@ -123,7 +123,7 @@ bool AnimationNodeBlendSpace::has_triangle(int p_x, int p_y, int p_z) const {
return false;
}
-void AnimationNodeBlendSpace::add_triangle(int p_x, int p_y, int p_z, int p_at_index) {
+void AnimationNodeBlendSpace2D::add_triangle(int p_x, int p_y, int p_z, int p_at_index) {
ERR_FAIL_INDEX(p_x, blend_points_used);
ERR_FAIL_INDEX(p_y, blend_points_used);
@@ -156,7 +156,7 @@ void AnimationNodeBlendSpace::add_triangle(int p_x, int p_y, int p_z, int p_at_i
triangles.insert(p_at_index, t);
}
}
-int AnimationNodeBlendSpace::get_triangle_point(int p_triangle, int p_point) {
+int AnimationNodeBlendSpace2D::get_triangle_point(int p_triangle, int p_point) {
_update_triangles();
@@ -164,17 +164,17 @@ int AnimationNodeBlendSpace::get_triangle_point(int p_triangle, int p_point) {
ERR_FAIL_INDEX_V(p_triangle, triangles.size(), -1);
return triangles[p_triangle].points[p_point];
}
-void AnimationNodeBlendSpace::remove_triangle(int p_triangle) {
+void AnimationNodeBlendSpace2D::remove_triangle(int p_triangle) {
ERR_FAIL_INDEX(p_triangle, triangles.size());
triangles.remove(p_triangle);
}
-int AnimationNodeBlendSpace::get_triangle_count() const {
+int AnimationNodeBlendSpace2D::get_triangle_count() const {
return triangles.size();
}
-void AnimationNodeBlendSpace::set_min_space(const Vector2 &p_min) {
+void AnimationNodeBlendSpace2D::set_min_space(const Vector2 &p_min) {
min_space = p_min;
if (min_space.x >= max_space.x) {
@@ -184,11 +184,11 @@ void AnimationNodeBlendSpace::set_min_space(const Vector2 &p_min) {
min_space.y = max_space.y - 1;
}
}
-Vector2 AnimationNodeBlendSpace::get_min_space() const {
+Vector2 AnimationNodeBlendSpace2D::get_min_space() const {
return min_space;
}
-void AnimationNodeBlendSpace::set_max_space(const Vector2 &p_max) {
+void AnimationNodeBlendSpace2D::set_max_space(const Vector2 &p_max) {
max_space = p_max;
if (max_space.x <= min_space.x) {
@@ -198,39 +198,39 @@ void AnimationNodeBlendSpace::set_max_space(const Vector2 &p_max) {
max_space.y = min_space.y + 1;
}
}
-Vector2 AnimationNodeBlendSpace::get_max_space() const {
+Vector2 AnimationNodeBlendSpace2D::get_max_space() const {
return max_space;
}
-void AnimationNodeBlendSpace::set_snap(const Vector2 &p_snap) {
+void AnimationNodeBlendSpace2D::set_snap(const Vector2 &p_snap) {
snap = p_snap;
}
-Vector2 AnimationNodeBlendSpace::get_snap() const {
+Vector2 AnimationNodeBlendSpace2D::get_snap() const {
return snap;
}
-void AnimationNodeBlendSpace::set_blend_pos(const Vector2 &p_pos) {
+void AnimationNodeBlendSpace2D::set_blend_position(const Vector2 &p_pos) {
blend_pos = p_pos;
}
-Vector2 AnimationNodeBlendSpace::get_blend_pos() const {
+Vector2 AnimationNodeBlendSpace2D::get_blend_position() const {
return blend_pos;
}
-void AnimationNodeBlendSpace::set_x_label(const String &p_label) {
+void AnimationNodeBlendSpace2D::set_x_label(const String &p_label) {
x_label = p_label;
}
-String AnimationNodeBlendSpace::get_x_label() const {
+String AnimationNodeBlendSpace2D::get_x_label() const {
return x_label;
}
-void AnimationNodeBlendSpace::set_y_label(const String &p_label) {
+void AnimationNodeBlendSpace2D::set_y_label(const String &p_label) {
y_label = p_label;
}
-String AnimationNodeBlendSpace::get_y_label() const {
+String AnimationNodeBlendSpace2D::get_y_label() const {
return y_label;
}
-void AnimationNodeBlendSpace::_add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node) {
+void AnimationNodeBlendSpace2D::_add_blend_point(int p_index, const Ref<AnimationRootNode> &p_node) {
if (p_index == blend_points_used) {
add_blend_point(p_node, Vector2());
} else {
@@ -238,7 +238,7 @@ void AnimationNodeBlendSpace::_add_blend_point(int p_index, const Ref<AnimationR
}
}
-void AnimationNodeBlendSpace::_set_triangles(const Vector<int> &p_triangles) {
+void AnimationNodeBlendSpace2D::_set_triangles(const Vector<int> &p_triangles) {
if (auto_triangles)
return;
@@ -248,7 +248,7 @@ void AnimationNodeBlendSpace::_set_triangles(const Vector<int> &p_triangles) {
}
}
-Vector<int> AnimationNodeBlendSpace::_get_triangles() const {
+Vector<int> AnimationNodeBlendSpace2D::_get_triangles() const {
Vector<int> t;
if (auto_triangles && trianges_dirty)
@@ -263,7 +263,7 @@ Vector<int> AnimationNodeBlendSpace::_get_triangles() const {
return t;
}
-void AnimationNodeBlendSpace::_update_triangles() {
+void AnimationNodeBlendSpace2D::_update_triangles() {
if (!auto_triangles || !trianges_dirty)
return;
@@ -286,7 +286,7 @@ void AnimationNodeBlendSpace::_update_triangles() {
}
}
-Vector2 AnimationNodeBlendSpace::get_closest_point(const Vector2 &p_point) {
+Vector2 AnimationNodeBlendSpace2D::get_closest_point(const Vector2 &p_point) {
_update_triangles();
@@ -323,7 +323,7 @@ Vector2 AnimationNodeBlendSpace::get_closest_point(const Vector2 &p_point) {
return best_point;
}
-void AnimationNodeBlendSpace::_blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights) {
+void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vector2 *p_points, float *r_weights) {
if (p_pos.distance_squared_to(p_points[0]) < CMP_EPSILON2) {
r_weights[0] = 1;
@@ -369,7 +369,7 @@ void AnimationNodeBlendSpace::_blend_triangle(const Vector2 &p_pos, const Vector
r_weights[2] = w;
}
-float AnimationNodeBlendSpace::process(float p_time, bool p_seek) {
+float AnimationNodeBlendSpace2D::process(float p_time, bool p_seek) {
_update_triangles();
@@ -453,11 +453,11 @@ float AnimationNodeBlendSpace::process(float p_time, bool p_seek) {
return mind;
}
-String AnimationNodeBlendSpace::get_caption() const {
- return "BlendSpace";
+String AnimationNodeBlendSpace2D::get_caption() const {
+ return "BlendSpace2D";
}
-void AnimationNodeBlendSpace::_validate_property(PropertyInfo &property) const {
+void AnimationNodeBlendSpace2D::_validate_property(PropertyInfo &property) const {
if (property.name.begins_with("blend_point_")) {
String left = property.name.get_slicec('/', 0);
int idx = left.get_slicec('_', 2).to_int();
@@ -468,57 +468,57 @@ void AnimationNodeBlendSpace::_validate_property(PropertyInfo &property) const {
AnimationRootNode::_validate_property(property);
}
-void AnimationNodeBlendSpace::set_auto_triangles(bool p_enable) {
+void AnimationNodeBlendSpace2D::set_auto_triangles(bool p_enable) {
auto_triangles = p_enable;
if (auto_triangles) {
trianges_dirty = true;
}
}
-bool AnimationNodeBlendSpace::get_auto_triangles() const {
+bool AnimationNodeBlendSpace2D::get_auto_triangles() const {
return auto_triangles;
}
-void AnimationNodeBlendSpace::_bind_methods() {
+void AnimationNodeBlendSpace2D::_bind_methods() {
- ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace::add_blend_point, DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace::set_blend_point_position);
- ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace::get_blend_point_position);
- ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace::set_blend_point_node);
- ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace::get_blend_point_node);
- ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace::remove_blend_point);
- ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace::get_blend_point_count);
+ ClassDB::bind_method(D_METHOD("add_blend_point", "node", "pos", "at_index"), &AnimationNodeBlendSpace2D::add_blend_point, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("set_blend_point_position", "point", "pos"), &AnimationNodeBlendSpace2D::set_blend_point_position);
+ ClassDB::bind_method(D_METHOD("get_blend_point_position", "point"), &AnimationNodeBlendSpace2D::get_blend_point_position);
+ ClassDB::bind_method(D_METHOD("set_blend_point_node", "point", "node"), &AnimationNodeBlendSpace2D::set_blend_point_node);
+ ClassDB::bind_method(D_METHOD("get_blend_point_node", "point"), &AnimationNodeBlendSpace2D::get_blend_point_node);
+ ClassDB::bind_method(D_METHOD("remove_blend_point", "point"), &AnimationNodeBlendSpace2D::remove_blend_point);
+ ClassDB::bind_method(D_METHOD("get_blend_point_count"), &AnimationNodeBlendSpace2D::get_blend_point_count);
- ClassDB::bind_method(D_METHOD("add_triangle", "x", "y", "z", "at_index"), &AnimationNodeBlendSpace::add_triangle, DEFVAL(-1));
- ClassDB::bind_method(D_METHOD("get_triangle_point", "triangle", "point"), &AnimationNodeBlendSpace::get_triangle_point);
- ClassDB::bind_method(D_METHOD("remove_triangle", "triangle"), &AnimationNodeBlendSpace::remove_triangle);
- ClassDB::bind_method(D_METHOD("get_triangle_count"), &AnimationNodeBlendSpace::get_triangle_count);
+ ClassDB::bind_method(D_METHOD("add_triangle", "x", "y", "z", "at_index"), &AnimationNodeBlendSpace2D::add_triangle, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("get_triangle_point", "triangle", "point"), &AnimationNodeBlendSpace2D::get_triangle_point);
+ ClassDB::bind_method(D_METHOD("remove_triangle", "triangle"), &AnimationNodeBlendSpace2D::remove_triangle);
+ ClassDB::bind_method(D_METHOD("get_triangle_count"), &AnimationNodeBlendSpace2D::get_triangle_count);
- ClassDB::bind_method(D_METHOD("set_min_space", "min_space"), &AnimationNodeBlendSpace::set_min_space);
- ClassDB::bind_method(D_METHOD("get_min_space"), &AnimationNodeBlendSpace::get_min_space);
+ ClassDB::bind_method(D_METHOD("set_min_space", "min_space"), &AnimationNodeBlendSpace2D::set_min_space);
+ ClassDB::bind_method(D_METHOD("get_min_space"), &AnimationNodeBlendSpace2D::get_min_space);
- ClassDB::bind_method(D_METHOD("set_max_space", "max_space"), &AnimationNodeBlendSpace::set_max_space);
- ClassDB::bind_method(D_METHOD("get_max_space"), &AnimationNodeBlendSpace::get_max_space);
+ ClassDB::bind_method(D_METHOD("set_max_space", "max_space"), &AnimationNodeBlendSpace2D::set_max_space);
+ ClassDB::bind_method(D_METHOD("get_max_space"), &AnimationNodeBlendSpace2D::get_max_space);
- ClassDB::bind_method(D_METHOD("set_snap", "snap"), &AnimationNodeBlendSpace::set_snap);
- ClassDB::bind_method(D_METHOD("get_snap"), &AnimationNodeBlendSpace::get_snap);
+ ClassDB::bind_method(D_METHOD("set_snap", "snap"), &AnimationNodeBlendSpace2D::set_snap);
+ ClassDB::bind_method(D_METHOD("get_snap"), &AnimationNodeBlendSpace2D::get_snap);
- ClassDB::bind_method(D_METHOD("set_blend_pos", "pos"), &AnimationNodeBlendSpace::set_blend_pos);
- ClassDB::bind_method(D_METHOD("get_blend_pos"), &AnimationNodeBlendSpace::get_blend_pos);
+ ClassDB::bind_method(D_METHOD("set_blend_position", "pos"), &AnimationNodeBlendSpace2D::set_blend_position);
+ ClassDB::bind_method(D_METHOD("get_blend_position"), &AnimationNodeBlendSpace2D::get_blend_position);
- ClassDB::bind_method(D_METHOD("set_x_label", "text"), &AnimationNodeBlendSpace::set_x_label);
- ClassDB::bind_method(D_METHOD("get_x_label"), &AnimationNodeBlendSpace::get_x_label);
+ ClassDB::bind_method(D_METHOD("set_x_label", "text"), &AnimationNodeBlendSpace2D::set_x_label);
+ ClassDB::bind_method(D_METHOD("get_x_label"), &AnimationNodeBlendSpace2D::get_x_label);
- ClassDB::bind_method(D_METHOD("set_y_label", "text"), &AnimationNodeBlendSpace::set_y_label);
- ClassDB::bind_method(D_METHOD("get_y_label"), &AnimationNodeBlendSpace::get_y_label);
+ ClassDB::bind_method(D_METHOD("set_y_label", "text"), &AnimationNodeBlendSpace2D::set_y_label);
+ ClassDB::bind_method(D_METHOD("get_y_label"), &AnimationNodeBlendSpace2D::get_y_label);
- ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace::_add_blend_point);
+ ClassDB::bind_method(D_METHOD("_add_blend_point", "index", "node"), &AnimationNodeBlendSpace2D::_add_blend_point);
- ClassDB::bind_method(D_METHOD("_set_triangles", "triangles"), &AnimationNodeBlendSpace::_set_triangles);
- ClassDB::bind_method(D_METHOD("_get_triangles"), &AnimationNodeBlendSpace::_get_triangles);
+ ClassDB::bind_method(D_METHOD("_set_triangles", "triangles"), &AnimationNodeBlendSpace2D::_set_triangles);
+ ClassDB::bind_method(D_METHOD("_get_triangles"), &AnimationNodeBlendSpace2D::_get_triangles);
- ClassDB::bind_method(D_METHOD("set_auto_triangles", "enable"), &AnimationNodeBlendSpace::set_auto_triangles);
- ClassDB::bind_method(D_METHOD("get_auto_triangles"), &AnimationNodeBlendSpace::get_auto_triangles);
+ ClassDB::bind_method(D_METHOD("set_auto_triangles", "enable"), &AnimationNodeBlendSpace2D::set_auto_triangles);
+ ClassDB::bind_method(D_METHOD("get_auto_triangles"), &AnimationNodeBlendSpace2D::get_auto_triangles);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_triangles", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_auto_triangles", "get_auto_triangles");
@@ -532,12 +532,12 @@ void AnimationNodeBlendSpace::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "min_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_min_space", "get_min_space");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "max_space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_max_space", "get_max_space");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "snap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_snap", "get_snap");
- ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "blend_pos", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_blend_pos", "get_blend_pos");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "blend_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_blend_position", "get_blend_position");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "x_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_x_label", "get_x_label");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "y_label", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_y_label", "get_y_label");
}
-AnimationNodeBlendSpace::AnimationNodeBlendSpace() {
+AnimationNodeBlendSpace2D::AnimationNodeBlendSpace2D() {
auto_triangles = true;
blend_points_used = 0;
@@ -549,10 +549,10 @@ AnimationNodeBlendSpace::AnimationNodeBlendSpace() {
trianges_dirty = false;
}
-AnimationNodeBlendSpace::~AnimationNodeBlendSpace() {
+AnimationNodeBlendSpace2D::~AnimationNodeBlendSpace2D() {
for (int i = 0; i < blend_points_used; i++) {
blend_points[i].node->set_parent(this);
- blend_points[i].node->set_graph_player(get_graph_player());
+ blend_points[i].node->set_tree(get_tree());
}
}
diff --git a/scene/animation/animation_blend_space.h b/scene/animation/animation_blend_space_2d.h
index 3eda3d2d39..5af0b9ba7f 100644
--- a/scene/animation/animation_blend_space.h
+++ b/scene/animation/animation_blend_space_2d.h
@@ -1,10 +1,10 @@
-#ifndef ANIMATION_BLEND_SPACE_H
-#define ANIMATION_BLEND_SPACE_H
+#ifndef ANIMATION_BLEND_SPACE_2D_H
+#define ANIMATION_BLEND_SPACE_2D_H
-#include "scene/animation/animation_graph_player.h"
+#include "scene/animation/animation_tree.h"
-class AnimationNodeBlendSpace : public AnimationRootNode {
- GDCLASS(AnimationNodeBlendSpace, AnimationRootNode)
+class AnimationNodeBlendSpace2D : public AnimationRootNode {
+ GDCLASS(AnimationNodeBlendSpace2D, AnimationRootNode)
enum {
MAX_BLEND_POINTS = 64
@@ -70,8 +70,8 @@ public:
void set_snap(const Vector2 &p_snap);
Vector2 get_snap() const;
- void set_blend_pos(const Vector2 &p_pos);
- Vector2 get_blend_pos() const;
+ void set_blend_position(const Vector2 &p_pos);
+ Vector2 get_blend_position() const;
void set_x_label(const String &p_label);
String get_x_label() const;
@@ -79,16 +79,16 @@ public:
void set_y_label(const String &p_label);
String get_y_label() const;
- float process(float p_time, bool p_seek);
- String get_caption() const;
+ virtual float process(float p_time, bool p_seek);
+ virtual String get_caption() const;
Vector2 get_closest_point(const Vector2 &p_point);
void set_auto_triangles(bool p_enable);
bool get_auto_triangles() const;
- AnimationNodeBlendSpace();
- ~AnimationNodeBlendSpace();
+ AnimationNodeBlendSpace2D();
+ ~AnimationNodeBlendSpace2D();
};
-#endif // ANIMATION_BLEND_SPACE_H
+#endif // ANIMATION_BLEND_SPACE_2D_H
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index ced0cbbbba..feacea5656 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -16,7 +16,7 @@ float AnimationNodeAnimation::get_playback_time() const {
void AnimationNodeAnimation::_validate_property(PropertyInfo &property) const {
if (property.name == "animation") {
- AnimationGraphPlayer *gp = get_graph_player();
+ AnimationTree *gp = get_tree();
if (gp && gp->has_node(gp->get_animation_player())) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(gp->get_node(gp->get_animation_player()));
if (ap) {
@@ -762,13 +762,13 @@ void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNod
ERR_FAIL_COND(nodes.has(p_name));
ERR_FAIL_COND(p_node.is_null());
ERR_FAIL_COND(p_node->get_parent().is_valid());
- ERR_FAIL_COND(p_node->get_graph_player() != NULL);
+ ERR_FAIL_COND(p_node->get_tree() != 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->set_parent(this);
- p_node->set_graph_player(get_graph_player());
+ p_node->set_tree(get_tree());
emit_changed();
}
@@ -804,7 +804,7 @@ void AnimationNodeBlendTree::remove_node(const StringName &p_name) {
node->set_input_connection(i, StringName());
}
node->set_parent(NULL);
- node->set_graph_player(NULL);
+ node->set_tree(NULL);
}
nodes.erase(p_name);
@@ -965,13 +965,13 @@ Vector2 AnimationNodeBlendTree::get_graph_offset() const {
return graph_offset;
}
-void AnimationNodeBlendTree::set_graph_player(AnimationGraphPlayer *p_player) {
+void AnimationNodeBlendTree::set_tree(AnimationTree *p_player) {
- AnimationNode::set_graph_player(p_player);
+ AnimationNode::set_tree(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);
+ node->set_tree(p_player);
}
}
@@ -1107,6 +1107,6 @@ AnimationNodeBlendTree::~AnimationNodeBlendTree() {
for (Map<StringName, Ref<AnimationNode> >::Element *E = nodes.front(); E; E = E->next()) {
E->get()->set_parent(NULL);
- E->get()->set_graph_player(NULL);
+ E->get()->set_tree(NULL);
}
}
diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h
index 7623fd3e57..8d7932196c 100644
--- a/scene/animation/animation_blend_tree.h
+++ b/scene/animation/animation_blend_tree.h
@@ -1,7 +1,7 @@
#ifndef ANIMATION_BLEND_TREE_H
#define ANIMATION_BLEND_TREE_H
-#include "scene/animation/animation_graph_player.h"
+#include "scene/animation/animation_tree.h"
class AnimationNodeAnimation : public AnimationRootNode {
@@ -318,7 +318,7 @@ public:
void set_graph_offset(const Vector2 &p_graph_offset);
Vector2 get_graph_offset() const;
- virtual void set_graph_player(AnimationGraphPlayer *p_player);
+ virtual void set_tree(AnimationTree *p_player);
AnimationNodeBlendTree();
~AnimationNodeBlendTree();
};
diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp
new file mode 100644
index 0000000000..c5ad980806
--- /dev/null
+++ b/scene/animation/animation_node_state_machine.cpp
@@ -0,0 +1,790 @@
+#include "animation_node_state_machine.h"
+
+/////////////////////////////////////////////////
+
+void AnimationNodeStateMachineTransition::set_switch_mode(SwitchMode p_mode) {
+
+ switch_mode = p_mode;
+}
+
+AnimationNodeStateMachineTransition::SwitchMode AnimationNodeStateMachineTransition::get_switch_mode() const {
+
+ return switch_mode;
+}
+
+void AnimationNodeStateMachineTransition::set_auto_advance(bool p_enable) {
+ auto_advance = p_enable;
+}
+
+bool AnimationNodeStateMachineTransition::has_auto_advance() const {
+ return auto_advance;
+}
+
+void AnimationNodeStateMachineTransition::set_xfade_time(float p_xfade) {
+
+ ERR_FAIL_COND(p_xfade < 0);
+ xfade = p_xfade;
+ emit_changed();
+}
+
+float AnimationNodeStateMachineTransition::get_xfade_time() const {
+ return xfade;
+}
+
+void AnimationNodeStateMachineTransition::set_disabled(bool p_disabled) {
+ disabled = p_disabled;
+ emit_changed();
+}
+
+bool AnimationNodeStateMachineTransition::is_disabled() const {
+ return disabled;
+}
+
+void AnimationNodeStateMachineTransition::set_priority(int p_priority) {
+ priority = p_priority;
+ emit_changed();
+}
+
+int AnimationNodeStateMachineTransition::get_priority() const {
+ return priority;
+}
+
+void AnimationNodeStateMachineTransition::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_switch_mode", "mode"), &AnimationNodeStateMachineTransition::set_switch_mode);
+ ClassDB::bind_method(D_METHOD("get_switch_mode"), &AnimationNodeStateMachineTransition::get_switch_mode);
+
+ ClassDB::bind_method(D_METHOD("set_auto_advance", "auto_advance"), &AnimationNodeStateMachineTransition::set_auto_advance);
+ ClassDB::bind_method(D_METHOD("has_auto_advance"), &AnimationNodeStateMachineTransition::has_auto_advance);
+
+ ClassDB::bind_method(D_METHOD("set_xfade_time", "secs"), &AnimationNodeStateMachineTransition::set_xfade_time);
+ ClassDB::bind_method(D_METHOD("get_xfade_time"), &AnimationNodeStateMachineTransition::get_xfade_time);
+
+ ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &AnimationNodeStateMachineTransition::set_disabled);
+ ClassDB::bind_method(D_METHOD("is_disabled"), &AnimationNodeStateMachineTransition::is_disabled);
+
+ ClassDB::bind_method(D_METHOD("set_priority", "priority"), &AnimationNodeStateMachineTransition::set_priority);
+ ClassDB::bind_method(D_METHOD("get_priority"), &AnimationNodeStateMachineTransition::get_priority);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "switch_mode", PROPERTY_HINT_ENUM, "Immediate,Sync,AtEnd"), "set_switch_mode", "get_switch_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_advance"), "set_auto_advance", "has_auto_advance");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01"), "set_xfade_time", "get_xfade_time");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled");
+
+ BIND_CONSTANT(SWITCH_MODE_IMMEDIATE);
+ BIND_CONSTANT(SWITCH_MODE_SYNC);
+ BIND_CONSTANT(SWITCH_MODE_AT_END);
+}
+
+AnimationNodeStateMachineTransition::AnimationNodeStateMachineTransition() {
+
+ switch_mode = SWITCH_MODE_IMMEDIATE;
+ auto_advance = false;
+ xfade = 0;
+ disabled = false;
+ priority = 1;
+}
+
+///////////////////////////////////////////////////////
+void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<AnimationNode> p_node) {
+
+ ERR_FAIL_COND(states.has(p_name));
+ ERR_FAIL_COND(p_node.is_null());
+ ERR_FAIL_COND(p_node->get_parent().is_valid());
+ ERR_FAIL_COND(p_node->get_tree() != NULL);
+ ERR_FAIL_COND(String(p_name).find("/") != -1);
+ states[p_name] = p_node;
+
+ p_node->set_parent(this);
+ p_node->set_tree(get_tree());
+
+ emit_changed();
+}
+
+Ref<AnimationNode> AnimationNodeStateMachine::get_node(const StringName &p_name) const {
+
+ ERR_FAIL_COND_V(!states.has(p_name), Ref<AnimationNode>());
+
+ return states[p_name];
+}
+
+StringName AnimationNodeStateMachine::get_node_name(const Ref<AnimationNode> &p_node) const {
+ for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) {
+ if (E->get() == p_node) {
+ return E->key();
+ }
+ }
+
+ ERR_FAIL_V(StringName());
+}
+
+bool AnimationNodeStateMachine::has_node(const StringName &p_name) const {
+ return states.has(p_name);
+}
+void AnimationNodeStateMachine::remove_node(const StringName &p_name) {
+
+ ERR_FAIL_COND(!states.has(p_name));
+
+ {
+ //erase node connections
+ Ref<AnimationNode> node = states[p_name];
+ for (int i = 0; i < node->get_input_count(); i++) {
+ node->set_input_connection(i, StringName());
+ }
+ node->set_parent(NULL);
+ node->set_tree(NULL);
+ }
+
+ states.erase(p_name);
+ path.erase(p_name);
+
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from == p_name || transitions[i].to == p_name) {
+ transitions.remove(i);
+ i--;
+ }
+ }
+
+ if (start_node == p_name) {
+ start_node = StringName();
+ }
+
+ if (end_node == p_name) {
+ end_node = StringName();
+ }
+
+ if (playing && current == p_name) {
+ stop();
+ }
+ emit_changed();
+}
+
+void AnimationNodeStateMachine::rename_node(const StringName &p_name, const StringName &p_new_name) {
+
+ ERR_FAIL_COND(!states.has(p_name));
+ ERR_FAIL_COND(states.has(p_new_name));
+
+ states[p_new_name] = states[p_name];
+ states.erase(p_name);
+
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from == p_name) {
+ transitions[i].from = p_new_name;
+ }
+
+ if (transitions[i].to == p_name) {
+ transitions[i].to = p_new_name;
+ }
+ }
+
+ if (start_node == p_name) {
+ start_node = p_new_name;
+ }
+
+ if (end_node == p_name) {
+ end_node = p_new_name;
+ }
+
+ if (playing && current == p_name) {
+ current = p_new_name;
+ }
+
+ path.clear(); //clear path
+}
+
+void AnimationNodeStateMachine::get_node_list(List<StringName> *r_nodes) const {
+
+ List<StringName> nodes;
+ for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) {
+ nodes.push_back(E->key());
+ }
+ nodes.sort_custom<StringName::AlphCompare>();
+
+ for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
+ r_nodes->push_back(E->get());
+ }
+}
+
+bool AnimationNodeStateMachine::has_transition(const StringName &p_from, const StringName &p_to) const {
+
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from == p_from && transitions[i].to == p_to)
+ return true;
+ }
+ return false;
+}
+
+int AnimationNodeStateMachine::find_transition(const StringName &p_from, const StringName &p_to) const {
+
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from == p_from && transitions[i].to == p_to)
+ return i;
+ }
+ return -1;
+}
+
+void AnimationNodeStateMachine::add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition) {
+
+ ERR_FAIL_COND(p_from == p_to);
+ ERR_FAIL_COND(!states.has(p_from));
+ ERR_FAIL_COND(!states.has(p_to));
+ ERR_FAIL_COND(p_transition.is_null());
+
+ for (int i = 0; i < transitions.size(); i++) {
+ ERR_FAIL_COND(transitions[i].from == p_from && transitions[i].to == p_to);
+ }
+
+ Transition tr;
+ tr.from = p_from;
+ tr.to = p_to;
+ tr.transition = p_transition;
+
+ transitions.push_back(tr);
+}
+
+Ref<AnimationNodeStateMachineTransition> AnimationNodeStateMachine::get_transition(int p_transition) const {
+ ERR_FAIL_INDEX_V(p_transition, transitions.size(), Ref<AnimationNodeStateMachineTransition>());
+ return transitions[p_transition].transition;
+}
+StringName AnimationNodeStateMachine::get_transition_from(int p_transition) const {
+
+ ERR_FAIL_INDEX_V(p_transition, transitions.size(), StringName());
+ return transitions[p_transition].from;
+}
+StringName AnimationNodeStateMachine::get_transition_to(int p_transition) const {
+
+ ERR_FAIL_INDEX_V(p_transition, transitions.size(), StringName());
+ return transitions[p_transition].to;
+}
+
+int AnimationNodeStateMachine::get_transition_count() const {
+
+ return transitions.size();
+}
+void AnimationNodeStateMachine::remove_transition(const StringName &p_from, const StringName &p_to) {
+
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from == p_from && transitions[i].to == p_to) {
+ transitions.remove(i);
+ return;
+ }
+ }
+
+ if (playing) {
+ path.clear();
+ }
+}
+
+void AnimationNodeStateMachine::remove_transition_by_index(int p_transition) {
+
+ transitions.remove(p_transition);
+ if (playing) {
+ path.clear();
+ }
+}
+
+void AnimationNodeStateMachine::set_start_node(const StringName &p_node) {
+
+ ERR_FAIL_COND(p_node != StringName() && !states.has(p_node));
+ start_node = p_node;
+}
+
+String AnimationNodeStateMachine::get_start_node() const {
+
+ return start_node;
+}
+
+void AnimationNodeStateMachine::set_end_node(const StringName &p_node) {
+
+ ERR_FAIL_COND(p_node != StringName() && !states.has(p_node));
+ end_node = p_node;
+}
+
+String AnimationNodeStateMachine::get_end_node() const {
+
+ return end_node;
+}
+
+void AnimationNodeStateMachine::set_graph_offset(const Vector2 &p_offset) {
+ graph_offset = p_offset;
+}
+
+Vector2 AnimationNodeStateMachine::get_graph_offset() const {
+ return graph_offset;
+}
+
+float AnimationNodeStateMachine::process(float p_time, bool p_seek) {
+
+ //if not playing and it can restart, then restart
+ if (!playing) {
+ if (start_node) {
+ start(start_node);
+ } else {
+ return 0;
+ }
+ }
+
+ bool do_start = (p_seek && p_time == 0) || play_start || current == StringName();
+
+ if (do_start) {
+
+ if (start_node != StringName() && p_seek && p_time == 0) {
+ current = start_node;
+ }
+
+ len_current = blend_node(states[current], 0, true, 1.0, FILTER_IGNORE, false);
+ pos_current = 0;
+ loops_current = 0;
+ play_start = false;
+ }
+
+ float fade_blend = 1.0;
+
+ if (fading_from != StringName()) {
+
+ if (!p_seek) {
+ fading_pos += p_time;
+ }
+ fade_blend = MIN(1.0, fading_pos / fading_time);
+ if (fade_blend >= 1.0) {
+ fading_from = StringName();
+ }
+ }
+
+ float rem = blend_node(states[current], p_time, p_seek, fade_blend, FILTER_IGNORE, false);
+
+ if (fading_from != StringName()) {
+
+ blend_node(states[fading_from], p_time, p_seek, 1.0 - fade_blend, FILTER_IGNORE, false);
+ }
+
+ //guess playback position
+ if (rem > len_current) { // weird but ok
+ len_current = rem;
+ }
+
+ { //advance and loop check
+
+ float next_pos = len_current - rem;
+
+ if (next_pos < pos_current) {
+ loops_current++;
+ }
+ pos_current = next_pos; //looped
+ }
+
+ //find next
+ StringName next;
+ float next_xfade = 0;
+ AnimationNodeStateMachineTransition::SwitchMode switch_mode = AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE;
+
+ if (path.size()) {
+
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from == current && transitions[i].to == path[0]) {
+ next_xfade = transitions[i].transition->get_xfade_time();
+ switch_mode = transitions[i].transition->get_switch_mode();
+ next = path[0];
+ }
+ }
+ } else {
+ float priority_best = 1e20;
+ int auto_advance_to = -1;
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from == current && transitions[i].transition->has_auto_advance()) {
+
+ if (transitions[i].transition->get_priority() < priority_best) {
+ auto_advance_to = i;
+ }
+ }
+ }
+
+ if (auto_advance_to != -1) {
+ next = transitions[auto_advance_to].to;
+ next_xfade = transitions[auto_advance_to].transition->get_xfade_time();
+ switch_mode = transitions[auto_advance_to].transition->get_switch_mode();
+ }
+ }
+
+ //if next, see when to transition
+ if (next != StringName()) {
+
+ bool goto_next = false;
+
+ if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_IMMEDIATE) {
+ goto_next = fading_from == StringName();
+ } else {
+ goto_next = next_xfade >= (len_current - pos_current) || loops_current > 0;
+ if (loops_current > 0) {
+ next_xfade = 0;
+ }
+ }
+
+ if (goto_next) { //loops should be used because fade time may be too small or zero and animation may have looped
+
+ if (next_xfade) {
+ //time to fade, baby
+ fading_from = current;
+ fading_time = next_xfade;
+ fading_pos = 0;
+ } else {
+ fading_from = StringName();
+ fading_pos = 0;
+ }
+
+ if (path.size()) { //if it came from path, remove path
+ path.remove(0);
+ }
+ current = next;
+ if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) {
+ len_current = blend_node(states[current], 0, true, 0, FILTER_IGNORE, false);
+ pos_current = MIN(pos_current, len_current);
+ blend_node(states[current], pos_current, true, 0, FILTER_IGNORE, false);
+
+ } else {
+ len_current = blend_node(states[current], 0, true, 0, FILTER_IGNORE, false);
+ pos_current = 0;
+ }
+
+ rem = len_current; //so it does not show 0 on transition
+ loops_current = 0;
+ }
+ }
+
+ //compute time left for transitions by using the end node
+
+ if (end_node != StringName() && end_node != current) {
+
+ rem = blend_node(states[end_node], 0, true, 0, FILTER_IGNORE, false);
+ }
+
+ return rem;
+}
+
+bool AnimationNodeStateMachine::travel(const StringName &p_state) {
+ ERR_FAIL_COND_V(!playing, false);
+ ERR_FAIL_COND_V(!states.has(p_state), false);
+ ERR_FAIL_COND_V(!states.has(current), false);
+
+ path.clear(); //a new one will be needed
+
+ if (current == p_state)
+ return true; //nothing to do
+
+ loops_current = 0; // reset loops, so fade does not happen immediately
+
+ Vector2 current_pos = states[current]->get_position();
+ Vector2 target_pos = states[p_state]->get_position();
+
+ Map<StringName, AStarCost> cost_map;
+
+ List<int> open_list;
+
+ //build open list
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from == current) {
+ open_list.push_back(i);
+ float cost = states[transitions[i].to]->get_position().distance_to(current_pos);
+ cost *= transitions[i].transition->get_priority();
+ AStarCost ap;
+ ap.prev = current;
+ ap.distance = cost;
+ cost_map[transitions[i].to] = ap;
+
+ if (transitions[i].to == p_state) { //prematurely found it! :D
+ path.push_back(p_state);
+ return true;
+ }
+ }
+ }
+
+ //begin astar
+ bool found_route = false;
+ while (!found_route) {
+
+ if (open_list.size() == 0) {
+ return false; //no path found
+ }
+
+ //find the last cost transition
+ List<int>::Element *least_cost_transition = NULL;
+ float least_cost = 1e20;
+
+ for (List<int>::Element *E = open_list.front(); E; E = E->next()) {
+
+ float cost = cost_map[transitions[E->get()].to].distance;
+ cost += states[transitions[E->get()].to]->get_position().distance_to(target_pos);
+
+ if (cost < least_cost) {
+ least_cost_transition = E;
+ }
+ }
+
+ StringName transition_prev = transitions[least_cost_transition->get()].from;
+ StringName transition = transitions[least_cost_transition->get()].to;
+
+ for (int i = 0; i < transitions.size(); i++) {
+ if (transitions[i].from != transition || transitions[i].to == transition_prev) {
+ continue; //not interested on those
+ }
+
+ float distance = states[transitions[i].from]->get_position().distance_to(states[transitions[i].to]->get_position());
+ distance *= transitions[i].transition->get_priority();
+ distance += cost_map[transitions[i].from].distance;
+
+ if (cost_map.has(transitions[i].to)) {
+ //oh this was visited already, can we win the cost?
+ if (distance < cost_map[transitions[i].to].distance) {
+ cost_map[transitions[i].to].distance = distance;
+ cost_map[transitions[i].to].prev = transitions[i].from;
+ }
+ } else {
+ //add to open list
+ AStarCost ac;
+ ac.prev = transitions[i].from;
+ ac.distance = distance;
+ cost_map[transitions[i].to] = ac;
+
+ open_list.push_back(i);
+
+ if (transitions[i].to == p_state) {
+ found_route = true;
+ break;
+ }
+ }
+ }
+
+ if (found_route) {
+ break;
+ }
+
+ open_list.erase(least_cost_transition);
+ }
+
+ //make path
+ StringName at = p_state;
+ while (at != current) {
+ path.push_back(at);
+ at = cost_map[at].prev;
+ }
+
+ path.invert();
+
+ return true;
+}
+
+void AnimationNodeStateMachine::start(const StringName &p_state) {
+
+ ERR_FAIL_COND(!states.has(p_state));
+ path.clear();
+ current = p_state;
+ playing = true;
+ play_start = true;
+}
+void AnimationNodeStateMachine::stop() {
+ playing = false;
+ play_start = false;
+ current = StringName();
+}
+bool AnimationNodeStateMachine::is_playing() const {
+
+ return playing;
+}
+StringName AnimationNodeStateMachine::get_current_node() const {
+ if (!playing) {
+ return StringName();
+ }
+
+ return current;
+}
+
+StringName AnimationNodeStateMachine::get_blend_from_node() const {
+ if (!playing) {
+ return StringName();
+ }
+
+ return fading_from;
+}
+
+float AnimationNodeStateMachine::get_current_play_pos() const {
+ return pos_current;
+}
+float AnimationNodeStateMachine::get_current_length() const {
+ return len_current;
+}
+
+Vector<StringName> AnimationNodeStateMachine::get_travel_path() const {
+ return path;
+}
+String AnimationNodeStateMachine::get_caption() const {
+ return "StateMachine";
+}
+
+void AnimationNodeStateMachine::_notification(int p_what) {
+}
+
+void AnimationNodeStateMachine::set_tree(AnimationTree *p_player) {
+
+ AnimationNode::set_tree(p_player);
+
+ for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) {
+ Ref<AnimationRootNode> node = E->get();
+ node->set_tree(p_player);
+ }
+}
+
+bool AnimationNodeStateMachine::_set(const StringName &p_name, const Variant &p_value) {
+
+ String name = p_name;
+ if (name.begins_with("states/")) {
+ String node_name = name.get_slicec('/', 1);
+ String what = name.get_slicec('/', 2);
+
+ if (what == "node") {
+ Ref<AnimationNode> anode = p_value;
+ if (anode.is_valid()) {
+ add_node(node_name, p_value);
+ }
+ return true;
+ }
+
+ if (what == "position") {
+
+ if (states.has(node_name)) {
+ states[node_name]->set_position(p_value);
+ }
+ return true;
+ }
+ } else if (name == "transitions") {
+
+ Array trans = p_value;
+ ERR_FAIL_COND_V(trans.size() % 3 != 0, false);
+
+ for (int i = 0; i < trans.size(); i += 3) {
+ add_transition(trans[i], trans[i + 1], trans[i + 2]);
+ }
+ return true;
+ } else if (name == "start_node") {
+ set_start_node(p_value);
+ return true;
+ } else if (name == "end_node") {
+ set_end_node(p_value);
+ return true;
+ } else if (name == "graph_offset") {
+ set_graph_offset(p_value);
+ return true;
+ }
+
+ return false;
+}
+
+bool AnimationNodeStateMachine::_get(const StringName &p_name, Variant &r_ret) const {
+
+ String name = p_name;
+ if (name.begins_with("states/")) {
+ String node_name = name.get_slicec('/', 1);
+ String what = name.get_slicec('/', 2);
+
+ if (what == "node") {
+ if (states.has(node_name)) {
+ r_ret = states[node_name];
+ return true;
+ }
+ }
+
+ if (what == "position") {
+
+ if (states.has(node_name)) {
+ r_ret = states[node_name]->get_position();
+ return true;
+ }
+ }
+ } else if (name == "transitions") {
+ Array trans;
+ trans.resize(transitions.size() * 3);
+
+ for (int i = 0; i < transitions.size(); i++) {
+ trans[i * 3 + 0] = transitions[i].from;
+ trans[i * 3 + 1] = transitions[i].to;
+ trans[i * 3 + 2] = transitions[i].transition;
+ }
+
+ r_ret = trans;
+ return true;
+ } else if (name == "start_node") {
+ r_ret = get_start_node();
+ return true;
+ } else if (name == "end_node") {
+ r_ret = get_end_node();
+ return true;
+ } else if (name == "graph_offset") {
+ r_ret = get_graph_offset();
+ return true;
+ }
+
+ return false;
+}
+void AnimationNodeStateMachine::_get_property_list(List<PropertyInfo> *p_list) const {
+
+ List<StringName> names;
+ for (Map<StringName, Ref<AnimationRootNode> >::Element *E = states.front(); E; E = E->next()) {
+ names.push_back(E->key());
+ }
+ names.sort_custom<StringName::AlphCompare>();
+
+ for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ String name = E->get();
+ p_list->push_back(PropertyInfo(Variant::OBJECT, "states/" + name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, "states/" + name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ }
+
+ p_list->push_back(PropertyInfo(Variant::ARRAY, "transitions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING, "start_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::STRING, "end_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+}
+
+void AnimationNodeStateMachine::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("add_node", "name", "node"), &AnimationNodeStateMachine::add_node);
+ ClassDB::bind_method(D_METHOD("get_node", "name"), &AnimationNodeStateMachine::get_node);
+ ClassDB::bind_method(D_METHOD("remove_node", "name"), &AnimationNodeStateMachine::remove_node);
+ ClassDB::bind_method(D_METHOD("rename_node", "name", "new_name"), &AnimationNodeStateMachine::rename_node);
+ ClassDB::bind_method(D_METHOD("has_node", "name"), &AnimationNodeStateMachine::has_node);
+ ClassDB::bind_method(D_METHOD("get_node_name", "node"), &AnimationNodeStateMachine::get_node_name);
+
+ ClassDB::bind_method(D_METHOD("has_transition", "from", "to"), &AnimationNodeStateMachine::add_transition);
+ ClassDB::bind_method(D_METHOD("add_transition", "from", "to", "transition"), &AnimationNodeStateMachine::add_transition);
+ ClassDB::bind_method(D_METHOD("get_transition", "idx"), &AnimationNodeStateMachine::get_transition);
+ ClassDB::bind_method(D_METHOD("get_transition_from", "idx"), &AnimationNodeStateMachine::get_transition_from);
+ ClassDB::bind_method(D_METHOD("get_transition_to", "idx"), &AnimationNodeStateMachine::get_transition_to);
+ ClassDB::bind_method(D_METHOD("get_transition_count"), &AnimationNodeStateMachine::get_transition_count);
+ ClassDB::bind_method(D_METHOD("remove_transition_by_index", "idx"), &AnimationNodeStateMachine::remove_transition_by_index);
+ ClassDB::bind_method(D_METHOD("remove_transition", "from", "to"), &AnimationNodeStateMachine::remove_transition);
+
+ ClassDB::bind_method(D_METHOD("set_start_node", "name"), &AnimationNodeStateMachine::set_start_node);
+ ClassDB::bind_method(D_METHOD("get_start_node"), &AnimationNodeStateMachine::get_start_node);
+
+ ClassDB::bind_method(D_METHOD("set_end_node", "name"), &AnimationNodeStateMachine::set_end_node);
+ ClassDB::bind_method(D_METHOD("get_end_node"), &AnimationNodeStateMachine::get_end_node);
+
+ ClassDB::bind_method(D_METHOD("set_graph_offset", "name"), &AnimationNodeStateMachine::set_graph_offset);
+ ClassDB::bind_method(D_METHOD("get_graph_offset"), &AnimationNodeStateMachine::get_graph_offset);
+
+ ClassDB::bind_method(D_METHOD("travel", "to_node"), &AnimationNodeStateMachine::travel);
+ ClassDB::bind_method(D_METHOD("start", "node"), &AnimationNodeStateMachine::start);
+ ClassDB::bind_method(D_METHOD("stop"), &AnimationNodeStateMachine::stop);
+ ClassDB::bind_method(D_METHOD("is_playing"), &AnimationNodeStateMachine::is_playing);
+ ClassDB::bind_method(D_METHOD("get_current_node"), &AnimationNodeStateMachine::get_current_node);
+ ClassDB::bind_method(D_METHOD("get_travel_path"), &AnimationNodeStateMachine::get_travel_path);
+}
+
+AnimationNodeStateMachine::AnimationNodeStateMachine() {
+
+ play_start = false;
+
+ playing = false;
+ len_current = 0;
+
+ fading_time = 0;
+}
diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h
new file mode 100644
index 0000000000..e7357e09ea
--- /dev/null
+++ b/scene/animation/animation_node_state_machine.h
@@ -0,0 +1,142 @@
+#ifndef ANIMATION_NODE_STATE_MACHINE_H
+#define ANIMATION_NODE_STATE_MACHINE_H
+
+#include "scene/animation/animation_tree.h"
+
+class AnimationNodeStateMachineTransition : public Resource {
+ GDCLASS(AnimationNodeStateMachineTransition, Resource)
+public:
+ enum SwitchMode {
+ SWITCH_MODE_IMMEDIATE,
+ SWITCH_MODE_SYNC,
+ SWITCH_MODE_AT_END,
+ };
+
+private:
+ SwitchMode switch_mode;
+ bool auto_advance;
+ float xfade;
+ bool disabled;
+ int priority;
+
+protected:
+ static void _bind_methods();
+
+public:
+ void set_switch_mode(SwitchMode p_mode);
+ SwitchMode get_switch_mode() const;
+
+ void set_auto_advance(bool p_enable);
+ bool has_auto_advance() const;
+
+ void set_xfade_time(float p_xfade);
+ float get_xfade_time() const;
+
+ void set_disabled(bool p_disabled);
+ bool is_disabled() const;
+
+ void set_priority(int p_priority);
+ int get_priority() const;
+
+ AnimationNodeStateMachineTransition();
+};
+
+VARIANT_ENUM_CAST(AnimationNodeStateMachineTransition::SwitchMode)
+
+class AnimationNodeStateMachine : public AnimationRootNode {
+
+ GDCLASS(AnimationNodeStateMachine, AnimationRootNode);
+
+private:
+ Map<StringName, Ref<AnimationRootNode> > states;
+
+ struct Transition {
+
+ StringName from;
+ StringName to;
+ Ref<AnimationNodeStateMachineTransition> transition;
+ };
+
+ struct AStarCost {
+ float distance;
+ StringName prev;
+ };
+
+ Vector<Transition> transitions;
+
+ float len_total;
+
+ float len_current;
+ float pos_current;
+ int loops_current;
+
+ bool play_start;
+ StringName start_node;
+ StringName end_node;
+
+ Vector2 graph_offset;
+
+ StringName current;
+
+ StringName fading_from;
+ float fading_time;
+ float fading_pos;
+
+ Vector<StringName> path;
+ bool playing;
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+
+public:
+ 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 get_node_list(List<StringName> *r_nodes) const;
+
+ bool has_transition(const StringName &p_from, const StringName &p_to) const;
+ int find_transition(const StringName &p_from, const StringName &p_to) const;
+ void add_transition(const StringName &p_from, const StringName &p_to, const Ref<AnimationNodeStateMachineTransition> &p_transition);
+ Ref<AnimationNodeStateMachineTransition> get_transition(int p_transition) const;
+ StringName get_transition_from(int p_transition) const;
+ StringName get_transition_to(int p_transition) const;
+ int get_transition_count() const;
+ void remove_transition_by_index(int p_transition);
+ void remove_transition(const StringName &p_from, const StringName &p_to);
+
+ void set_start_node(const StringName &p_node);
+ String get_start_node() const;
+
+ void set_end_node(const StringName &p_node);
+ String get_end_node() const;
+
+ void set_graph_offset(const Vector2 &p_offset);
+ Vector2 get_graph_offset() const;
+
+ virtual float process(float p_time, bool p_seek);
+ virtual String get_caption() const;
+
+ bool travel(const StringName &p_state);
+ void start(const StringName &p_state);
+ void stop();
+ bool is_playing() const;
+ StringName get_current_node() const;
+ StringName get_blend_from_node() const;
+ Vector<StringName> get_travel_path() const;
+ float get_current_play_pos() const;
+ float get_current_length() const;
+
+ virtual void set_tree(AnimationTree *p_player);
+
+ AnimationNodeStateMachine();
+};
+
+#endif // ANIMATION_NODE_STATE_MACHINE_H
diff --git a/scene/animation/animation_graph_player.cpp b/scene/animation/animation_tree.cpp
index b8efecebe9..7cc67b4cc3 100644
--- a/scene/animation/animation_graph_player.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -1,4 +1,4 @@
-#include "animation_graph_player.h"
+#include "animation_tree.h"
#include "animation_blend_tree.h"
#include "core/method_bind_ext.gen.inc"
#include "engine.h"
@@ -56,11 +56,11 @@ void AnimationNode::make_invalid(const String &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
+ ERR_FAIL_COND_V(!get_tree(), 0); //should not happen, but used to catch bugs
Ref<AnimationNodeBlendTree> tree = get_parent();
- if (!tree.is_valid() && get_graph_player()->get_graph_root().ptr() != this) {
+ if (!tree.is_valid() && get_tree()->get_graph_root().ptr() != this) {
make_invalid(RTR("Can't blend input because node is not in a tree"));
return 0;
}
@@ -204,10 +204,10 @@ String AnimationNode::get_input_name(int p_input) {
float AnimationNode::get_input_activity(int p_input) const {
ERR_FAIL_INDEX_V(p_input, inputs.size(), 0);
- if (!get_graph_player())
+ if (!get_tree())
return 0;
- if (get_graph_player()->get_last_process_pass() != inputs[p_input].last_pass) {
+ if (get_tree()->get_last_process_pass() != inputs[p_input].last_pass) {
return 0;
}
return inputs[p_input].activity;
@@ -265,7 +265,7 @@ Ref<AnimationNode> AnimationNode::get_parent() const {
return Ref<AnimationNode>();
}
-AnimationGraphPlayer *AnimationNode::get_graph_player() const {
+AnimationTree *AnimationNode::get_tree() const {
return player;
}
@@ -316,7 +316,7 @@ Vector2 AnimationNode::get_position() const {
return position;
}
-void AnimationNode::set_graph_player(AnimationGraphPlayer *p_player) {
+void AnimationNode::set_tree(AnimationTree *p_player) {
if (player != NULL && p_player == NULL) {
emit_signal("removed_from_graph");
@@ -398,10 +398,10 @@ AnimationNode::AnimationNode() {
////////////////////
-void AnimationGraphPlayer::set_graph_root(const Ref<AnimationNode> &p_root) {
+void AnimationTree::set_graph_root(const Ref<AnimationNode> &p_root) {
if (root.is_valid()) {
- root->set_graph_player(NULL);
+ root->set_tree(NULL);
}
if (p_root.is_valid()) {
ERR_EXPLAIN("root node already set to another player");
@@ -410,17 +410,17 @@ void AnimationGraphPlayer::set_graph_root(const Ref<AnimationNode> &p_root) {
root = p_root;
if (root.is_valid()) {
- root->set_graph_player(this);
+ root->set_tree(this);
}
update_configuration_warning();
}
-Ref<AnimationNode> AnimationGraphPlayer::get_graph_root() const {
+Ref<AnimationNode> AnimationTree::get_graph_root() const {
return root;
}
-void AnimationGraphPlayer::set_active(bool p_active) {
+void AnimationTree::set_active(bool p_active) {
if (active == p_active)
return;
@@ -447,12 +447,12 @@ void AnimationGraphPlayer::set_active(bool p_active) {
}
}
-bool AnimationGraphPlayer::is_active() const {
+bool AnimationTree::is_active() const {
return active;
}
-void AnimationGraphPlayer::set_process_mode(AnimationProcessMode p_mode) {
+void AnimationTree::set_process_mode(AnimationProcessMode p_mode) {
if (process_mode == p_mode)
return;
@@ -469,20 +469,20 @@ void AnimationGraphPlayer::set_process_mode(AnimationProcessMode p_mode) {
}
}
-AnimationGraphPlayer::AnimationProcessMode AnimationGraphPlayer::get_process_mode() const {
+AnimationTree::AnimationProcessMode AnimationTree::get_process_mode() const {
return process_mode;
}
-void AnimationGraphPlayer::_node_removed(Node *p_node) {
+void AnimationTree::_node_removed(Node *p_node) {
cache_valid = false;
}
-bool AnimationGraphPlayer::_update_caches(AnimationPlayer *player) {
+bool AnimationTree::_update_caches(AnimationPlayer *player) {
setup_pass++;
if (!player->has_node(player->get_root())) {
- ERR_PRINT("AnimationGraphPlayer: AnimationPlayer root is invalid.");
+ ERR_PRINT("AnimationTree: AnimationPlayer root is invalid.");
set_active(false);
return false;
}
@@ -517,7 +517,7 @@ bool AnimationGraphPlayer::_update_caches(AnimationPlayer *player) {
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) + "'");
+ ERR_PRINTS("AnimationTree: '" + String(E->get()) + "', couldn't resolve track: '" + String(path) + "'");
continue;
}
@@ -547,7 +547,7 @@ bool AnimationGraphPlayer::_update_caches(AnimationPlayer *player) {
Spatial *spatial = Object::cast_to<Spatial>(child);
if (!spatial) {
- ERR_PRINTS("AnimationGraphPlayer: '" + String(E->get()) + "', transform track does not point to spatial: '" + String(path) + "'");
+ ERR_PRINTS("AnimationTree: '" + String(E->get()) + "', transform track does not point to spatial: '" + String(path) + "'");
continue;
}
@@ -666,7 +666,7 @@ bool AnimationGraphPlayer::_update_caches(AnimationPlayer *player) {
return true;
}
-void AnimationGraphPlayer::_clear_caches() {
+void AnimationTree::_clear_caches() {
const NodePath *K = NULL;
while ((K = track_cache.next(K))) {
@@ -678,19 +678,19 @@ void AnimationGraphPlayer::_clear_caches() {
cache_valid = false;
}
-void AnimationGraphPlayer::_process_graph(float p_delta) {
+void AnimationTree::_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.");
+ ERR_PRINT("AnimationTree: 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");
+ ERR_PRINT("AnimationTree: no valid AnimationPlayer path set, disabling playback");
set_active(false);
cache_valid = false;
return;
@@ -699,7 +699,7 @@ void AnimationGraphPlayer::_process_graph(float p_delta) {
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");
+ ERR_PRINT("AnimationTree: path points to a node not an AnimationPlayer, disabling playback");
set_active(false);
cache_valid = false;
return;
@@ -1093,7 +1093,7 @@ void AnimationGraphPlayer::_process_graph(float p_delta) {
}
}
-void AnimationGraphPlayer::_notification(int p_what) {
+void AnimationTree::_notification(int p_what) {
if (active && p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS && process_mode == ANIMATION_PROCESS_PHYSICS) {
_process_graph(get_physics_process_delta_time());
@@ -1108,29 +1108,29 @@ void AnimationGraphPlayer::_notification(int p_what) {
}
}
-void AnimationGraphPlayer::set_animation_player(const NodePath &p_player) {
+void AnimationTree::set_animation_player(const NodePath &p_player) {
animation_player = p_player;
update_configuration_warning();
}
-NodePath AnimationGraphPlayer::get_animation_player() const {
+NodePath AnimationTree::get_animation_player() const {
return animation_player;
}
-bool AnimationGraphPlayer::is_state_invalid() const {
+bool AnimationTree::is_state_invalid() const {
return !state.valid;
}
-String AnimationGraphPlayer::get_invalid_state_reason() const {
+String AnimationTree::get_invalid_state_reason() const {
return state.invalid_reasons;
}
-uint64_t AnimationGraphPlayer::get_last_process_pass() const {
+uint64_t AnimationTree::get_last_process_pass() const {
return process_pass;
}
-String AnimationGraphPlayer::get_configuration_warning() const {
+String AnimationTree::get_configuration_warning() const {
String warning = Node::get_configuration_warning();
@@ -1174,20 +1174,20 @@ String AnimationGraphPlayer::get_configuration_warning() const {
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);
+void AnimationTree::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_active", "active"), &AnimationTree::set_active);
+ ClassDB::bind_method(D_METHOD("is_active"), &AnimationTree::is_active);
- ClassDB::bind_method(D_METHOD("set_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_graph_root", "root"), &AnimationTree::set_graph_root);
+ ClassDB::bind_method(D_METHOD("get_graph_root"), &AnimationTree::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_process_mode", "mode"), &AnimationTree::set_process_mode);
+ ClassDB::bind_method(D_METHOD("get_process_mode"), &AnimationTree::get_process_mode);
- ClassDB::bind_method(D_METHOD("set_animation_player", "root"), &AnimationGraphPlayer::set_animation_player);
- ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationGraphPlayer::get_animation_player);
+ ClassDB::bind_method(D_METHOD("set_animation_player", "root"), &AnimationTree::set_animation_player);
+ ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationTree::get_animation_player);
- ClassDB::bind_method(D_METHOD("_node_removed"), &AnimationGraphPlayer::_node_removed);
+ ClassDB::bind_method(D_METHOD("_node_removed"), &AnimationTree::_node_removed);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "graph_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode", 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");
@@ -1195,7 +1195,7 @@ void AnimationGraphPlayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_process_mode", "get_process_mode");
}
-AnimationGraphPlayer::AnimationGraphPlayer() {
+AnimationTree::AnimationTree() {
process_mode = ANIMATION_PROCESS_IDLE;
active = false;
@@ -1204,7 +1204,7 @@ AnimationGraphPlayer::AnimationGraphPlayer() {
started = true;
}
-AnimationGraphPlayer::~AnimationGraphPlayer() {
+AnimationTree::~AnimationTree() {
if (root.is_valid()) {
root->player = NULL;
}
diff --git a/scene/animation/animation_graph_player.h b/scene/animation/animation_tree.h
index 8952be75c9..adc25d5607 100644
--- a/scene/animation/animation_graph_player.h
+++ b/scene/animation/animation_tree.h
@@ -8,7 +8,7 @@
class AnimationNodeBlendTree;
class AnimationPlayer;
-class AnimationGraphPlayer;
+class AnimationTree;
class AnimationNode : public Resource {
GDCLASS(AnimationNode, Resource)
@@ -32,7 +32,7 @@ public:
float process_input(int p_input, float p_time, bool p_seek, float p_blend);
- friend class AnimationGraphPlayer;
+ friend class AnimationTree;
struct AnimationState {
@@ -62,7 +62,7 @@ public:
Vector2 position;
AnimationNode *parent;
- AnimationGraphPlayer *player;
+ AnimationTree *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);
@@ -85,8 +85,8 @@ protected:
public:
void set_parent(AnimationNode *p_parent);
Ref<AnimationNode> get_parent() const;
- virtual void set_graph_player(AnimationGraphPlayer *p_player);
- AnimationGraphPlayer *get_graph_player() const;
+ virtual void set_tree(AnimationTree *p_player);
+ AnimationTree *get_tree() const;
AnimationPlayer *get_player() const;
virtual float process(float p_time, bool p_seek);
@@ -125,8 +125,8 @@ public:
AnimationRootNode() {}
};
-class AnimationGraphPlayer : public Node {
- GDCLASS(AnimationGraphPlayer, Node)
+class AnimationTree : public Node {
+ GDCLASS(AnimationTree, Node)
public:
enum AnimationProcessMode {
ANIMATION_PROCESS_PHYSICS,
@@ -258,10 +258,10 @@ public:
String get_invalid_state_reason() const;
uint64_t get_last_process_pass() const;
- AnimationGraphPlayer();
- ~AnimationGraphPlayer();
+ AnimationTree();
+ ~AnimationTree();
};
-VARIANT_ENUM_CAST(AnimationGraphPlayer::AnimationProcessMode)
+VARIANT_ENUM_CAST(AnimationTree::AnimationProcessMode)
#endif // ANIMATION_GRAPH_PLAYER_H
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 244d95d9c7..333e5c8e08 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -63,9 +63,11 @@
#include "scene/2d/tile_map.h"
#include "scene/2d/visibility_notifier_2d.h"
#include "scene/2d/y_sort.h"
-#include "scene/animation/animation_blend_space.h"
+#include "scene/animation/animation_blend_space_1d.h"
+#include "scene/animation/animation_blend_space_2d.h"
#include "scene/animation/animation_blend_tree.h"
-#include "scene/animation/animation_graph_player.h"
+#include "scene/animation/animation_tree.h"
+#include "scene/animation/animation_node_state_machine.h"
#include "scene/animation/animation_player.h"
#include "scene/animation/animation_tree_player.h"
#include "scene/animation/tween.h"
@@ -385,11 +387,14 @@ void register_scene_types() {
ClassDB::register_class<NavigationMesh>();
ClassDB::register_class<Navigation>();
- ClassDB::register_class<AnimationGraphPlayer>();
+ ClassDB::register_class<AnimationTree>();
ClassDB::register_class<AnimationNode>();
ClassDB::register_class<AnimationRootNode>();
ClassDB::register_class<AnimationNodeBlendTree>();
- ClassDB::register_class<AnimationNodeBlendSpace>();
+ ClassDB::register_class<AnimationNodeBlendSpace1D>();
+ ClassDB::register_class<AnimationNodeBlendSpace2D>();
+ ClassDB::register_class<AnimationNodeStateMachine>();
+ ClassDB::register_class<AnimationNodeStateMachineTransition>();
ClassDB::register_class<AnimationNodeOutput>();
ClassDB::register_class<AnimationNodeOneShot>();
ClassDB::register_class<AnimationNodeAnimation>();