summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/io/image_loader.cpp2
-rw-r--r--doc/classes/TileMap.xml2
-rw-r--r--drivers/gles2/shader_gles2.h4
-rw-r--r--editor/editor_node.cpp2
-rw-r--r--editor/plugins/skeleton_ik_editor_plugin.cpp110
-rw-r--r--editor/plugins/skeleton_ik_editor_plugin.h65
-rw-r--r--modules/bullet/soft_body_bullet.cpp2
-rw-r--r--modules/mono/glue/cs_files/GD.cs5
-rw-r--r--modules/mono/glue/cs_files/ResourceLoaderExtensions.cs10
-rw-r--r--modules/mono/mono_gd/gd_mono_class.cpp4
-rw-r--r--modules/mono/mono_gd/gd_mono_method.cpp12
-rw-r--r--modules/mono/mono_gd/gd_mono_property.cpp22
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.cpp46
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.h8
-rw-r--r--modules/mono/utils/thread_local.cpp4
-rw-r--r--modules/mono/utils/thread_local.h2
-rw-r--r--platform_methods.py26
-rw-r--r--scene/3d/physics_body.h1
-rw-r--r--scene/3d/skeleton.cpp11
-rw-r--r--scene/3d/skeleton.h4
-rw-r--r--scene/3d/soft_body.cpp85
-rw-r--r--scene/3d/soft_body.h6
-rw-r--r--scene/animation/skeleton_ik.cpp551
-rw-r--r--scene/animation/skeleton_ik.h212
-rw-r--r--scene/main/node.cpp5
-rw-r--r--scene/main/node.h3
-rw-r--r--scene/register_scene_types.cpp2
27 files changed, 1123 insertions, 83 deletions
diff --git a/core/io/image_loader.cpp b/core/io/image_loader.cpp
index 614fbb771f..b8fd13d67c 100644
--- a/core/io/image_loader.cpp
+++ b/core/io/image_loader.cpp
@@ -185,5 +185,5 @@ bool ResourceFormatLoaderImage::handles_type(const String &p_type) const {
String ResourceFormatLoaderImage::get_resource_type(const String &p_path) const {
- return "Image";
+ return p_path.get_extension().to_lower() == "image" ? "Image" : String();
}
diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml
index 2990906f7c..73d60e49b7 100644
--- a/doc/classes/TileMap.xml
+++ b/doc/classes/TileMap.xml
@@ -68,7 +68,7 @@
<return type="Array">
</return>
<description>
- Returns an array of all cells containing a tile from the tileset (i.e. a tile index different from [code]-1[/code]).
+ Returns a [Vector2] array with the positions of all cells containing a tile from the tileset (i.e. a tile index different from [code]-1[/code]).
</description>
</method>
<method name="get_used_cells_by_id" qualifiers="const">
diff --git a/drivers/gles2/shader_gles2.h b/drivers/gles2/shader_gles2.h
index 0c53c4ba72..49e45eea9f 100644
--- a/drivers/gles2/shader_gles2.h
+++ b/drivers/gles2/shader_gles2.h
@@ -300,7 +300,7 @@ public:
case ShaderLanguage::TYPE_MAT3: {
GLfloat mat[9];
- for (int i = 0; i < 0; i++) {
+ for (int i = 0; i < 9; i++) {
mat[i] = values[i].real;
}
@@ -311,7 +311,7 @@ public:
case ShaderLanguage::TYPE_MAT4: {
GLfloat mat[16];
- for (int i = 0; i < 0; i++) {
+ for (int i = 0; i < 16; i++) {
mat[i] = values[i].real;
}
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 212ee33ffa..15e40bfba1 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -109,6 +109,7 @@
#include "editor/plugins/shader_graph_editor_plugin.h"
#include "editor/plugins/skeleton_2d_editor_plugin.h"
#include "editor/plugins/skeleton_editor_plugin.h"
+#include "editor/plugins/skeleton_ik_editor_plugin.h"
#include "editor/plugins/spatial_editor_plugin.h"
#include "editor/plugins/sprite_editor_plugin.h"
#include "editor/plugins/sprite_frames_editor_plugin.h"
@@ -5596,6 +5597,7 @@ EditorNode::EditorNode() {
add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor)));
add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor)));
add_editor_plugin(memnew(SkeletonEditorPlugin(this)));
+ add_editor_plugin(memnew(SkeletonIKEditorPlugin(this)));
add_editor_plugin(memnew(PhysicalBonePlugin(this)));
// FIXME: Disabled as (according to reduz) users were complaining that it gets in the way
diff --git a/editor/plugins/skeleton_ik_editor_plugin.cpp b/editor/plugins/skeleton_ik_editor_plugin.cpp
new file mode 100644
index 0000000000..2d343d3edd
--- /dev/null
+++ b/editor/plugins/skeleton_ik_editor_plugin.cpp
@@ -0,0 +1,110 @@
+/*************************************************************************/
+/* skeleton_ik_editor_plugin.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "skeleton_ik_editor_plugin.h"
+
+#include "scene/animation/skeleton_ik.h"
+
+void SkeletonIKEditorPlugin::_play() {
+
+ if (!skeleton_ik)
+ return;
+
+ if (!skeleton_ik->get_parent_skeleton())
+ return;
+
+ if (play_btn->is_pressed()) {
+
+ initial_bone_poses.resize(skeleton_ik->get_parent_skeleton()->get_bone_count());
+ for (int i = 0; i < skeleton_ik->get_parent_skeleton()->get_bone_count(); ++i) {
+ initial_bone_poses.write[i] = skeleton_ik->get_parent_skeleton()->get_bone_pose(i);
+ }
+
+ skeleton_ik->start();
+ } else {
+ skeleton_ik->stop();
+
+ if (initial_bone_poses.size() != skeleton_ik->get_parent_skeleton()->get_bone_count())
+ return;
+
+ for (int i = 0; i < skeleton_ik->get_parent_skeleton()->get_bone_count(); ++i) {
+ skeleton_ik->get_parent_skeleton()->set_bone_pose(i, initial_bone_poses[i]);
+ }
+ }
+}
+
+void SkeletonIKEditorPlugin::edit(Object *p_object) {
+
+ if (p_object != skeleton_ik) {
+ if (skeleton_ik) {
+ play_btn->set_pressed(false);
+ _play();
+ }
+ }
+
+ SkeletonIK *s = Object::cast_to<SkeletonIK>(p_object);
+ if (!s)
+ return;
+
+ skeleton_ik = s;
+}
+
+bool SkeletonIKEditorPlugin::handles(Object *p_object) const {
+
+ return p_object->is_class("SkeletonIK");
+}
+
+void SkeletonIKEditorPlugin::make_visible(bool p_visible) {
+
+ if (p_visible)
+ play_btn->show();
+ else
+ play_btn->hide();
+}
+
+void SkeletonIKEditorPlugin::_bind_methods() {
+
+ ClassDB::bind_method("_play", &SkeletonIKEditorPlugin::_play);
+}
+
+SkeletonIKEditorPlugin::SkeletonIKEditorPlugin(EditorNode *p_node) {
+
+ editor = p_node;
+ play_btn = memnew(Button);
+ play_btn->set_icon(editor->get_gui_base()->get_icon("Play", "EditorIcons"));
+ play_btn->set_text(TTR("Play IK"));
+ play_btn->set_toggle_mode(true);
+ play_btn->hide();
+ play_btn->connect("pressed", this, "_play");
+ add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, play_btn);
+ skeleton_ik = NULL;
+}
+
+SkeletonIKEditorPlugin::~SkeletonIKEditorPlugin() {}
diff --git a/editor/plugins/skeleton_ik_editor_plugin.h b/editor/plugins/skeleton_ik_editor_plugin.h
new file mode 100644
index 0000000000..e645bea39a
--- /dev/null
+++ b/editor/plugins/skeleton_ik_editor_plugin.h
@@ -0,0 +1,65 @@
+/*************************************************************************/
+/* skeleton_ik_editor_plugin.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef SKELETON_IK_EDITOR_PLUGIN_H
+#define SKELETON_IK_EDITOR_PLUGIN_H
+
+#include "editor/editor_node.h"
+#include "editor/editor_plugin.h"
+
+class SkeletonIK;
+
+class SkeletonIKEditorPlugin : public EditorPlugin {
+
+ GDCLASS(SkeletonIKEditorPlugin, EditorPlugin);
+
+ SkeletonIK *skeleton_ik;
+
+ Button *play_btn;
+ EditorNode *editor;
+ Vector<Transform> initial_bone_poses;
+
+ void _play();
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual String get_name() const { return "SkeletonIK"; }
+ 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);
+
+ SkeletonIKEditorPlugin(EditorNode *p_node);
+ ~SkeletonIKEditorPlugin();
+};
+
+#endif // SKELETON_IK_EDITOR_PLUGIN_H
diff --git a/modules/bullet/soft_body_bullet.cpp b/modules/bullet/soft_body_bullet.cpp
index 1686a6e87e..9fc7230f91 100644
--- a/modules/bullet/soft_body_bullet.cpp
+++ b/modules/bullet/soft_body_bullet.cpp
@@ -123,7 +123,7 @@ void SoftBodyBullet::update_visual_server(SoftBodyVisualServerHandler *p_visual_
void SoftBodyBullet::set_soft_mesh(const Ref<Mesh> &p_mesh) {
- if (p_mesh.is_null() || !p_mesh->surface_is_softbody_friendly(0))
+ if (p_mesh.is_null())
soft_mesh.unref();
else
soft_mesh = p_mesh;
diff --git a/modules/mono/glue/cs_files/GD.cs b/modules/mono/glue/cs_files/GD.cs
index e2457ff98b..43de9156f2 100644
--- a/modules/mono/glue/cs_files/GD.cs
+++ b/modules/mono/glue/cs_files/GD.cs
@@ -64,6 +64,11 @@ namespace Godot
return ResourceLoader.Load(path);
}
+ public static T Load<T>(string path) where T : Godot.Resource
+ {
+ return (T) ResourceLoader.Load(path);
+ }
+
public static void Print(params object[] what)
{
NativeCalls.godot_icall_Godot_print(what);
diff --git a/modules/mono/glue/cs_files/ResourceLoaderExtensions.cs b/modules/mono/glue/cs_files/ResourceLoaderExtensions.cs
new file mode 100644
index 0000000000..ceecc589e6
--- /dev/null
+++ b/modules/mono/glue/cs_files/ResourceLoaderExtensions.cs
@@ -0,0 +1,10 @@
+namespace Godot
+{
+ public static partial class ResourceLoader
+ {
+ public static T Load<T>(string path) where T : Godot.Resource
+ {
+ return (T) Load(path);
+ }
+ }
+}
diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp
index ad74f73d74..4e515cde28 100644
--- a/modules/mono/mono_gd/gd_mono_class.cpp
+++ b/modules/mono/mono_gd/gd_mono_class.cpp
@@ -40,9 +40,7 @@ String GDMonoClass::get_full_name(MonoClass *p_mono_class) {
MonoReflectionType *type_obj = mono_type_get_object(mono_domain_get(), get_mono_type(p_mono_class));
MonoException *exc = NULL;
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- MonoString *str = mono_object_to_string((MonoObject *)type_obj, (MonoObject **)&exc);
- GD_MONO_END_RUNTIME_INVOKE;
+ MonoString *str = GDMonoUtils::object_to_string((MonoObject *)type_obj, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return GDMonoMarshal::mono_string_to_godot(str);
diff --git a/modules/mono/mono_gd/gd_mono_method.cpp b/modules/mono/mono_gd/gd_mono_method.cpp
index c8df1038ce..630bda8b4e 100644
--- a/modules/mono/mono_gd/gd_mono_method.cpp
+++ b/modules/mono/mono_gd/gd_mono_method.cpp
@@ -105,9 +105,7 @@ MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params,
}
MonoException *exc = NULL;
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- MonoObject *ret = mono_runtime_invoke_array(mono_method, p_object, params, (MonoObject **)&exc);
- GD_MONO_END_RUNTIME_INVOKE;
+ MonoObject *ret = GDMonoUtils::runtime_invoke_array(mono_method, p_object, params, &exc);
if (exc) {
ret = NULL;
@@ -121,9 +119,7 @@ MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params,
return ret;
} else {
MonoException *exc = NULL;
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- mono_runtime_invoke(mono_method, p_object, NULL, (MonoObject **)&exc);
- GD_MONO_END_RUNTIME_INVOKE;
+ GDMonoUtils::runtime_invoke(mono_method, p_object, NULL, &exc);
if (exc) {
if (r_exc) {
@@ -144,9 +140,7 @@ MonoObject *GDMonoMethod::invoke(MonoObject *p_object, MonoException **r_exc) {
MonoObject *GDMonoMethod::invoke_raw(MonoObject *p_object, void **p_params, MonoException **r_exc) {
MonoException *exc = NULL;
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- MonoObject *ret = mono_runtime_invoke(mono_method, p_object, p_params, (MonoObject **)&exc);
- GD_MONO_END_RUNTIME_INVOKE;
+ MonoObject *ret = GDMonoUtils::runtime_invoke(mono_method, p_object, p_params, &exc);
if (exc) {
ret = NULL;
diff --git a/modules/mono/mono_gd/gd_mono_property.cpp b/modules/mono/mono_gd/gd_mono_property.cpp
index a1c710c26c..ce66e0c8db 100644
--- a/modules/mono/mono_gd/gd_mono_property.cpp
+++ b/modules/mono/mono_gd/gd_mono_property.cpp
@@ -139,15 +139,23 @@ bool GDMonoProperty::has_setter() {
}
void GDMonoProperty::set_value(MonoObject *p_object, MonoObject *p_value, MonoException **r_exc) {
- void *params[1] = { p_value };
- set_value(p_object, params, r_exc);
+ MonoMethod *prop_method = mono_property_get_set_method(mono_property);
+ MonoArray *params = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), 1);
+ mono_array_set(params, MonoObject *, 0, p_value);
+ MonoException *exc = NULL;
+ GDMonoUtils::runtime_invoke_array(prop_method, p_object, params, &exc);
+ if (exc) {
+ if (r_exc) {
+ *r_exc = exc;
+ } else {
+ GDMonoUtils::set_pending_exception(exc);
+ }
+ }
}
void GDMonoProperty::set_value(MonoObject *p_object, void **p_params, MonoException **r_exc) {
MonoException *exc = NULL;
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- mono_property_set_value(mono_property, p_object, p_params, (MonoObject **)&exc);
- GD_MONO_END_RUNTIME_INVOKE;
+ GDMonoUtils::property_set_value(mono_property, p_object, p_params, &exc);
if (exc) {
if (r_exc) {
@@ -160,9 +168,7 @@ void GDMonoProperty::set_value(MonoObject *p_object, void **p_params, MonoExcept
MonoObject *GDMonoProperty::get_value(MonoObject *p_object, MonoException **r_exc) {
MonoException *exc = NULL;
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- MonoObject *ret = mono_property_get_value(mono_property, p_object, NULL, (MonoObject **)&exc);
- GD_MONO_END_RUNTIME_INVOKE;
+ MonoObject *ret = GDMonoUtils::property_get_value(mono_property, p_object, NULL, &exc);
if (exc) {
ret = NULL;
diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp
index 21efd4064a..911d629956 100644
--- a/modules/mono/mono_gd/gd_mono_utils.cpp
+++ b/modules/mono/mono_gd/gd_mono_utils.cpp
@@ -414,7 +414,7 @@ MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) {
void *args[1] = { &new_array };
MonoException *exc = NULL;
- mono_runtime_invoke(m, mono_object, args, (MonoObject **)&exc);
+ GDMonoUtils::runtime_invoke(m, mono_object, args, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return mono_object;
@@ -444,7 +444,7 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class)
void *args[1] = { &new_dict };
MonoException *exc = NULL;
- mono_runtime_invoke(m, mono_object, args, (MonoObject **)&exc);
+ GDMonoUtils::runtime_invoke(m, mono_object, args, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return mono_object;
@@ -476,9 +476,7 @@ String get_exception_name_and_message(MonoException *p_exc) {
res += ": ";
MonoProperty *prop = mono_class_get_property_from_name(klass, "Message");
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- MonoString *msg = (MonoString *)mono_property_get_value(prop, (MonoObject *)p_exc, NULL, NULL);
- GD_MONO_END_RUNTIME_INVOKE;
+ MonoString *msg = (MonoString *)property_get_value(prop, (MonoObject *)p_exc, NULL, NULL);
res += GDMonoMarshal::mono_string_to_godot(msg);
return res;
@@ -489,9 +487,7 @@ void set_exception_message(MonoException *p_exc, String message) {
MonoProperty *prop = mono_class_get_property_from_name(klass, "Message");
MonoString *msg = GDMonoMarshal::mono_string_from_godot(message);
void *params[1] = { msg };
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- mono_property_set_value(prop, (MonoObject *)p_exc, params, NULL);
- GD_MONO_END_RUNTIME_INVOKE;
+ property_set_value(prop, (MonoObject *)p_exc, params, NULL);
}
void debug_print_unhandled_exception(MonoException *p_exc) {
@@ -592,4 +588,38 @@ void set_pending_exception(MonoException *p_exc) {
_THREAD_LOCAL_(int)
current_invoke_count = 0;
+MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **p_exc) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ MonoObject *ret = mono_runtime_invoke(p_method, p_obj, p_params, (MonoObject **)&p_exc);
+ GD_MONO_END_RUNTIME_INVOKE;
+ return ret;
+}
+
+MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **p_exc) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ MonoObject *ret = mono_runtime_invoke_array(p_method, p_obj, p_params, (MonoObject **)&p_exc);
+ GD_MONO_END_RUNTIME_INVOKE;
+ return ret;
+}
+
+MonoString *object_to_string(MonoObject *p_obj, MonoException **p_exc) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ MonoString *ret = mono_object_to_string(p_obj, (MonoObject **)p_exc);
+ GD_MONO_END_RUNTIME_INVOKE;
+ return ret;
+}
+
+void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **p_exc) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ mono_property_set_value(p_prop, p_obj, p_params, (MonoObject **)p_exc);
+ GD_MONO_END_RUNTIME_INVOKE;
+}
+
+MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **p_exc) {
+ GD_MONO_BEGIN_RUNTIME_INVOKE;
+ MonoObject *ret = mono_property_get_value(p_prop, p_obj, p_params, (MonoObject **)p_exc);
+ GD_MONO_END_RUNTIME_INVOKE;
+ return ret;
+}
+
} // namespace GDMonoUtils
diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h
index d6774ed41d..bf8860c85a 100644
--- a/modules/mono/mono_gd/gd_mono_utils.h
+++ b/modules/mono/mono_gd/gd_mono_utils.h
@@ -230,6 +230,14 @@ _FORCE_INLINE_ int &get_runtime_invoke_count_ref() {
return current_invoke_count;
}
+MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **p_exc);
+MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **p_exc);
+
+MonoString *object_to_string(MonoObject *p_obj, MonoException **p_exc);
+
+void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **p_exc);
+MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **p_exc);
+
} // namespace GDMonoUtils
#define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoMarshal::mono_string_to_godot((MonoString *)m_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(NULL)))
diff --git a/modules/mono/utils/thread_local.cpp b/modules/mono/utils/thread_local.cpp
index 248f24c855..ae9f130518 100644
--- a/modules/mono/utils/thread_local.cpp
+++ b/modules/mono/utils/thread_local.cpp
@@ -69,7 +69,7 @@ struct ThreadLocalStorage::Impl {
#define _CALLBACK_FUNC_
#endif
- Impl(void _CALLBACK_FUNC_ (*p_destr_callback_func)(void *)) {
+ Impl(void (_CALLBACK_FUNC_ *p_destr_callback_func)(void *)) {
#ifdef WINDOWS_ENABLED
dwFlsIndex = FlsAlloc(p_destr_callback_func);
ERR_FAIL_COND(dwFlsIndex == FLS_OUT_OF_INDEXES);
@@ -95,7 +95,7 @@ void ThreadLocalStorage::set_value(void *p_value) const {
pimpl->set_value(p_value);
}
-void ThreadLocalStorage::alloc(void _CALLBACK_FUNC_ (*p_destr_callback)(void *)) {
+void ThreadLocalStorage::alloc(void (_CALLBACK_FUNC_ *p_destr_callback)(void *)) {
pimpl = memnew(ThreadLocalStorage::Impl(p_destr_callback));
}
diff --git a/modules/mono/utils/thread_local.h b/modules/mono/utils/thread_local.h
index d0c5df3c91..783e40dc01 100644
--- a/modules/mono/utils/thread_local.h
+++ b/modules/mono/utils/thread_local.h
@@ -76,7 +76,7 @@ struct ThreadLocalStorage {
void *get_value() const;
void set_value(void *p_value) const;
- void alloc(void _CALLBACK_FUNC_ (*p_dest_callback)(void *));
+ void alloc(void (_CALLBACK_FUNC_ *p_dest_callback)(void *));
void free();
private:
diff --git a/platform_methods.py b/platform_methods.py
index 0e0d93d0a6..4300216427 100644
--- a/platform_methods.py
+++ b/platform_methods.py
@@ -7,6 +7,11 @@ import subprocess
# NOTE: The multiprocessing module is not compatible with SCons due to conflict on cPickle
+if sys.version_info[0] < 3:
+ JSON_SERIALIZABLE_TYPES = (bool, int, long, float, basestring)
+else:
+ JSON_SERIALIZABLE_TYPES = (bool, int, float, str)
+
def run_in_subprocess(builder_function):
@@ -17,9 +22,9 @@ def run_in_subprocess(builder_function):
target = [node.srcnode().abspath for node in target]
source = [node.srcnode().abspath for node in source]
- # Short circuit on non-Windows platforms
- if os.name != 'nt':
- return builder_function(target, source, None)
+ # Short circuit on non-Windows platforms, no need to run in subprocess
+ if sys.platform not in ('win32', 'cygwin'):
+ return builder_function(target, source, env)
# Identify module
module_name = builder_function.__module__
@@ -32,14 +37,25 @@ def run_in_subprocess(builder_function):
subprocess_env = os.environ.copy()
subprocess_env['PYTHONPATH'] = os.pathsep.join([os.getcwd()] + sys.path)
+ # Keep only JSON serializable environment items
+ filtered_env = dict(
+ (key, value)
+ for key, value in env.items()
+ if isinstance(value, JSON_SERIALIZABLE_TYPES)
+ )
+
# Save parameters
- args = (target, source, None)
+ args = (target, source, filtered_env)
data = dict(fn=function_name, args=args)
json_path = os.path.join(os.environ['TMP'], uuid.uuid4().hex + '.json')
with open(json_path, 'wt') as json_file:
json.dump(data, json_file, indent=2)
+ json_file_size = os.stat(json_path).st_size
+
+ print('Executing builder function in subprocess: '
+ 'module_path=%r, parameter_file=%r, parameter_file_size=%r, target=%r, source=%r' % (
+ module_path, json_path, json_file_size, target, source))
try:
- print('Executing builder function in subprocess: module_path=%r; data=%r' % (module_path, data))
exit_code = subprocess.call([sys.executable, module_path, json_path], env=subprocess_env)
finally:
try:
diff --git a/scene/3d/physics_body.h b/scene/3d/physics_body.h
index 4143989671..80bf422c98 100644
--- a/scene/3d/physics_body.h
+++ b/scene/3d/physics_body.h
@@ -557,6 +557,7 @@ protected:
private:
static Skeleton *find_skeleton_parent(Node *p_parent);
+
void _fix_joint_offset();
void _reload_joint();
diff --git a/scene/3d/skeleton.cpp b/scene/3d/skeleton.cpp
index 4b6b59b2d3..f45cb02211 100644
--- a/scene/3d/skeleton.cpp
+++ b/scene/3d/skeleton.cpp
@@ -600,9 +600,12 @@ PhysicalBone *Skeleton::_get_physical_bone_parent(int p_bone) {
void Skeleton::_rebuild_physical_bones_cache() {
const int b_size = bones.size();
for (int i = 0; i < b_size; ++i) {
- bones.write[i].cache_parent_physical_bone = _get_physical_bone_parent(i);
- if (bones[i].physical_bone)
- bones[i].physical_bone->_on_bone_parent_changed();
+ PhysicalBone *parent_pb = _get_physical_bone_parent(i);
+ if (parent_pb != bones[i].physical_bone) {
+ bones.write[i].cache_parent_physical_bone = parent_pb;
+ if (bones[i].physical_bone)
+ bones[i].physical_bone->_on_bone_parent_changed();
+ }
}
}
@@ -740,6 +743,8 @@ void Skeleton::_bind_methods() {
#endif // _3D_DISABLED
+ ClassDB::bind_method(D_METHOD("set_bone_ignore_animation", "bone", "ignore"), &Skeleton::set_bone_ignore_animation);
+
BIND_CONSTANT(NOTIFICATION_UPDATE_SKELETON);
}
diff --git a/scene/3d/skeleton.h b/scene/3d/skeleton.h
index 9672acb57a..a8413ebaf2 100644
--- a/scene/3d/skeleton.h
+++ b/scene/3d/skeleton.h
@@ -39,6 +39,8 @@
*/
#ifndef _3D_DISABLED
+typedef int BoneId;
+
class PhysicalBone;
#endif // _3D_DISABLED
@@ -96,7 +98,7 @@ class Skeleton : public Spatial {
void _make_dirty();
bool dirty;
- //bind helpers
+ // bind helpers
Array _get_bound_child_nodes_to_bone(int p_bone) const {
Array bound;
diff --git a/scene/3d/soft_body.cpp b/scene/3d/soft_body.cpp
index 4ab7013707..980c348c9b 100644
--- a/scene/3d/soft_body.cpp
+++ b/scene/3d/soft_body.cpp
@@ -133,8 +133,8 @@ bool SoftBody::_get(const StringName &p_name, Variant &r_ret) const {
if ("pinned_points" == which) {
Array arr_ret;
- const int pinned_points_indices_size = pinned_points_indices.size();
- PoolVector<PinnedPoint>::Read r = pinned_points_indices.read();
+ const int pinned_points_indices_size = pinned_points.size();
+ PoolVector<PinnedPoint>::Read r = pinned_points.read();
arr_ret.resize(pinned_points_indices_size);
for (int i = 0; i < pinned_points_indices_size; ++i) {
@@ -157,7 +157,7 @@ bool SoftBody::_get(const StringName &p_name, Variant &r_ret) const {
void SoftBody::_get_property_list(List<PropertyInfo> *p_list) const {
- const int pinned_points_indices_size = pinned_points_indices.size();
+ const int pinned_points_indices_size = pinned_points.size();
p_list->push_back(PropertyInfo(Variant::POOL_INT_ARRAY, "pinned_points"));
@@ -173,17 +173,17 @@ bool SoftBody::_set_property_pinned_points_indices(const Array &p_indices) {
const int p_indices_size = p_indices.size();
{ // Remove the pined points on physics server that will be removed by resize
- PoolVector<PinnedPoint>::Read r = pinned_points_indices.read();
- if (p_indices_size < pinned_points_indices.size()) {
- for (int i = pinned_points_indices.size() - 1; i >= p_indices_size; --i) {
+ PoolVector<PinnedPoint>::Read r = pinned_points.read();
+ if (p_indices_size < pinned_points.size()) {
+ for (int i = pinned_points.size() - 1; i >= p_indices_size; --i) {
pin_point(r[i].point_index, false);
}
}
}
- pinned_points_indices.resize(p_indices_size);
+ pinned_points.resize(p_indices_size);
- PoolVector<PinnedPoint>::Write w = pinned_points_indices.write();
+ PoolVector<PinnedPoint>::Write w = pinned_points.write();
int point_index;
for (int i = 0; i < p_indices_size; ++i) {
point_index = p_indices.get(i);
@@ -198,16 +198,16 @@ bool SoftBody::_set_property_pinned_points_indices(const Array &p_indices) {
}
bool SoftBody::_set_property_pinned_points_attachment(int p_item, const String &p_what, const Variant &p_value) {
- if (pinned_points_indices.size() <= p_item) {
+ if (pinned_points.size() <= p_item) {
return false;
}
if ("spatial_attachment_path" == p_what) {
- PoolVector<PinnedPoint>::Write w = pinned_points_indices.write();
+ PoolVector<PinnedPoint>::Write w = pinned_points.write();
pin_point(w[p_item].point_index, true, p_value);
_make_cache_dirty();
} else if ("offset" == p_what) {
- PoolVector<PinnedPoint>::Write w = pinned_points_indices.write();
+ PoolVector<PinnedPoint>::Write w = pinned_points.write();
w[p_item].offset = p_value;
} else {
return false;
@@ -217,10 +217,10 @@ bool SoftBody::_set_property_pinned_points_attachment(int p_item, const String &
}
bool SoftBody::_get_property_pinned_points(int p_item, const String &p_what, Variant &r_ret) const {
- if (pinned_points_indices.size() <= p_item) {
+ if (pinned_points.size() <= p_item) {
return false;
}
- PoolVector<PinnedPoint>::Read r = pinned_points_indices.read();
+ PoolVector<PinnedPoint>::Read r = pinned_points.read();
if ("point_index" == p_what) {
r_ret = r[p_item].point_index;
@@ -236,6 +236,8 @@ bool SoftBody::_get_property_pinned_points(int p_item, const String &p_what, Var
}
void SoftBody::_changed_callback(Object *p_changed, const char *p_prop) {
+ update_physics_server();
+ _reset_points_offsets();
#ifdef TOOLS_ENABLED
if (p_changed == this) {
update_configuration_warning();
@@ -247,8 +249,10 @@ void SoftBody::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_WORLD: {
- if (Engine::get_singleton()->is_editor_hint())
+ if (Engine::get_singleton()->is_editor_hint()) {
+
add_change_receptor(this);
+ }
RID space = get_world()->get_space();
PhysicsServer::get_singleton()->soft_body_set_space(physics_rid, space);
@@ -261,8 +265,10 @@ void SoftBody::_notification(int p_what) {
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
- if (Engine::get_singleton()->is_editor_hint())
+ if (Engine::get_singleton()->is_editor_hint()) {
+ _reset_points_offsets();
return;
+ }
PhysicsServer::get_singleton()->soft_body_set_transform(physics_rid, get_global_transform());
@@ -280,8 +286,8 @@ void SoftBody::_notification(int p_what) {
_update_cache_pin_points_datas();
// Submit bone attachment
- const int pinned_points_indices_size = pinned_points_indices.size();
- PoolVector<PinnedPoint>::Read r = pinned_points_indices.read();
+ const int pinned_points_indices_size = pinned_points.size();
+ PoolVector<PinnedPoint>::Read r = pinned_points.read();
for (int i = 0; i < pinned_points_indices_size; ++i) {
if (r[i].spatial_attachment) {
PhysicsServer::get_singleton()->soft_body_move_point(physics_rid, r[i].point_index, r[i].spatial_attachment->get_global_transform().xform(r[i].offset));
@@ -531,15 +537,15 @@ const NodePath &SoftBody::get_parent_collision_ignore() const {
}
void SoftBody::set_pinned_points_indices(PoolVector<SoftBody::PinnedPoint> p_pinned_points_indices) {
- pinned_points_indices = p_pinned_points_indices;
- PoolVector<PinnedPoint>::Read w = pinned_points_indices.read();
- for (int i = pinned_points_indices.size() - 1; 0 <= i; --i) {
+ pinned_points = p_pinned_points_indices;
+ PoolVector<PinnedPoint>::Read w = pinned_points.read();
+ for (int i = pinned_points.size() - 1; 0 <= i; --i) {
pin_point(p_pinned_points_indices[i].point_index, true);
}
}
PoolVector<SoftBody::PinnedPoint> SoftBody::get_pinned_points_indices() {
- return pinned_points_indices;
+ return pinned_points;
}
void SoftBody::add_collision_exception_with(Node *p_node) {
@@ -685,8 +691,8 @@ SoftBody::~SoftBody() {
void SoftBody::reset_softbody_pin() {
PhysicsServer::get_singleton()->soft_body_remove_all_pinned_points(physics_rid);
- PoolVector<PinnedPoint>::Read pps = pinned_points_indices.read();
- for (int i = pinned_points_indices.size() - 1; 0 < i; --i) {
+ PoolVector<PinnedPoint>::Read pps = pinned_points.read();
+ for (int i = pinned_points.size() - 1; 0 < i; --i) {
PhysicsServer::get_singleton()->soft_body_pin_point(physics_rid, pps[i].point_index, true);
}
}
@@ -701,8 +707,8 @@ void SoftBody::_update_cache_pin_points_datas() {
pinned_points_cache_dirty = false;
- PoolVector<PinnedPoint>::Write w = pinned_points_indices.write();
- for (int i = pinned_points_indices.size() - 1; 0 <= i; --i) {
+ PoolVector<PinnedPoint>::Write w = pinned_points.write();
+ for (int i = pinned_points.size() - 1; 0 <= i; --i) {
if (!w[i].spatial_attachment_path.is_empty()) {
w[i].spatial_attachment = Object::cast_to<Spatial>(get_node(w[i].spatial_attachment_path));
@@ -731,7 +737,7 @@ void SoftBody::_add_pinned_point(int p_point_index, const NodePath &p_spatial_at
pp.offset = (pp.spatial_attachment->get_global_transform().affine_inverse() * get_global_transform()).xform(PhysicsServer::get_singleton()->soft_body_get_point_global_position(physics_rid, pp.point_index));
}
- pinned_points_indices.push_back(pp);
+ pinned_points.push_back(pp);
} else {
@@ -745,10 +751,29 @@ void SoftBody::_add_pinned_point(int p_point_index, const NodePath &p_spatial_at
}
}
+void SoftBody::_reset_points_offsets() {
+
+ if (!Engine::get_singleton()->is_editor_hint())
+ return;
+
+ PoolVector<PinnedPoint>::Read r = pinned_points.read();
+ PoolVector<PinnedPoint>::Write w = pinned_points.write();
+ for (int i = pinned_points.size() - 1; 0 <= i; --i) {
+
+ if (!r[i].spatial_attachment)
+ w[i].spatial_attachment = Object::cast_to<Spatial>(get_node(r[i].spatial_attachment_path));
+
+ if (!r[i].spatial_attachment)
+ continue;
+
+ w[i].offset = (r[i].spatial_attachment->get_global_transform().affine_inverse() * get_global_transform()).xform(PhysicsServer::get_singleton()->soft_body_get_point_global_position(physics_rid, r[i].point_index));
+ }
+}
+
void SoftBody::_remove_pinned_point(int p_point_index) {
const int id(_has_pinned_point(p_point_index));
if (-1 != id) {
- pinned_points_indices.remove(id);
+ pinned_points.remove(id);
}
}
@@ -758,14 +783,14 @@ int SoftBody::_get_pinned_point(int p_point_index, SoftBody::PinnedPoint *&r_poi
r_point = NULL;
return -1;
} else {
- r_point = const_cast<SoftBody::PinnedPoint *>(&pinned_points_indices.read()[id]);
+ r_point = const_cast<SoftBody::PinnedPoint *>(&pinned_points.read()[id]);
return id;
}
}
int SoftBody::_has_pinned_point(int p_point_index) const {
- PoolVector<PinnedPoint>::Read r = pinned_points_indices.read();
- for (int i = pinned_points_indices.size() - 1; 0 <= i; --i) {
+ PoolVector<PinnedPoint>::Read r = pinned_points.read();
+ for (int i = pinned_points.size() - 1; 0 <= i; --i) {
if (p_point_index == r[i].point_index) {
return i;
}
diff --git a/scene/3d/soft_body.h b/scene/3d/soft_body.h
index 6cf19ef8c4..cee32b9651 100644
--- a/scene/3d/soft_body.h
+++ b/scene/3d/soft_body.h
@@ -87,7 +87,7 @@ private:
uint32_t collision_mask;
uint32_t collision_layer;
NodePath parent_collision_ignore;
- PoolVector<PinnedPoint> pinned_points_indices;
+ PoolVector<PinnedPoint> pinned_points;
bool simulation_started;
bool pinned_points_cache_dirty;
@@ -184,10 +184,14 @@ public:
private:
void reset_softbody_pin();
+
void _make_cache_dirty();
void _update_cache_pin_points_datas();
+
void _pin_point_on_physics_server(int p_point_index, bool pin);
void _add_pinned_point(int p_point_index, const NodePath &p_spatial_attachment_path);
+ void _reset_points_offsets();
+
void _remove_pinned_point(int p_point_index);
int _get_pinned_point(int p_point_index, PinnedPoint *&r_point) const;
int _has_pinned_point(int p_point_index) const;
diff --git a/scene/animation/skeleton_ik.cpp b/scene/animation/skeleton_ik.cpp
new file mode 100644
index 0000000000..4991cedfab
--- /dev/null
+++ b/scene/animation/skeleton_ik.cpp
@@ -0,0 +1,551 @@
+/*************************************************************************/
+/* skeleton_ik.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+/**
+ * @author AndreaCatania
+ */
+
+#include "skeleton_ik.h"
+
+FabrikInverseKinematic::ChainItem *FabrikInverseKinematic::ChainItem::find_child(const BoneId p_bone_id) {
+ for (int i = childs.size() - 1; 0 <= i; --i) {
+ if (p_bone_id == childs[i].bone) {
+ return &childs.write[i];
+ }
+ }
+ return NULL;
+}
+
+FabrikInverseKinematic::ChainItem *FabrikInverseKinematic::ChainItem::add_child(const BoneId p_bone_id) {
+ const int infant_child_id = childs.size();
+ childs.resize(infant_child_id + 1);
+ childs.write[infant_child_id].bone = p_bone_id;
+ childs.write[infant_child_id].parent_item = this;
+ return &childs.write[infant_child_id];
+}
+
+/// Build a chain that starts from the root to tip
+void FabrikInverseKinematic::build_chain(Task *p_task, bool p_force_simple_chain) {
+
+ ERR_FAIL_COND(-1 == p_task->root_bone);
+
+ Chain &chain(p_task->chain);
+
+ chain.tips.resize(p_task->end_effectors.size());
+ chain.chain_root.bone = p_task->root_bone;
+ chain.chain_root.initial_transform = p_task->skeleton->get_bone_global_pose(chain.chain_root.bone);
+ chain.chain_root.current_pos = chain.chain_root.initial_transform.origin;
+ chain.chain_root.pb = p_task->skeleton->get_physical_bone(chain.chain_root.bone);
+ chain.middle_chain_item = NULL;
+
+ // Holds all IDs that are composing a single chain in reverse order
+ Vector<BoneId> chain_ids;
+ // This is used to know the chain size
+ int sub_chain_size;
+ // Resize only one time in order to fit all joints for performance reason
+ chain_ids.resize(p_task->skeleton->get_bone_count());
+
+ for (int x = p_task->end_effectors.size() - 1; 0 <= x; --x) {
+
+ const EndEffector *ee(&p_task->end_effectors[x]);
+ ERR_FAIL_COND(p_task->root_bone >= ee->tip_bone);
+ ERR_FAIL_INDEX(ee->tip_bone, p_task->skeleton->get_bone_count());
+
+ sub_chain_size = 0;
+ // Picks all IDs that composing a single chain in reverse order (except the root)
+ BoneId chain_sub_tip(ee->tip_bone);
+ while (chain_sub_tip > p_task->root_bone) {
+
+ chain_ids.write[sub_chain_size++] = chain_sub_tip;
+ chain_sub_tip = p_task->skeleton->get_bone_parent(chain_sub_tip);
+ }
+
+ BoneId middle_chain_item_id = (((float)sub_chain_size) * 0.5);
+
+ // Build chain by reading chain ids in reverse order
+ // For each chain item id will be created a ChainItem if doesn't exists
+ ChainItem *sub_chain(&chain.chain_root);
+ for (int i = sub_chain_size - 1; 0 <= i; --i) {
+
+ ChainItem *child_ci(sub_chain->find_child(chain_ids[i]));
+ if (!child_ci) {
+
+ child_ci = sub_chain->add_child(chain_ids[i]);
+
+ child_ci->pb = p_task->skeleton->get_physical_bone(child_ci->bone);
+
+ child_ci->initial_transform = p_task->skeleton->get_bone_global_pose(child_ci->bone);
+ child_ci->current_pos = child_ci->initial_transform.origin;
+
+ if (child_ci->parent_item) {
+ child_ci->length = (child_ci->current_pos - child_ci->parent_item->current_pos).length();
+ }
+ }
+
+ sub_chain = child_ci;
+
+ if (middle_chain_item_id == i) {
+ chain.middle_chain_item = child_ci;
+ }
+ }
+
+ if (!middle_chain_item_id)
+ chain.middle_chain_item = NULL;
+
+ // Initialize current tip
+ chain.tips.write[x].chain_item = sub_chain;
+ chain.tips.write[x].end_effector = ee;
+
+ if (p_force_simple_chain) {
+ // NOTE:
+ // This is an "hack" that force to create only one tip per chain since the solver of multi tip (end effector)
+ // is not yet created.
+ // Remove this code when this is done
+ break;
+ }
+ }
+}
+
+void FabrikInverseKinematic::update_chain(const Skeleton *p_sk, ChainItem *p_chain_item) {
+
+ if (!p_chain_item)
+ return;
+
+ p_chain_item->initial_transform = p_sk->get_bone_global_pose(p_chain_item->bone);
+ p_chain_item->current_pos = p_chain_item->initial_transform.origin;
+
+ for (int i = p_chain_item->childs.size() - 1; 0 <= i; --i) {
+ update_chain(p_sk, &p_chain_item->childs.write[i]);
+ }
+}
+
+void FabrikInverseKinematic::solve_simple(Task *p_task, bool p_solve_magnet) {
+
+ real_t distance_to_goal(1e4);
+ real_t previous_distance_to_goal(0);
+ int can_solve(p_task->max_iterations);
+ while (distance_to_goal > p_task->min_distance && Math::abs(previous_distance_to_goal - distance_to_goal) > 0.005 && can_solve) {
+ previous_distance_to_goal = distance_to_goal;
+ --can_solve;
+
+ solve_simple_backwards(p_task->chain, p_solve_magnet);
+ solve_simple_forwards(p_task->chain, p_solve_magnet);
+
+ distance_to_goal = (p_task->chain.tips[0].chain_item->current_pos - p_task->chain.tips[0].end_effector->goal_transform.origin).length();
+ }
+}
+
+void FabrikInverseKinematic::solve_simple_backwards(Chain &r_chain, bool p_solve_magnet) {
+
+ if (p_solve_magnet && !r_chain.middle_chain_item) {
+ return;
+ }
+
+ Vector3 goal;
+ ChainItem *sub_chain_tip;
+ if (p_solve_magnet) {
+ goal = r_chain.magnet_position;
+ sub_chain_tip = r_chain.middle_chain_item;
+ } else {
+ goal = r_chain.tips[0].end_effector->goal_transform.origin;
+ sub_chain_tip = r_chain.tips[0].chain_item;
+ }
+
+ while (sub_chain_tip) {
+ sub_chain_tip->current_pos = goal;
+
+ if (sub_chain_tip->parent_item) {
+ // Not yet in the chain root
+ // So calculate next goal location
+
+ const Vector3 look_parent((sub_chain_tip->parent_item->current_pos - sub_chain_tip->current_pos).normalized());
+ goal = sub_chain_tip->current_pos + (look_parent * sub_chain_tip->length);
+
+ // [TODO] Constraints goes here
+ }
+
+ sub_chain_tip = sub_chain_tip->parent_item;
+ }
+}
+
+void FabrikInverseKinematic::solve_simple_forwards(Chain &r_chain, bool p_solve_magnet) {
+
+ if (p_solve_magnet && !r_chain.middle_chain_item) {
+ return;
+ }
+
+ ChainItem *sub_chain_root(&r_chain.chain_root);
+ Vector3 origin(r_chain.chain_root.initial_transform.origin);
+
+ while (sub_chain_root) { // Reach the tip
+ sub_chain_root->current_pos = origin;
+
+ if (!sub_chain_root->childs.empty()) {
+
+ ChainItem &child(sub_chain_root->childs.write[0]);
+
+ // Is not tip
+ // So calculate next origin location
+
+ // Look child
+ sub_chain_root->current_ori = (child.current_pos - sub_chain_root->current_pos).normalized();
+ origin = sub_chain_root->current_pos + (sub_chain_root->current_ori * child.length);
+
+ // [TODO] Constraints goes here
+
+ if (p_solve_magnet && sub_chain_root == r_chain.middle_chain_item) {
+ // In case of magnet solving this is the tip
+ sub_chain_root = NULL;
+ } else {
+ sub_chain_root = &child;
+ }
+ } else {
+
+ // Is tip
+ sub_chain_root = NULL;
+ }
+ }
+}
+
+FabrikInverseKinematic::Task *FabrikInverseKinematic::create_simple_task(Skeleton *p_sk, BoneId root_bone, BoneId tip_bone, const Transform &goal_transform) {
+
+ FabrikInverseKinematic::EndEffector ee;
+ ee.tip_bone = tip_bone;
+
+ Task *task(memnew(Task));
+ task->skeleton = p_sk;
+ task->root_bone = root_bone;
+ task->end_effectors.push_back(ee);
+ task->goal_global_transform = goal_transform;
+
+ build_chain(task);
+
+ return task;
+}
+
+void FabrikInverseKinematic::free_task(Task *p_task) {
+ if (p_task)
+ memdelete(p_task);
+}
+
+void FabrikInverseKinematic::set_goal(Task *p_task, const Transform &p_goal) {
+ p_task->goal_global_transform = p_goal;
+}
+
+void FabrikInverseKinematic::make_goal(Task *p_task, const Transform &p_inverse_transf, real_t blending_delta) {
+
+ if (blending_delta >= 0.99f) {
+ // Update the end_effector (local transform) without blending
+ p_task->end_effectors.write[0].goal_transform = p_inverse_transf * p_task->goal_global_transform;
+ } else {
+
+ // End effector in local transform
+ const Transform end_effector_pose(p_task->skeleton->get_bone_global_pose(p_task->end_effectors.write[0].tip_bone));
+
+ // Update the end_effector (local transform) by blending with current pose
+ p_task->end_effectors.write[0].goal_transform = end_effector_pose.interpolate_with(p_inverse_transf * p_task->goal_global_transform, blending_delta);
+ }
+}
+
+void FabrikInverseKinematic::solve(Task *p_task, real_t blending_delta, bool p_use_magnet, const Vector3 &p_magnet_position) {
+
+ if (blending_delta <= 0.01f) {
+ return; // Skip solving
+ }
+
+ make_goal(p_task, p_task->skeleton->get_global_transform().affine_inverse().scaled(p_task->skeleton->get_global_transform().get_basis().get_scale()), blending_delta);
+
+ update_chain(p_task->skeleton, &p_task->chain.chain_root);
+
+ if (p_use_magnet && p_task->chain.middle_chain_item) {
+ p_task->chain.magnet_position = p_task->chain.middle_chain_item->initial_transform.origin.linear_interpolate(p_magnet_position, blending_delta);
+ solve_simple(p_task, true);
+ }
+ solve_simple(p_task, false);
+
+ // Assign new bone position.
+ ChainItem *ci(&p_task->chain.chain_root);
+ while (ci) {
+ Transform new_bone_pose(ci->initial_transform);
+ new_bone_pose.origin = ci->current_pos;
+
+ if (!ci->childs.empty()) {
+
+ /// Rotate basis
+ const Vector3 initial_ori((ci->childs[0].initial_transform.origin - ci->initial_transform.origin).normalized());
+ const Vector3 rot_axis(initial_ori.cross(ci->current_ori).normalized());
+
+ if (rot_axis[0] != 0 && rot_axis[1] != 0 && rot_axis[2] != 0) {
+ const real_t rot_angle(Math::acos(CLAMP(initial_ori.dot(ci->current_ori), -1, 1)));
+ new_bone_pose.basis.rotate(rot_axis, rot_angle);
+ }
+ } else {
+ // Set target orientation to tip
+ new_bone_pose.basis = p_task->chain.tips[0].end_effector->goal_transform.basis;
+ }
+
+ p_task->skeleton->set_bone_global_pose(ci->bone, new_bone_pose);
+
+ if (!ci->childs.empty())
+ ci = &ci->childs.write[0];
+ else
+ ci = NULL;
+ }
+}
+
+void SkeletonIK::_validate_property(PropertyInfo &property) const {
+
+ if (property.name == "root_bone" || property.name == "tip_bone") {
+
+ if (skeleton) {
+
+ String names;
+ for (int i = 0; i < skeleton->get_bone_count(); i++) {
+ if (i > 0)
+ names += ",";
+ names += skeleton->get_bone_name(i);
+ }
+
+ property.hint = PROPERTY_HINT_ENUM;
+ property.hint_string = names;
+ } else {
+
+ property.hint = PROPERTY_HINT_NONE;
+ property.hint_string = "";
+ }
+ }
+}
+
+void SkeletonIK::_bind_methods() {
+
+ ClassDB::bind_method(D_METHOD("set_root_bone", "root_bone"), &SkeletonIK::set_root_bone);
+ ClassDB::bind_method(D_METHOD("get_root_bone"), &SkeletonIK::get_root_bone);
+
+ ClassDB::bind_method(D_METHOD("set_tip_bone", "tip_bone"), &SkeletonIK::set_tip_bone);
+ ClassDB::bind_method(D_METHOD("get_tip_bone"), &SkeletonIK::get_tip_bone);
+
+ ClassDB::bind_method(D_METHOD("set_interpolation", "interpolation"), &SkeletonIK::set_interpolation);
+ ClassDB::bind_method(D_METHOD("get_interpolation"), &SkeletonIK::get_interpolation);
+
+ ClassDB::bind_method(D_METHOD("set_target_transform", "target"), &SkeletonIK::set_target_transform);
+ ClassDB::bind_method(D_METHOD("get_target_transform"), &SkeletonIK::get_target_transform);
+
+ ClassDB::bind_method(D_METHOD("set_target_node", "node"), &SkeletonIK::set_target_node);
+ ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonIK::get_target_node);
+
+ ClassDB::bind_method(D_METHOD("set_use_magnet", "use"), &SkeletonIK::set_use_magnet);
+ ClassDB::bind_method(D_METHOD("is_using_magnet"), &SkeletonIK::is_using_magnet);
+
+ ClassDB::bind_method(D_METHOD("set_magnet_position", "local_position"), &SkeletonIK::set_magnet_position);
+ ClassDB::bind_method(D_METHOD("get_magnet_position"), &SkeletonIK::get_magnet_position);
+
+ ClassDB::bind_method(D_METHOD("get_parent_skeleton"), &SkeletonIK::get_parent_skeleton);
+ ClassDB::bind_method(D_METHOD("is_running"), &SkeletonIK::is_running);
+
+ ClassDB::bind_method(D_METHOD("set_min_distance", "min_distance"), &SkeletonIK::set_min_distance);
+ ClassDB::bind_method(D_METHOD("get_min_distance"), &SkeletonIK::get_min_distance);
+
+ ClassDB::bind_method(D_METHOD("set_max_iterations", "iterations"), &SkeletonIK::set_max_iterations);
+ ClassDB::bind_method(D_METHOD("get_max_iterations"), &SkeletonIK::get_max_iterations);
+
+ ClassDB::bind_method(D_METHOD("start", "one_time"), &SkeletonIK::start, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("stop"), &SkeletonIK::stop);
+
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "root_bone"), "set_root_bone", "get_root_bone");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "tip_bone"), "set_tip_bone", "get_tip_bone");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "interpolation", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_interpolation", "get_interpolation");
+ ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "target"), "set_target_transform", "get_target_transform");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_magnet"), "set_use_magnet", "is_using_magnet");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "magnet"), "set_magnet_position", "get_magnet_position");
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node"), "set_target_node", "get_target_node");
+ ADD_PROPERTY(PropertyInfo(Variant::REAL, "min_distance"), "set_min_distance", "get_min_distance");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "max_iterations"), "set_max_iterations", "get_max_iterations");
+}
+
+void SkeletonIK::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ skeleton = Object::cast_to<Skeleton>(get_parent());
+ reload_chain();
+ } break;
+ case NOTIFICATION_INTERNAL_PROCESS: {
+
+ if (target_node_override)
+ reload_goal();
+
+ _solve_chain();
+
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ reload_chain();
+ } break;
+ }
+}
+
+SkeletonIK::SkeletonIK() :
+ Node(),
+ interpolation(1),
+ skeleton(NULL),
+ target_node_override(NULL),
+ use_magnet(false),
+ min_distance(0.01),
+ max_iterations(10),
+ task(NULL) {
+
+ set_process_priority(1);
+}
+
+SkeletonIK::~SkeletonIK() {
+ FabrikInverseKinematic::free_task(task);
+ task = NULL;
+}
+
+void SkeletonIK::set_root_bone(const StringName &p_root_bone) {
+ root_bone = p_root_bone;
+ reload_chain();
+}
+
+StringName SkeletonIK::get_root_bone() const {
+ return root_bone;
+}
+
+void SkeletonIK::set_tip_bone(const StringName &p_tip_bone) {
+ tip_bone = p_tip_bone;
+ reload_chain();
+}
+
+StringName SkeletonIK::get_tip_bone() const {
+ return tip_bone;
+}
+
+void SkeletonIK::set_interpolation(real_t p_interpolation) {
+ interpolation = p_interpolation;
+}
+
+real_t SkeletonIK::get_interpolation() const {
+ return interpolation;
+}
+
+void SkeletonIK::set_target_transform(const Transform &p_target) {
+ target = p_target;
+ reload_goal();
+}
+
+const Transform &SkeletonIK::get_target_transform() const {
+ return target;
+}
+
+void SkeletonIK::set_target_node(const NodePath &p_node) {
+ target_node_path_override = p_node;
+ target_node_override = NULL;
+ reload_goal();
+}
+
+NodePath SkeletonIK::get_target_node() {
+ return target_node_path_override;
+}
+
+void SkeletonIK::set_use_magnet(bool p_use) {
+ use_magnet = p_use;
+}
+
+bool SkeletonIK::is_using_magnet() const {
+ return use_magnet;
+}
+
+void SkeletonIK::set_magnet_position(const Vector3 &p_local_position) {
+ magnet_position = p_local_position;
+}
+
+const Vector3 &SkeletonIK::get_magnet_position() const {
+ return magnet_position;
+}
+
+void SkeletonIK::set_min_distance(real_t p_min_distance) {
+ min_distance = p_min_distance;
+}
+
+void SkeletonIK::set_max_iterations(int p_iterations) {
+ max_iterations = p_iterations;
+}
+
+bool SkeletonIK::is_running() {
+ return is_processing_internal();
+}
+
+void SkeletonIK::start(bool p_one_time) {
+ if (p_one_time) {
+ set_process_internal(false);
+ _solve_chain();
+ } else {
+ set_process_internal(true);
+ }
+}
+
+void SkeletonIK::stop() {
+ set_process_internal(false);
+}
+
+Transform SkeletonIK::_get_target_transform() {
+
+ if (!target_node_override && !target_node_path_override.is_empty())
+ target_node_override = Object::cast_to<Spatial>(get_node(target_node_path_override));
+
+ if (target_node_override)
+ return target_node_override->get_global_transform();
+ else
+ return target;
+}
+
+void SkeletonIK::reload_chain() {
+
+ FabrikInverseKinematic::free_task(task);
+ task = NULL;
+
+ if (!skeleton)
+ return;
+
+ task = FabrikInverseKinematic::create_simple_task(skeleton, skeleton->find_bone(root_bone), skeleton->find_bone(tip_bone), _get_target_transform());
+ task->max_iterations = max_iterations;
+ task->min_distance = min_distance;
+}
+
+void SkeletonIK::reload_goal() {
+ if (!task)
+ return;
+
+ FabrikInverseKinematic::set_goal(task, _get_target_transform());
+}
+
+void SkeletonIK::_solve_chain() {
+ if (!task)
+ return;
+ FabrikInverseKinematic::solve(task, interpolation, use_magnet, magnet_position);
+}
diff --git a/scene/animation/skeleton_ik.h b/scene/animation/skeleton_ik.h
new file mode 100644
index 0000000000..366c599c01
--- /dev/null
+++ b/scene/animation/skeleton_ik.h
@@ -0,0 +1,212 @@
+/*************************************************************************/
+/* skeleton_ik.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef SKELETON_IK_H
+#define SKELETON_IK_H
+
+/**
+ * @author AndreaCatania
+ */
+
+#include "core/math/transform.h"
+#include "scene/3d/skeleton.h"
+
+class FabrikInverseKinematic {
+
+ struct EndEffector {
+ BoneId tip_bone;
+ Transform goal_transform;
+ };
+
+ struct ChainItem {
+
+ Vector<ChainItem> childs;
+ ChainItem *parent_item;
+
+ // Bone info
+ BoneId bone;
+ PhysicalBone *pb;
+
+ real_t length;
+ /// Positions relative to root bone
+ Transform initial_transform;
+ Vector3 current_pos;
+ // Direction from this bone to child
+ Vector3 current_ori;
+
+ ChainItem() :
+ parent_item(NULL),
+ bone(-1),
+ pb(NULL),
+ length(0) {}
+
+ ChainItem *find_child(const BoneId p_bone_id);
+ ChainItem *add_child(const BoneId p_bone_id);
+ };
+
+ struct ChainTip {
+ ChainItem *chain_item;
+ const EndEffector *end_effector;
+
+ ChainTip() :
+ chain_item(NULL),
+ end_effector(NULL) {}
+
+ ChainTip(ChainItem *p_chain_item, const EndEffector *p_end_effector) :
+ chain_item(p_chain_item),
+ end_effector(p_end_effector) {}
+
+ ChainTip(const ChainTip &p_other_ct) :
+ chain_item(p_other_ct.chain_item),
+ end_effector(p_other_ct.end_effector) {}
+ };
+
+ struct Chain {
+ ChainItem chain_root;
+ ChainItem *middle_chain_item;
+ Vector<ChainTip> tips;
+ Vector3 magnet_position;
+ };
+
+public:
+ struct Task : public RID_Data {
+ RID self;
+ Skeleton *skeleton;
+
+ Chain chain;
+
+ // Settings
+ real_t min_distance;
+ int max_iterations;
+
+ // Bone data
+ BoneId root_bone;
+ Vector<EndEffector> end_effectors;
+
+ Transform goal_global_transform;
+
+ Task() :
+ skeleton(NULL),
+ min_distance(0.01),
+ max_iterations(10),
+ root_bone(-1) {}
+ };
+
+private:
+ /// Init a chain that starts from the root to tip
+ static void build_chain(Task *p_task, bool p_force_simple_chain = true);
+
+ static void update_chain(const Skeleton *p_sk, ChainItem *p_chain_item);
+
+ static void solve_simple(Task *p_task, bool p_solve_magnet);
+ /// Special solvers that solve only chains with one end effector
+ static void solve_simple_backwards(Chain &r_chain, bool p_solve_magnet);
+ static void solve_simple_forwards(Chain &r_chain, bool p_solve_magnet);
+
+public:
+ static Task *create_simple_task(Skeleton *p_sk, BoneId root_bone, BoneId tip_bone, const Transform &goal_transform);
+ static void free_task(Task *p_task);
+ // The goal of chain should be always in local space
+ static void set_goal(Task *p_task, const Transform &p_goal);
+ static void make_goal(Task *p_task, const Transform &p_inverse_transf, real_t blending_delta);
+ static void solve(Task *p_task, real_t blending_delta, bool p_use_magnet, const Vector3 &p_magnet_position);
+};
+
+class SkeletonIK : public Node {
+ GDCLASS(SkeletonIK, Node);
+
+ StringName root_bone;
+ StringName tip_bone;
+ real_t interpolation;
+ Transform target;
+ NodePath target_node_path_override;
+ bool use_magnet;
+ Vector3 magnet_position;
+
+ real_t min_distance;
+ int max_iterations;
+
+ Skeleton *skeleton;
+ Spatial *target_node_override;
+ FabrikInverseKinematic::Task *task;
+
+protected:
+ virtual void
+ _validate_property(PropertyInfo &property) const;
+
+ static void _bind_methods();
+ virtual void _notification(int p_notification);
+
+public:
+ SkeletonIK();
+ virtual ~SkeletonIK();
+
+ void set_root_bone(const StringName &p_root_bone);
+ StringName get_root_bone() const;
+
+ void set_tip_bone(const StringName &p_tip_bone);
+ StringName get_tip_bone() const;
+
+ void set_interpolation(real_t p_interpolation);
+ real_t get_interpolation() const;
+
+ void set_target_transform(const Transform &p_target);
+ const Transform &get_target_transform() const;
+
+ void set_target_node(const NodePath &p_node);
+ NodePath get_target_node();
+
+ void set_use_magnet(bool p_use);
+ bool is_using_magnet() const;
+
+ void set_magnet_position(const Vector3 &p_constraint);
+ const Vector3 &get_magnet_position() const;
+
+ void set_min_distance(real_t p_min_distance);
+ real_t get_min_distance() const { return min_distance; }
+
+ void set_max_iterations(int p_iterations);
+ int get_max_iterations() const { return max_iterations; }
+
+ Skeleton *get_parent_skeleton() const { return skeleton; }
+
+ bool is_running();
+
+ void start(bool p_one_time = false);
+ void stop();
+
+private:
+ Transform _get_target_transform();
+ void reload_chain();
+ void reload_goal();
+ void _solve_chain();
+};
+
+#endif // SKELETON_IK_H
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 68de7b3a0a..6144240328 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -1415,11 +1415,6 @@ bool Node::is_greater_than(const Node *p_node) const {
return res;
}
-bool Node::has_priority_higher_than(const Node *p_node) const {
- ERR_FAIL_NULL_V(p_node, false);
- return data.process_priority > p_node->data.process_priority;
-}
-
void Node::get_owned_by(Node *p_by, List<Node *> *p_owned) {
if (data.owner == p_by)
diff --git a/scene/main/node.h b/scene/main/node.h
index 9ee0340678..f3422618ce 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -72,7 +72,7 @@ public:
struct ComparatorWithPriority {
- bool operator()(const Node *p_a, const Node *p_b) const { return p_b->has_priority_higher_than(p_a) || p_b->is_greater_than(p_a); }
+ bool operator()(const Node *p_a, const Node *p_b) const { return p_b->data.process_priority == p_a->data.process_priority ? p_b->is_greater_than(p_a) : p_b->data.process_priority > p_a->data.process_priority; }
};
private:
@@ -265,7 +265,6 @@ public:
bool is_a_parent_of(const Node *p_node) const;
bool is_greater_than(const Node *p_node) const;
- bool has_priority_higher_than(const Node *p_node) const;
NodePath get_path() const;
NodePath get_path_to(const Node *p_node) const;
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index a4fd35304a..ffac166453 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -204,6 +204,7 @@
#include "scene/3d/sprite_3d.h"
#include "scene/3d/vehicle_body.h"
#include "scene/3d/visibility_notifier.h"
+#include "scene/animation/skeleton_ik.h"
#include "scene/resources/environment.h"
#include "scene/resources/physics_material.h"
#endif
@@ -361,6 +362,7 @@ void register_scene_types() {
ClassDB::register_class<Spatial>();
ClassDB::register_virtual_class<SpatialGizmo>();
ClassDB::register_class<Skeleton>();
+ ClassDB::register_class<SkeletonIK>();
ClassDB::register_class<AnimationPlayer>();
ClassDB::register_class<Tween>();