summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRĂ©mi Verschelde <remi@verschelde.fr>2022-07-02 00:17:21 +0200
committerGitHub <noreply@github.com>2022-07-02 00:17:21 +0200
commita2459c7d35eeaeea8b1d4fa9ce6c9cbe74f11643 (patch)
treec52f05eddaf57d2b54e4df061ecf354a8b3ea133
parentd67691fbd6c1335caf523a2601450733bb4f101f (diff)
parentdc43cfc830e7e602ad5aa1f59ceaa82e344dd378 (diff)
Merge pull request #61610 from TokageItLab/importer-retarget-registered-gdhumanoid
-rw-r--r--core/string/node_path.cpp15
-rw-r--r--core/string/node_path.h2
-rw-r--r--core/variant/variant_call.cpp1
-rw-r--r--doc/classes/BoneMap.xml56
-rw-r--r--doc/classes/EditorScenePostImportPlugin.xml4
-rw-r--r--doc/classes/NodePath.xml6
-rw-r--r--doc/classes/SkeletonProfile.xml106
-rw-r--r--doc/classes/SkeletonProfileHumanoid.xml14
-rw-r--r--editor/editor_node.cpp2
-rw-r--r--editor/icons/BoneMapHumanBody.svg1
-rw-r--r--editor/icons/BoneMapHumanFace.svg1
-rw-r--r--editor/icons/BoneMapHumanLeftHand.svg1
-rw-r--r--editor/icons/BoneMapHumanRightHand.svg1
-rw-r--r--editor/icons/BoneMapperHandle.svg1
-rw-r--r--editor/icons/BoneMapperHandleCircle.svg1
-rw-r--r--editor/icons/BoneMapperHandleSelected.svg1
-rw-r--r--editor/import/post_import_plugin_skeleton_renamer.cpp144
-rw-r--r--editor/import/post_import_plugin_skeleton_renamer.h46
-rw-r--r--editor/import/resource_importer_scene.cpp77
-rw-r--r--editor/import/resource_importer_scene.h2
-rw-r--r--editor/import/scene_import_settings.cpp11
-rw-r--r--editor/import/scene_import_settings.h1
-rw-r--r--editor/plugins/bone_map_editor_plugin.cpp439
-rw-r--r--editor/plugins/bone_map_editor_plugin.h176
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs18
-rw-r--r--modules/mono/glue/nodepath_glue.cpp5
-rw-r--r--scene/3d/bone_attachment_3d.cpp21
-rw-r--r--scene/3d/bone_attachment_3d.h6
-rw-r--r--scene/register_scene_types.cpp6
-rw-r--r--scene/resources/bone_map.cpp172
-rw-r--r--scene/resources/bone_map.h69
-rw-r--r--scene/resources/skeleton_profile.cpp514
-rw-r--r--scene/resources/skeleton_profile.h100
33 files changed, 2006 insertions, 14 deletions
diff --git a/core/string/node_path.cpp b/core/string/node_path.cpp
index 238897c2b1..30fa434fad 100644
--- a/core/string/node_path.cpp
+++ b/core/string/node_path.cpp
@@ -199,6 +199,21 @@ Vector<StringName> NodePath::get_subnames() const {
return Vector<StringName>();
}
+StringName NodePath::get_concatenated_names() const {
+ ERR_FAIL_COND_V(!data, StringName());
+
+ if (!data->concatenated_path) {
+ int pc = data->path.size();
+ String concatenated;
+ const StringName *sn = data->path.ptr();
+ for (int i = 0; i < pc; i++) {
+ concatenated += i == 0 ? sn[i].operator String() : "/" + sn[i];
+ }
+ data->concatenated_path = concatenated;
+ }
+ return data->concatenated_path;
+}
+
StringName NodePath::get_concatenated_subnames() const {
ERR_FAIL_COND_V(!data, StringName());
diff --git a/core/string/node_path.h b/core/string/node_path.h
index 53976bd524..2bce33e21e 100644
--- a/core/string/node_path.h
+++ b/core/string/node_path.h
@@ -39,6 +39,7 @@ class NodePath {
SafeRefCount refcount;
Vector<StringName> path;
Vector<StringName> subpath;
+ StringName concatenated_path;
StringName concatenated_subpath;
bool absolute;
bool has_slashes;
@@ -59,6 +60,7 @@ public:
StringName get_subname(int p_idx) const;
Vector<StringName> get_names() const;
Vector<StringName> get_subnames() const;
+ StringName get_concatenated_names() const;
StringName get_concatenated_subnames() const;
NodePath rel_path_to(const NodePath &p_np) const;
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index c14de74af7..8e16a767cf 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -1806,6 +1806,7 @@ static void _register_variant_builtin_methods() {
bind_method(NodePath, get_subname_count, sarray(), varray());
bind_method(NodePath, hash, sarray(), varray());
bind_method(NodePath, get_subname, sarray("idx"), varray());
+ bind_method(NodePath, get_concatenated_names, sarray(), varray());
bind_method(NodePath, get_concatenated_subnames, sarray(), varray());
bind_method(NodePath, get_as_property_path, sarray(), varray());
bind_method(NodePath, is_empty, sarray(), varray());
diff --git a/doc/classes/BoneMap.xml b/doc/classes/BoneMap.xml
new file mode 100644
index 0000000000..371cb4fa93
--- /dev/null
+++ b/doc/classes/BoneMap.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="BoneMap" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Bone map for retargeting.
+ </brief_description>
+ <description>
+ This class contains a hashmap that uses a list of bone names in [SkeletonProfile] as key names.
+ By assigning the actual [Skeleton3D] bone name as the key value, it maps the [Skeleton3D] to the [SkeletonProfile].
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="find_profile_bone_name" qualifiers="const">
+ <return type="StringName" />
+ <argument index="0" name="skeleton_bone_name" type="StringName" />
+ <description>
+ Returns a profile bone name having [code]skeleton_bone_name[/code]. If not found, an empty [StringName] will be returned.
+ In the retargeting process, the returned bone name is the bone name of the target skeleton.
+ </description>
+ </method>
+ <method name="get_skeleton_bone_name" qualifiers="const">
+ <return type="StringName" />
+ <argument index="0" name="profile_bone_name" type="StringName" />
+ <description>
+ Returns a skeleton bone name is mapped to [code]profile_bone_name[/code].
+ In the retargeting process, the returned bone name is the bone name of the source skeleton.
+ </description>
+ </method>
+ <method name="set_skeleton_bone_name">
+ <return type="void" />
+ <argument index="0" name="profile_bone_name" type="StringName" />
+ <argument index="1" name="skeleton_bone_name" type="StringName" />
+ <description>
+ Maps a skeleton bone name to [code]profile_bone_name[/code].
+ In the retargeting process, the setting bone name is the bone name of the source skeleton.
+ </description>
+ </method>
+ </methods>
+ <members>
+ <member name="profile" type="SkeletonProfile" setter="set_profile" getter="get_profile">
+ A [SkeletonProfile] of the mapping target. Key names in the [BoneMap] are synchronized with it.
+ </member>
+ </members>
+ <signals>
+ <signal name="bone_map_updated">
+ <description>
+ This signal is emitted when change the key value in the [BoneMap]. This is used to validate mapping and to update [BoneMap] editor.
+ </description>
+ </signal>
+ <signal name="profile_updated">
+ <description>
+ This signal is emitted when change the value in profile or change the reference of profile. This is used to update key names in the [BoneMap] and to redraw the [BoneMap] editor.
+ </description>
+ </signal>
+ </signals>
+</class>
diff --git a/doc/classes/EditorScenePostImportPlugin.xml b/doc/classes/EditorScenePostImportPlugin.xml
index 0fdbd5db1e..93fd5e46ba 100644
--- a/doc/classes/EditorScenePostImportPlugin.xml
+++ b/doc/classes/EditorScenePostImportPlugin.xml
@@ -114,7 +114,9 @@
</constant>
<constant name="INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE" value="5" enum="InternalImportCategory">
</constant>
- <constant name="INTERNAL_IMPORT_CATEGORY_MAX" value="6" enum="InternalImportCategory">
+ <constant name="INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE" value="6" enum="InternalImportCategory">
+ </constant>
+ <constant name="INTERNAL_IMPORT_CATEGORY_MAX" value="7" enum="InternalImportCategory">
</constant>
</constants>
</class>
diff --git a/doc/classes/NodePath.xml b/doc/classes/NodePath.xml
index 00c5dcaa3d..d9e0680a38 100644
--- a/doc/classes/NodePath.xml
+++ b/doc/classes/NodePath.xml
@@ -87,6 +87,12 @@
[/codeblocks]
</description>
</method>
+ <method name="get_concatenated_names" qualifiers="const">
+ <return type="StringName" />
+ <description>
+ Returns all paths concatenated with a slash character ([code]/[/code]) as separator without subnames.
+ </description>
+ </method>
<method name="get_concatenated_subnames" qualifiers="const">
<return type="StringName" />
<description>
diff --git a/doc/classes/SkeletonProfile.xml b/doc/classes/SkeletonProfile.xml
new file mode 100644
index 0000000000..55a2ea6759
--- /dev/null
+++ b/doc/classes/SkeletonProfile.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="SkeletonProfile" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ Profile of a virtual skeleton used as a target for retargeting.
+ </brief_description>
+ <description>
+ This resource is used in [EditorScenePostImport]. Some parameters are referring to bones in [Skeleton3D], [Skin], [Animation], and some other nodes are rewritten based on the parameters of [SkeletonProfile].
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="get_bone_name" qualifiers="const">
+ <return type="StringName" />
+ <argument index="0" name="bone_idx" type="int" />
+ <description>
+ Returns the name of the bone at [code]bone_idx[/code] that will be the key name in the [BoneMap].
+ In the retargeting process, the returned bone name is the bone name of the target skeleton.
+ </description>
+ </method>
+ <method name="get_group" qualifiers="const">
+ <return type="StringName" />
+ <argument index="0" name="bone_idx" type="int" />
+ <description>
+ Returns the group of the bone at [code]bone_idx[/code].
+ </description>
+ </method>
+ <method name="get_group_name" qualifiers="const">
+ <return type="StringName" />
+ <argument index="0" name="group_idx" type="int" />
+ <description>
+ Returns the name of the group at [code]group_idx[/code] that will be the drawing group in the [BoneMap] editor.
+ </description>
+ </method>
+ <method name="get_handle_offset" qualifiers="const">
+ <return type="Vector2" />
+ <argument index="0" name="bone_idx" type="int" />
+ <description>
+ Returns the offset of the bone at [code]bone_idx[/code] that will be the button position in the [BoneMap] editor.
+ This is the offset with origin at the top left corner of the square.
+ </description>
+ </method>
+ <method name="get_texture" qualifiers="const">
+ <return type="Texture2D" />
+ <argument index="0" name="group_idx" type="int" />
+ <description>
+ Returns the texture of the group at [code]group_idx[/code] that will be the drawing group background image in the [BoneMap] editor.
+ </description>
+ </method>
+ <method name="set_bone_name">
+ <return type="void" />
+ <argument index="0" name="bone_idx" type="int" />
+ <argument index="1" name="bone_name" type="StringName" />
+ <description>
+ Sets the name of the bone at [code]bone_idx[/code] that will be the key name in the [BoneMap].
+ In the retargeting process, the setting bone name is the bone name of the target skeleton.
+ </description>
+ </method>
+ <method name="set_group">
+ <return type="void" />
+ <argument index="0" name="bone_idx" type="int" />
+ <argument index="1" name="group" type="StringName" />
+ <description>
+ Sets the group of the bone at [code]bone_idx[/code].
+ </description>
+ </method>
+ <method name="set_group_name">
+ <return type="void" />
+ <argument index="0" name="group_idx" type="int" />
+ <argument index="1" name="group_name" type="StringName" />
+ <description>
+ Sets the name of the group at [code]group_idx[/code] that will be the drawing group in the [BoneMap] editor.
+ </description>
+ </method>
+ <method name="set_handle_offset">
+ <return type="void" />
+ <argument index="0" name="bone_idx" type="int" />
+ <argument index="1" name="handle_offset" type="Vector2" />
+ <description>
+ Sets the offset of the bone at [code]bone_idx[/code] that will be the button position in the [BoneMap] editor.
+ This is the offset with origin at the top left corner of the square.
+ </description>
+ </method>
+ <method name="set_texture">
+ <return type="void" />
+ <argument index="0" name="group_idx" type="int" />
+ <argument index="1" name="texture" type="Texture2D" />
+ <description>
+ Sets the texture of the group at [code]group_idx[/code] that will be the drawing group background image in the [BoneMap] editor.
+ </description>
+ </method>
+ </methods>
+ <members>
+ <member name="bone_size" type="int" setter="set_bone_size" getter="get_bone_size" default="0">
+ </member>
+ <member name="group_size" type="int" setter="set_group_size" getter="get_group_size" default="0">
+ </member>
+ </members>
+ <signals>
+ <signal name="profile_updated">
+ <description>
+ This signal is emitted when change the value in profile. This is used to update key name in the [BoneMap] and to redraw the [BoneMap] editor.
+ [b]Note[/b]: This signal is not connected directly to editor to simplify the reference, instead it is passed on to editor through the [BoneMap].
+ </description>
+ </signal>
+ </signals>
+</class>
diff --git a/doc/classes/SkeletonProfileHumanoid.xml b/doc/classes/SkeletonProfileHumanoid.xml
new file mode 100644
index 0000000000..065184244e
--- /dev/null
+++ b/doc/classes/SkeletonProfileHumanoid.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="SkeletonProfileHumanoid" inherits="SkeletonProfile" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ </brief_description>
+ <description>
+ A [SkeletonProfile] as a preset that is optimized for the human form. This exists for standardization, so all parameters are read-only.
+ </description>
+ <tutorials>
+ </tutorials>
+ <members>
+ <member name="bone_size" type="int" setter="set_bone_size" getter="get_bone_size" overrides="SkeletonProfile" default="56" />
+ <member name="group_size" type="int" setter="set_group_size" getter="get_group_size" overrides="SkeletonProfile" default="4" />
+ </members>
+</class>
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index e3caaf93c6..7697bbfdf4 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -134,6 +134,7 @@
#include "editor/plugins/audio_stream_editor_plugin.h"
#include "editor/plugins/audio_stream_randomizer_editor_plugin.h"
#include "editor/plugins/bit_map_editor_plugin.h"
+#include "editor/plugins/bone_map_editor_plugin.h"
#include "editor/plugins/camera_3d_editor_plugin.h"
#include "editor/plugins/canvas_item_editor_plugin.h"
#include "editor/plugins/collision_polygon_2d_editor_plugin.h"
@@ -7144,6 +7145,7 @@ EditorNode::EditorNode() {
add_editor_plugin(memnew(GradientTexture2DEditorPlugin));
add_editor_plugin(memnew(BitMapEditorPlugin));
add_editor_plugin(memnew(RayCast2DEditorPlugin));
+ add_editor_plugin(memnew(BoneMapEditorPlugin));
for (int i = 0; i < EditorPlugins::get_plugin_count(); i++) {
add_editor_plugin(EditorPlugins::create(i));
diff --git a/editor/icons/BoneMapHumanBody.svg b/editor/icons/BoneMapHumanBody.svg
new file mode 100644
index 0000000000..2c2c5db1f6
--- /dev/null
+++ b/editor/icons/BoneMapHumanBody.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 1024 1024" height="1024" viewBox="0 0 1024 1024" width="1024" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h1024v1024h-1024z" fill="#3f3f3f"/><path d="m926.5 217.162c-11.5-2-26.03 4.547-37.5 6.5-15.723 2.678-25.238 3.24-33.333 5.167-1.227.292-3.103.763-5.792.958 0 0-.019.16-.052.437-36.819.994-106.823-6.062-138.156-2.062-23.816 3.041-86.334-5.667-105.667-6-13.911-.239-59.292-4.583-71.75-2.5-.667-4.083-1.5-10.75.95-17.468 14.881-7.246 27.229-21.569 35.341-38.467.922 4.424 6.252 4.929 12.459-14.231 5.662-17.478 2.324-22.254-2.313-22.525.172-2.056.279-4.105.313-6.142.788-48.041-15-78.667-69-78.667s-69.787 30.626-69 78.667c.033 2.036.141 4.086.313 6.142-4.637.271-7.975 5.048-2.313 22.525 6.207 19.16 11.537 18.655 12.459 14.231 8.113 16.897 20.461 31.221 35.342 38.467 2.449 6.718 1.617 13.385.949 17.468-12.457-2.083-57.838 2.261-71.75 2.5-19.332.333-81.85 9.041-105.666 6-31.333-4-101.337 3.056-138.156 2.062-.033-.276-.053-.437-.053-.437-2.689-.195-4.564-.666-5.791-.958-8.096-1.927-17.611-2.489-33.334-5.167-11.469-1.953-26-8.5-37.5-6.5-3.367.586 6 9.834 15.5 12.334 13.635 3.588 25.25 10.666 36 13.166-2.25 3.75-15.59 7.063-23 12-5.336 3.557 6.5 6.5 12 5 20.842-5.684 22.973.389 37.514-9.019 30.078 4.078 102.537 20.514 122.154 14.186 12.457-4.018 100.332 7.083 142.332 5.833 6.039-.18 1.656 65.563 2 73.5 3 69-16.842 133.135-18.666 169.667-1.92 38.42-3.42 57.919 7.666 131.333 6.967 46.126-2.521 82.079-2 94 6 137 29 172 4 221-14 27.44 67.449 26.958 65 9-3.012-22.092-12.666-22.333-10.666-46.333 1.896-22.768 16.049-151.298 8.666-206.667-2-15 0-26 2-66 2.355-47.101 7-88 14-123 7 35 11.645 75.899 14 123 2 40 4 51 2 66-7.383 55.369 6.77 183.899 8.667 206.667 2 24-7.654 24.241-10.667 46.333-2.449 17.958 79 18.44 65-9-25-49-2-84 4-221 .522-11.921-8.966-47.874-2-94 11.086-73.414 9.586-92.913 7.667-131.333-1.824-36.532-21.667-100.667-18.667-169.667.345-7.938-4.039-73.68 2-73.5 42 1.25 129.876-9.852 142.333-5.833 19.616 6.328 92.076-10.107 122.153-14.186 14.541 9.407 16.673 3.335 37.514 9.019 5.5 1.5 17.336-1.443 12-5-7.409-4.937-20.75-8.25-23-12 10.75-2.5 22.366-9.578 36.001-13.166 9.5-2.5 18.866-11.748 15.499-12.334z" fill="#b2b2b2"/></svg>
diff --git a/editor/icons/BoneMapHumanFace.svg b/editor/icons/BoneMapHumanFace.svg
new file mode 100644
index 0000000000..6cb21140bc
--- /dev/null
+++ b/editor/icons/BoneMapHumanFace.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 1024 1024" height="1024" viewBox="0 0 1024 1024" width="1024" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h1024v1024h-1024z" fill="#3f3f3f"/><path d="m788.105 552.967c17.995-57.892 31.896-124.566 30.875-198.071-3.758-270.403-249.846-251.479-295.568-244.947-359.868 51.409-219.047 452.358-220.453 496.426-4.899 153.499 83.686 170.991 161.665 215.554 2.646 1.512 7.259 1.786 13.313 1.111 7.223 25.179 11.762 59.035 9.548 75.638-3.266 24.495 209.021 24.495 209.021 0 0-62.883 12.233-124.363 33.827-188.89 7.143-2.284 16.054-7.601 25.963-16.95 13.681-12.908 34.839-21.774 45.726-63.145 15.615-59.338 3.869-76.074-13.917-76.726z" fill="#b2b2b2"/></svg>
diff --git a/editor/icons/BoneMapHumanLeftHand.svg b/editor/icons/BoneMapHumanLeftHand.svg
new file mode 100644
index 0000000000..08c68bb4be
--- /dev/null
+++ b/editor/icons/BoneMapHumanLeftHand.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 1024 1024" height="1024" viewBox="0 0 1024 1024" width="1024" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h1024v1024h-1024z" fill="#3f3f3f"/><path d="m703.906 786.098c7.046-66.929 28.135-153.363 18.529-260.192-1.143-12.71-4.5-48.282-4.46-82.732.025-21.174-2.111-48.505-1.975-64.174.167-19.333-.428-41.584-.625-55.755-1.052-75.44-13.225-85.827-30.813-85.827-17.246 0-26.77 14.266-27.062 84.582-.061 14.42.5 51 .5 58.5 0 17.508-.333 34.167 0 53.5.447 25.955-4.279 68-9 68-3.902 0-8.099-39.299-9.575-76.999-.756-19.326-3.219-58.336-2.6-70.102 1.759-33.413.474-58.914 1.537-90.165 3.183-93.607-13.016-111.729-34.695-111.729-21.973 0-35.979 57.688-34.849 114.224.128 6.394-1.165 50.739.188 89.859.754 21.811-1.07 49.627-1.683 69.67-1.095 35.768-5.755 63.896-8.869 63.896-2.641 0-4.135-32.584-5.456-65.706-.859-21.557-4.468-58.477-3.664-83.616 1.886-59.012-1.139-110.226-1.063-121.501.635-94.955-14.66-123.101-36.052-123.101-21.476 0-37.188 30.192-36.6 123.343.067 10.53-2.62 99.926-1.759 121.816.865 21.992-2.773 65.062-3.517 84.818-1.299 34.521-6.49 63.947-9.124 63.947-3.281 0-10.794-25.638-11.724-60.965-.587-22.275 1.231-50.99.624-70.688-1.257-40.707-3.175-64.631-3.877-99.708-1.945-97.182-16.352-106.289-38.142-106.289-17.957 0-32.453 28.673-32.657 115.03-.065 27.702-2.429 62.626-.315 94.329.805 12.081-.622 42.512-1.875 73.894-.799 20.007-1.102 47.501-1.137 63.775-.17 78.595-26.712 133.424-36.555 131.308-30.333-6.521-51.648-43.918-71.219-117.307-10.551-39.566-36.667-71.149-69.9-77.813-25.9-5.193-19.783 46.161-1.319 125.293 8.65 37.068 27.909 86.227 39.566 122.655 31.653 98.917 125.574 188.563 160.903 228.546 17.146 19.403 236.894 19.403 264.59 0 11.525-8.07 43.087-101.557 45.724-126.616z" fill="#b2b2b2"/></svg>
diff --git a/editor/icons/BoneMapHumanRightHand.svg b/editor/icons/BoneMapHumanRightHand.svg
new file mode 100644
index 0000000000..4e40af35d8
--- /dev/null
+++ b/editor/icons/BoneMapHumanRightHand.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 1024 1024" height="1024" viewBox="0 0 1024 1024" width="1024" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h1024v1024h-1024z" fill="#3f3f3f"/><path d="m320.094 786.098c-7.046-66.929-28.135-153.363-18.529-260.192 1.143-12.71 4.5-48.282 4.46-82.732-.025-21.174 2.111-48.505 1.975-64.174-.167-19.333.428-41.584.625-55.755 1.052-75.44 13.225-85.827 30.813-85.827 17.246 0 26.77 14.266 27.062 84.582.061 14.42-.5 51-.5 58.5 0 17.508.333 34.167 0 53.5-.447 25.955 4.279 68 9 68 3.902 0 8.099-39.299 9.575-76.999.756-19.326 3.219-58.336 2.6-70.102-1.759-33.413-.474-58.914-1.537-90.165-3.183-93.607 13.016-111.729 34.695-111.729 21.973 0 35.979 57.688 34.849 114.224-.128 6.394 1.165 50.739-.188 89.859-.754 21.811 1.07 49.627 1.683 69.67 1.095 35.768 5.755 63.896 8.869 63.896 2.641 0 4.135-32.584 5.456-65.706.859-21.557 4.468-58.477 3.664-83.616-1.886-59.012 1.139-110.226 1.063-121.501-.635-94.955 14.66-123.101 36.052-123.101 21.476 0 37.188 30.192 36.6 123.343-.067 10.53 2.62 99.926 1.759 121.816-.865 21.992 2.773 65.062 3.517 84.818 1.299 34.521 6.49 63.947 9.124 63.947 3.281 0 10.794-25.638 11.724-60.965.587-22.275-1.231-50.99-.624-70.688 1.257-40.707 3.176-64.631 3.877-99.708 1.945-97.182 16.352-106.289 38.142-106.289 17.957 0 32.453 28.673 32.657 115.03.065 27.702 2.429 62.626.314 94.329-.805 12.081.622 42.512 1.875 73.894.799 20.007 1.102 47.501 1.137 63.775.171 78.595 26.713 133.424 36.556 131.308 30.333-6.521 51.648-43.918 71.219-117.307 10.551-39.566 36.667-71.149 69.9-77.813 25.9-5.193 19.783 46.161 1.318 125.293-8.649 37.068-27.909 86.227-39.566 122.655-31.652 98.917-125.573 188.563-160.902 228.546-17.146 19.403-236.894 19.403-264.59 0-11.525-8.07-43.087-101.557-45.724-126.616z" fill="#b2b2b2"/></svg>
diff --git a/editor/icons/BoneMapperHandle.svg b/editor/icons/BoneMapperHandle.svg
new file mode 100644
index 0000000000..8c7d7e1d70
--- /dev/null
+++ b/editor/icons/BoneMapperHandle.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 12 12" height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><circle cx="6" cy="6" fill-opacity=".2941" r="5"/><circle cx="6" cy="6" fill="#fff" r="4"/></svg>
diff --git a/editor/icons/BoneMapperHandleCircle.svg b/editor/icons/BoneMapperHandleCircle.svg
new file mode 100644
index 0000000000..ecf97669b8
--- /dev/null
+++ b/editor/icons/BoneMapperHandleCircle.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 12 12" height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><circle cx="6" cy="6" fill="#fff" r="3"/></svg>
diff --git a/editor/icons/BoneMapperHandleSelected.svg b/editor/icons/BoneMapperHandleSelected.svg
new file mode 100644
index 0000000000..729a443f6e
--- /dev/null
+++ b/editor/icons/BoneMapperHandleSelected.svg
@@ -0,0 +1 @@
+<svg enable-background="new -506.5 517.5 12 12" height="12" viewBox="-506.5 517.5 12 12" width="12" xmlns="http://www.w3.org/2000/svg"><circle cx="-500.5" cy="523.5" fill-opacity=".2941" r="5"/><g fill="#fff"><circle cx="-500.5" cy="523.5" r="4"/><path d="m-499.5 517.5h5v5h-1v-4h-4z"/><path d="m-494.5 524.5v5h-5v-1h4v-4z"/><path d="m-501.5 529.5h-5v-5h1v4h4z"/><path d="m-506.5 522.5v-5h5v1h-4v4z"/></g></svg>
diff --git a/editor/import/post_import_plugin_skeleton_renamer.cpp b/editor/import/post_import_plugin_skeleton_renamer.cpp
new file mode 100644
index 0000000000..b0c4bc8c30
--- /dev/null
+++ b/editor/import/post_import_plugin_skeleton_renamer.cpp
@@ -0,0 +1,144 @@
+/*************************************************************************/
+/* post_import_plugin_skeleton_renamer.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "post_import_plugin_skeleton_renamer.h"
+
+#include "editor/import/scene_import_settings.h"
+#include "scene/3d/importer_mesh_instance_3d.h"
+#include "scene/3d/skeleton_3d.h"
+#include "scene/animation/animation_player.h"
+#include "scene/resources/bone_map.h"
+
+void PostImportPluginSkeletonRenamer::get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) {
+ if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
+ r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "retarget/bone_renamer/rename_bones"), true));
+ }
+}
+
+void PostImportPluginSkeletonRenamer::internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) {
+ if (p_category == INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE) {
+ // Prepare objects.
+ Object *map = p_options["retarget/bone_map"].get_validated_object();
+ if (!map || !bool(p_options["retarget/bone_renamer/rename_bones"])) {
+ return;
+ }
+ BoneMap *bone_map = Object::cast_to<BoneMap>(map);
+ Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node);
+
+ // Rename bones in Skeleton3D.
+ {
+ int len = skeleton->get_bone_count();
+ for (int i = 0; i < len; i++) {
+ StringName bn = bone_map->find_profile_bone_name(skeleton->get_bone_name(i));
+ if (bn) {
+ skeleton->set_bone_name(i, bn);
+ }
+ }
+ }
+
+ // Rename bones in Skin.
+ {
+ TypedArray<Node> nodes = p_base_scene->find_children("*", "ImporterMeshInstance3D");
+ while (nodes.size()) {
+ ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(nodes.pop_back());
+ Ref<Skin> skin = mi->get_skin();
+ if (skin.is_valid()) {
+ Node *node = mi->get_node(mi->get_skeleton_path());
+ if (node) {
+ Skeleton3D *mesh_skeleton = Object::cast_to<Skeleton3D>(node);
+ if (mesh_skeleton && node == skeleton) {
+ int len = skin->get_bind_count();
+ for (int i = 0; i < len; i++) {
+ StringName bn = bone_map->find_profile_bone_name(skin->get_bind_name(i));
+ if (bn) {
+ skin->set_bind_name(i, bn);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Rename bones in AnimationPlayer.
+ {
+ TypedArray<Node> nodes = p_base_scene->find_children("*", "AnimationPlayer");
+ while (nodes.size()) {
+ AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(nodes.pop_back());
+ List<StringName> anims;
+ ap->get_animation_list(&anims);
+ for (const StringName &name : anims) {
+ Ref<Animation> anim = ap->get_animation(name);
+ int len = anim->get_track_count();
+ for (int i = 0; i < len; i++) {
+ if (anim->track_get_path(i).get_subname_count() != 1 || !(anim->track_get_type(i) == Animation::TYPE_POSITION_3D || anim->track_get_type(i) == Animation::TYPE_ROTATION_3D || anim->track_get_type(i) == Animation::TYPE_SCALE_3D)) {
+ continue;
+ }
+ String track_path = String(anim->track_get_path(i).get_concatenated_names());
+ Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path));
+ if (node) {
+ Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
+ if (track_skeleton && track_skeleton == skeleton) {
+ StringName bn = bone_map->find_profile_bone_name(anim->track_get_path(i).get_subname(0));
+ if (bn) {
+ anim->track_set_path(i, track_path + ":" + bn);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Rename bones in all Nodes by calling method.
+ {
+ Vector<Variant> vargs;
+ vargs.push_back(p_base_scene);
+ vargs.push_back(skeleton);
+ vargs.push_back(bone_map);
+ const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * vargs.size());
+ const Variant *args = vargs.ptr();
+ uint32_t argcount = vargs.size();
+ for (uint32_t i = 0; i < argcount; i++) {
+ argptrs[i] = &args[i];
+ }
+
+ TypedArray<Node> nodes = p_base_scene->find_children("*");
+ while (nodes.size()) {
+ Node *nd = Object::cast_to<Node>(nodes.pop_back());
+ Callable::CallError ce;
+ nd->callp("_notify_skeleton_bones_renamed", argptrs, argcount, ce);
+ }
+ }
+ }
+}
+
+PostImportPluginSkeletonRenamer::PostImportPluginSkeletonRenamer() {
+}
diff --git a/editor/import/post_import_plugin_skeleton_renamer.h b/editor/import/post_import_plugin_skeleton_renamer.h
new file mode 100644
index 0000000000..73cbabd1c5
--- /dev/null
+++ b/editor/import/post_import_plugin_skeleton_renamer.h
@@ -0,0 +1,46 @@
+/*************************************************************************/
+/* post_import_plugin_skeleton_renamer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 POST_IMPORT_PLUGIN_SKELETON_RENAMER_H
+#define POST_IMPORT_PLUGIN_SKELETON_RENAMER_H
+
+#include "resource_importer_scene.h"
+
+class PostImportPluginSkeletonRenamer : public EditorScenePostImportPlugin {
+ GDCLASS(PostImportPluginSkeletonRenamer, EditorScenePostImportPlugin);
+
+public:
+ virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) override;
+ virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) override;
+
+ PostImportPluginSkeletonRenamer();
+};
+
+#endif // POST_IMPORT_PLUGIN_SKELETON_RENAMER_H
diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp
index 171ef5bf4c..a9c43e573f 100644
--- a/editor/import/resource_importer_scene.cpp
+++ b/editor/import/resource_importer_scene.cpp
@@ -232,6 +232,7 @@ void EditorScenePostImportPlugin::_bind_methods() {
BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_MATERIAL);
BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_ANIMATION);
BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE);
+ BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE);
BIND_ENUM_CONSTANT(INTERNAL_IMPORT_CATEGORY_MAX);
}
@@ -766,6 +767,27 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<
}
{
+ //make sure this is unique
+ node_settings = node_settings.duplicate(true);
+ //fill node settings for this node with default values
+ List<ImportOption> iopts;
+ if (Object::cast_to<ImporterMeshInstance3D>(p_node)) {
+ get_internal_import_options(INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE, &iopts);
+ } else if (Object::cast_to<AnimationPlayer>(p_node)) {
+ get_internal_import_options(INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, &iopts);
+ } else if (Object::cast_to<Skeleton3D>(p_node)) {
+ get_internal_import_options(INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE, &iopts);
+ } else {
+ get_internal_import_options(INTERNAL_IMPORT_CATEGORY_NODE, &iopts);
+ }
+ for (const ImportOption &E : iopts) {
+ if (!node_settings.has(E.option.name)) {
+ node_settings[E.option.name] = E.default_value;
+ }
+ }
+ }
+
+ {
ObjectID node_id = p_node->get_instance_id();
for (int i = 0; i < post_importer_plugins.size(); i++) {
post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_NODE, p_root, p_node, Ref<Resource>(), node_settings);
@@ -785,6 +807,16 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<
}
}
+ if (Object::cast_to<Skeleton3D>(p_node)) {
+ ObjectID node_id = p_node->get_instance_id();
+ for (int i = 0; i < post_importer_plugins.size(); i++) {
+ post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE, p_root, p_node, Ref<Resource>(), node_settings);
+ if (ObjectDB::get_instance(node_id) == nullptr) { //may have been erased, so do not continue
+ break;
+ }
+ }
+ }
+
if (Object::cast_to<ImporterMeshInstance3D>(p_node)) {
ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(p_node);
@@ -799,6 +831,16 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<
if (!mat_id.is_empty() && p_material_data.has(mat_id)) {
Dictionary matdata = p_material_data[mat_id];
+ {
+ //fill node settings for this node with default values
+ List<ImportOption> iopts;
+ get_internal_import_options(INTERNAL_IMPORT_CATEGORY_MATERIAL, &iopts);
+ for (const ImportOption &E : iopts) {
+ if (!matdata.has(E.option.name)) {
+ matdata[E.option.name] = E.default_value;
+ }
+ }
+ }
for (int j = 0; j < post_importer_plugins.size(); j++) {
post_importer_plugins.write[j]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MATERIAL, p_root, p_node, mat, matdata);
@@ -966,19 +1008,6 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<
if (Object::cast_to<AnimationPlayer>(p_node)) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_node);
- {
- //make sure this is unique
- node_settings = node_settings.duplicate(true);
- //fill node settings for this node with default values
- List<ImportOption> iopts;
- get_internal_import_options(INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, &iopts);
- for (const ImportOption &E : iopts) {
- if (!node_settings.has(E.option.name)) {
- node_settings[E.option.name] = E.default_value;
- }
- }
- }
-
for (int i = 0; i < post_importer_plugins.size(); i++) {
post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE, p_root, p_node, Ref<Resource>(), node_settings);
}
@@ -1385,6 +1414,10 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "slice_" + itos(i + 1) + "/save_to_file/keep_custom_tracks"), false));
}
} break;
+ case INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE: {
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "retarget/bone_map", PROPERTY_HINT_RESOURCE_TYPE, "BoneMap", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Variant()));
+ } break;
default: {
}
}
@@ -1499,6 +1532,12 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor
}
}
} break;
+ case INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE: {
+ const bool use_retarget = p_options["retarget/bone_map"].get_validated_object() != nullptr;
+ if (p_option != "retarget/bone_map" && p_option.begins_with("retarget/")) {
+ return use_retarget;
+ }
+ } break;
default: {
}
}
@@ -1534,6 +1573,8 @@ bool ResourceImporterScene::get_internal_option_update_view_required(InternalImp
} break;
case INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE: {
} break;
+ case INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE: {
+ } break;
default: {
}
}
@@ -1622,6 +1663,16 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m
if (!mesh_id.is_empty() && p_mesh_data.has(mesh_id)) {
Dictionary mesh_settings = p_mesh_data[mesh_id];
+ {
+ //fill node settings for this node with default values
+ List<ImportOption> iopts;
+ get_internal_import_options(INTERNAL_IMPORT_CATEGORY_MESH, &iopts);
+ for (const ImportOption &E : iopts) {
+ if (!mesh_settings.has(E.option.name)) {
+ mesh_settings[E.option.name] = E.default_value;
+ }
+ }
+ }
if (mesh_settings.has("generate/shadow_meshes")) {
int shadow_meshes = mesh_settings["generate/shadow_meshes"];
diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h
index 16cf3d651d..c143e86bd4 100644
--- a/editor/import/resource_importer_scene.h
+++ b/editor/import/resource_importer_scene.h
@@ -106,6 +106,7 @@ public:
INTERNAL_IMPORT_CATEGORY_MATERIAL,
INTERNAL_IMPORT_CATEGORY_ANIMATION,
INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE,
+ INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE,
INTERNAL_IMPORT_CATEGORY_MAX
};
@@ -259,6 +260,7 @@ public:
INTERNAL_IMPORT_CATEGORY_MATERIAL = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MATERIAL,
INTERNAL_IMPORT_CATEGORY_ANIMATION = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION,
INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE,
+ INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE,
INTERNAL_IMPORT_CATEGORY_MAX = EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_MAX
};
diff --git a/editor/import/scene_import_settings.cpp b/editor/import/scene_import_settings.cpp
index 99d1658405..af145c22b4 100644
--- a/editor/import/scene_import_settings.cpp
+++ b/editor/import/scene_import_settings.cpp
@@ -339,6 +339,8 @@ void SceneImportSettings::_fill_scene(Node *p_node, TreeItem *p_parent_item) {
category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE;
} else if (Object::cast_to<AnimationPlayer>(p_node)) {
category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE;
+ } else if (Object::cast_to<Skeleton3D>(p_node)) {
+ category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE;
} else {
category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_NODE;
}
@@ -617,6 +619,13 @@ SceneImportSettings *SceneImportSettings::get_singleton() {
return singleton;
}
+Node *SceneImportSettings::get_selected_node() {
+ if (selected_id == "") {
+ return nullptr;
+ }
+ return node_map[selected_id].node;
+}
+
void SceneImportSettings::_select(Tree *p_from, String p_type, String p_id) {
selecting = true;
scene_import_settings_data->hide_options = false;
@@ -657,6 +666,8 @@ void SceneImportSettings::_select(Tree *p_from, String p_type, String p_id) {
scene_import_settings_data->hide_options = editing_animation;
} else if (Object::cast_to<AnimationPlayer>(nd.node)) {
scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE;
+ } else if (Object::cast_to<Skeleton3D>(nd.node)) {
+ scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE;
} else {
scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_NODE;
scene_import_settings_data->hide_options = editing_animation;
diff --git a/editor/import/scene_import_settings.h b/editor/import/scene_import_settings.h
index 81d13166ab..b5cf82f64b 100644
--- a/editor/import/scene_import_settings.h
+++ b/editor/import/scene_import_settings.h
@@ -201,6 +201,7 @@ public:
void update_view();
void open_settings(const String &p_path, bool p_for_animation = false);
static SceneImportSettings *get_singleton();
+ Node *get_selected_node();
SceneImportSettings();
~SceneImportSettings();
};
diff --git a/editor/plugins/bone_map_editor_plugin.cpp b/editor/plugins/bone_map_editor_plugin.cpp
new file mode 100644
index 0000000000..fffadae3eb
--- /dev/null
+++ b/editor/plugins/bone_map_editor_plugin.cpp
@@ -0,0 +1,439 @@
+/*************************************************************************/
+/* bone_map_editor_plugin.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "bone_map_editor_plugin.h"
+
+#include "editor/editor_scale.h"
+#include "editor/import/post_import_plugin_skeleton_renamer.h"
+#include "editor/import/scene_import_settings.h"
+
+void BoneMapperButton::fetch_textures() {
+ if (selected) {
+ set_normal_texture(get_theme_icon(SNAME("BoneMapperHandleSelected"), SNAME("EditorIcons")));
+ } else {
+ set_normal_texture(get_theme_icon(SNAME("BoneMapperHandle"), SNAME("EditorIcons")));
+ }
+ set_offset(SIDE_LEFT, 0);
+ set_offset(SIDE_RIGHT, 0);
+ set_offset(SIDE_TOP, 0);
+ set_offset(SIDE_BOTTOM, 0);
+
+ circle = memnew(TextureRect);
+ circle->set_texture(get_theme_icon(SNAME("BoneMapperHandleCircle"), SNAME("EditorIcons")));
+ add_child(circle);
+ set_state(BONE_MAP_STATE_UNSET);
+}
+
+StringName BoneMapperButton::get_profile_bone_name() const {
+ return profile_bone_name;
+}
+
+void BoneMapperButton::set_state(BoneMapState p_state) {
+ switch (p_state) {
+ case BONE_MAP_STATE_UNSET: {
+ circle->set_modulate(EditorSettings::get_singleton()->get("editors/bone_mapper/handle_colors/unset"));
+ } break;
+ case BONE_MAP_STATE_SET: {
+ circle->set_modulate(EditorSettings::get_singleton()->get("editors/bone_mapper/handle_colors/set"));
+ } break;
+ case BONE_MAP_STATE_ERROR: {
+ circle->set_modulate(EditorSettings::get_singleton()->get("editors/bone_mapper/handle_colors/error"));
+ } break;
+ default: {
+ } break;
+ }
+}
+
+void BoneMapperButton::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ fetch_textures();
+ } break;
+ }
+}
+
+BoneMapperButton::BoneMapperButton(const StringName p_profile_bone_name, bool p_selected) {
+ profile_bone_name = p_profile_bone_name;
+ selected = p_selected;
+}
+
+BoneMapperButton::~BoneMapperButton() {
+}
+
+void BoneMapperItem::create_editor() {
+ skeleton_bone_selector = memnew(EditorPropertyTextEnum);
+ skeleton_bone_selector->setup(skeleton_bone_names);
+ skeleton_bone_selector->set_label(profile_bone_name);
+ skeleton_bone_selector->set_selectable(false);
+ skeleton_bone_selector->set_object_and_property(bone_map.ptr(), "bone_map/" + String(profile_bone_name));
+ skeleton_bone_selector->update_property();
+ skeleton_bone_selector->connect("property_changed", callable_mp(this, &BoneMapperItem::_value_changed));
+ add_child(skeleton_bone_selector);
+}
+
+void BoneMapperItem::_update_property() {
+ if (skeleton_bone_selector->get_edited_object() && skeleton_bone_selector->get_edited_property()) {
+ skeleton_bone_selector->update_property();
+ }
+}
+
+void BoneMapperItem::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
+ bone_map->set(p_property, p_value);
+}
+
+void BoneMapperItem::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ create_editor();
+ bone_map->connect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property));
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ if (!bone_map.is_null() && bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property))) {
+ bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property));
+ }
+ } break;
+ }
+}
+
+void BoneMapperItem::_bind_methods() {
+}
+
+BoneMapperItem::BoneMapperItem(Ref<BoneMap> &p_bone_map, PackedStringArray p_skeleton_bone_names, const StringName &p_profile_bone_name) {
+ bone_map = p_bone_map;
+ skeleton_bone_names = p_skeleton_bone_names;
+ profile_bone_name = p_profile_bone_name;
+}
+
+BoneMapperItem::~BoneMapperItem() {
+}
+
+void BoneMapper::create_editor() {
+ profile_group_selector = memnew(EditorPropertyEnum);
+ profile_group_selector->set_label("Group");
+ profile_group_selector->set_selectable(false);
+ profile_group_selector->set_object_and_property(this, "current_group_idx");
+ profile_group_selector->update_property();
+ profile_group_selector->connect("property_changed", callable_mp(this, &BoneMapper::_value_changed));
+ add_child(profile_group_selector);
+
+ bone_mapper_field = memnew(AspectRatioContainer);
+ bone_mapper_field->set_stretch_mode(AspectRatioContainer::STRETCH_FIT);
+ bone_mapper_field->set_custom_minimum_size(Vector2(0, 256.0) * EDSCALE);
+ bone_mapper_field->set_h_size_flags(Control::SIZE_FILL);
+ add_child(bone_mapper_field);
+
+ profile_bg = memnew(ColorRect);
+ profile_bg->set_color(Color(0, 0, 0, 1));
+ profile_bg->set_h_size_flags(Control::SIZE_FILL);
+ profile_bg->set_v_size_flags(Control::SIZE_FILL);
+ bone_mapper_field->add_child(profile_bg);
+
+ profile_texture = memnew(TextureRect);
+ profile_texture->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
+ profile_texture->set_ignore_texture_size(true);
+ profile_texture->set_h_size_flags(Control::SIZE_FILL);
+ profile_texture->set_v_size_flags(Control::SIZE_FILL);
+ bone_mapper_field->add_child(profile_texture);
+
+ mapper_item_vbox = memnew(VBoxContainer);
+ add_child(mapper_item_vbox);
+
+ separator = memnew(HSeparator);
+ add_child(separator);
+
+ recreate_items();
+}
+
+void BoneMapper::update_group_idx() {
+ if (!bone_map->get_profile().is_valid()) {
+ return;
+ }
+
+ PackedStringArray group_names;
+ int len = bone_map->get_profile()->get_group_size();
+ for (int i = 0; i < len; i++) {
+ group_names.push_back(bone_map->get_profile()->get_group_name(i));
+ }
+ if (current_group_idx >= len) {
+ current_group_idx = 0;
+ }
+ if (len > 0) {
+ profile_group_selector->setup(group_names);
+ profile_group_selector->update_property();
+ profile_group_selector->set_read_only(false);
+ }
+}
+
+void BoneMapper::set_current_group_idx(int p_group_idx) {
+ current_group_idx = p_group_idx;
+ recreate_editor();
+}
+
+int BoneMapper::get_current_group_idx() const {
+ return current_group_idx;
+}
+
+void BoneMapper::set_current_bone_idx(int p_bone_idx) {
+ current_bone_idx = p_bone_idx;
+ recreate_editor();
+}
+
+int BoneMapper::get_current_bone_idx() const {
+ return current_bone_idx;
+}
+
+void BoneMapper::recreate_editor() {
+ // Clear buttons.
+ int len = bone_mapper_buttons.size();
+ for (int i = 0; i < len; i++) {
+ profile_texture->remove_child(bone_mapper_buttons[i]);
+ memdelete(bone_mapper_buttons[i]);
+ }
+ bone_mapper_buttons.clear();
+
+ // Organize mapper items.
+ len = bone_mapper_items.size();
+ for (int i = 0; i < len; i++) {
+ bone_mapper_items[i]->set_visible(current_bone_idx == i);
+ }
+
+ Ref<SkeletonProfile> profile = bone_map->get_profile();
+ if (profile.is_valid()) {
+ SkeletonProfileHumanoid *hmn = Object::cast_to<SkeletonProfileHumanoid>(profile.ptr());
+ if (hmn) {
+ StringName hmn_group_name = profile->get_group_name(current_group_idx);
+ if (hmn_group_name == "Body") {
+ profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanBody"), SNAME("EditorIcons")));
+ } else if (hmn_group_name == "Face") {
+ profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanFace"), SNAME("EditorIcons")));
+ } else if (hmn_group_name == "LeftHand") {
+ profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanLeftHand"), SNAME("EditorIcons")));
+ } else if (hmn_group_name == "RightHand") {
+ profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanRightHand"), SNAME("EditorIcons")));
+ }
+ } else {
+ profile_texture->set_texture(profile->get_texture(current_group_idx));
+ }
+ } else {
+ profile_texture->set_texture(Ref<Texture2D>());
+ }
+
+ if (!profile.is_valid()) {
+ return;
+ }
+
+ for (int i = 0; i < len; i++) {
+ if (profile->get_group(i) == profile->get_group_name(current_group_idx)) {
+ BoneMapperButton *mb = memnew(BoneMapperButton(profile->get_bone_name(i), current_bone_idx == i));
+ mb->connect("pressed", callable_mp(this, &BoneMapper::set_current_bone_idx), varray(i), CONNECT_DEFERRED);
+ mb->set_h_grow_direction(GROW_DIRECTION_BOTH);
+ mb->set_v_grow_direction(GROW_DIRECTION_BOTH);
+ Vector2 vc = profile->get_handle_offset(i);
+ bone_mapper_buttons.push_back(mb);
+ profile_texture->add_child(mb);
+ mb->set_anchor(SIDE_LEFT, vc.x);
+ mb->set_anchor(SIDE_RIGHT, vc.x);
+ mb->set_anchor(SIDE_TOP, vc.y);
+ mb->set_anchor(SIDE_BOTTOM, vc.y);
+ }
+ }
+
+ _update_state();
+}
+
+void BoneMapper::clear_items() {
+ // Clear items.
+ int len = bone_mapper_items.size();
+ for (int i = 0; i < len; i++) {
+ mapper_item_vbox->remove_child(bone_mapper_items[i]);
+ memdelete(bone_mapper_items[i]);
+ }
+ bone_mapper_items.clear();
+}
+
+void BoneMapper::recreate_items() {
+ clear_items();
+ // Create items by profile.
+ Ref<SkeletonProfile> profile = bone_map->get_profile();
+ if (profile.is_valid()) {
+ PackedStringArray skeleton_bone_names;
+ skeleton_bone_names.push_back(String());
+
+ int len = skeleton->get_bone_count();
+ for (int i = 0; i < len; i++) {
+ skeleton_bone_names.push_back(skeleton->get_bone_name(i));
+ }
+
+ len = profile->get_bone_size();
+ for (int i = 0; i < len; i++) {
+ StringName bn = profile->get_bone_name(i);
+ bone_mapper_items.append(memnew(BoneMapperItem(bone_map, skeleton_bone_names, bn)));
+ mapper_item_vbox->add_child(bone_mapper_items[i]);
+ }
+ }
+
+ update_group_idx();
+ recreate_editor();
+}
+
+void BoneMapper::_update_state() {
+ int len = bone_mapper_buttons.size();
+ for (int i = 0; i < len; i++) {
+ StringName sbn = bone_map->get_skeleton_bone_name(bone_mapper_buttons[i]->get_profile_bone_name());
+ if (skeleton->find_bone(sbn) >= 0) {
+ if (bone_map->get_skeleton_bone_name_count(sbn) == 1) {
+ bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_SET);
+ } else {
+ bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_ERROR);
+ }
+ } else {
+ bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_UNSET);
+ }
+ }
+}
+
+void BoneMapper::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
+ set(p_property, p_value);
+ recreate_editor();
+}
+
+void BoneMapper::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_current_group_idx", "current_group_idx"), &BoneMapper::set_current_group_idx);
+ ClassDB::bind_method(D_METHOD("get_current_group_idx"), &BoneMapper::get_current_group_idx);
+ ClassDB::bind_method(D_METHOD("set_current_bone_idx", "current_bone_idx"), &BoneMapper::set_current_bone_idx);
+ ClassDB::bind_method(D_METHOD("get_current_bone_idx"), &BoneMapper::get_current_bone_idx);
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "current_group_idx"), "set_current_group_idx", "get_current_group_idx");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "current_bone_idx"), "set_current_bone_idx", "get_current_bone_idx");
+}
+
+void BoneMapper::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ create_editor();
+ bone_map->connect("bone_map_updated", callable_mp(this, &BoneMapper::_update_state));
+ bone_map->connect("profile_updated", callable_mp(this, &BoneMapper::recreate_items));
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ clear_items();
+ if (!bone_map.is_null()) {
+ if (bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapper::_update_state))) {
+ bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapper::_update_state));
+ }
+ if (bone_map->is_connected("profile_updated", callable_mp(this, &BoneMapper::recreate_items))) {
+ bone_map->disconnect("profile_updated", callable_mp(this, &BoneMapper::recreate_items));
+ }
+ }
+ }
+ }
+}
+
+BoneMapper::BoneMapper(Skeleton3D *p_skeleton, Ref<BoneMap> &p_bone_map) {
+ skeleton = p_skeleton;
+ bone_map = p_bone_map;
+}
+
+BoneMapper::~BoneMapper() {
+}
+
+void BoneMapEditor::create_editors() {
+ if (!skeleton) {
+ return;
+ }
+ bone_mapper = memnew(BoneMapper(skeleton, bone_map));
+ add_child(bone_mapper);
+}
+
+void BoneMapEditor::fetch_objects() {
+ // Hackey... but it may be the easist way to get a selected object from "ImporterScene".
+ SceneImportSettings *si = SceneImportSettings::get_singleton();
+ if (!si) {
+ return;
+ }
+ Node *selected = si->get_selected_node();
+ if (selected) {
+ Skeleton3D *sk = Object::cast_to<Skeleton3D>(selected);
+ if (!sk) {
+ return;
+ }
+ skeleton = sk;
+ } else {
+ // Editor should not exist.
+ skeleton = nullptr;
+ }
+}
+
+void BoneMapEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ fetch_objects();
+ create_editors();
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ remove_child(bone_mapper);
+ bone_mapper->queue_delete();
+ }
+ }
+}
+
+BoneMapEditor::BoneMapEditor(Ref<BoneMap> &p_bone_map) {
+ bone_map = p_bone_map;
+}
+
+BoneMapEditor::~BoneMapEditor() {
+}
+
+bool EditorInspectorPluginBoneMap::can_handle(Object *p_object) {
+ return Object::cast_to<BoneMap>(p_object) != nullptr;
+}
+
+void EditorInspectorPluginBoneMap::parse_begin(Object *p_object) {
+ BoneMap *bm = Object::cast_to<BoneMap>(p_object);
+ if (!bm) {
+ return;
+ }
+ Ref<BoneMap> r(bm);
+ editor = memnew(BoneMapEditor(r));
+ add_custom_control(editor);
+}
+
+BoneMapEditorPlugin::BoneMapEditorPlugin() {
+ // Register properties in editor settings.
+ EDITOR_DEF("editors/bone_mapper/handle_colors/set", Color(0.1, 0.6, 0.25));
+ EDITOR_DEF("editors/bone_mapper/handle_colors/error", Color(0.8, 0.2, 0.2));
+ EDITOR_DEF("editors/bone_mapper/handle_colors/unset", Color(0.3, 0.3, 0.3));
+
+ Ref<EditorInspectorPluginBoneMap> inspector_plugin;
+ inspector_plugin.instantiate();
+ add_inspector_plugin(inspector_plugin);
+
+ Ref<PostImportPluginSkeletonRenamer> post_import_plugin_renamer;
+ post_import_plugin_renamer.instantiate();
+ add_scene_post_import_plugin(post_import_plugin_renamer);
+}
diff --git a/editor/plugins/bone_map_editor_plugin.h b/editor/plugins/bone_map_editor_plugin.h
new file mode 100644
index 0000000000..0ec9f74373
--- /dev/null
+++ b/editor/plugins/bone_map_editor_plugin.h
@@ -0,0 +1,176 @@
+/*************************************************************************/
+/* bone_map_editor_plugin.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 BONE_MAP_EDITOR_H
+#define BONE_MAP_EDITOR_H
+
+#include "editor/editor_node.h"
+#include "editor/editor_plugin.h"
+#include "editor/editor_properties.h"
+#include "scene/3d/skeleton_3d.h"
+#include "scene/gui/color_rect.h"
+#include "scene/gui/dialogs.h"
+#include "scene/resources/bone_map.h"
+#include "scene/resources/texture.h"
+
+class BoneMapperButton : public TextureButton {
+ GDCLASS(BoneMapperButton, TextureButton);
+
+public:
+ enum BoneMapState {
+ BONE_MAP_STATE_UNSET,
+ BONE_MAP_STATE_SET,
+ BONE_MAP_STATE_ERROR
+ };
+
+private:
+ StringName profile_bone_name;
+ bool selected = false;
+
+ TextureRect *circle;
+
+ void fetch_textures();
+
+protected:
+ void _notification(int p_what);
+
+public:
+ StringName get_profile_bone_name() const;
+ void set_state(BoneMapState p_state);
+
+ BoneMapperButton(const StringName p_profile_bone_name, bool p_selected);
+ ~BoneMapperButton();
+};
+
+class BoneMapperItem : public VBoxContainer {
+ GDCLASS(BoneMapperItem, VBoxContainer);
+
+ int button_id = -1;
+ StringName profile_bone_name;
+
+ PackedStringArray skeleton_bone_names;
+ Ref<BoneMap> bone_map;
+
+ EditorPropertyTextEnum *skeleton_bone_selector;
+
+ void _update_property();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+ virtual void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing);
+ virtual void create_editor();
+
+public:
+ void assign_button_id(int p_button_id);
+
+ BoneMapperItem(Ref<BoneMap> &p_bone_map, PackedStringArray p_skeleton_bone_names, const StringName &p_profile_bone_name = StringName());
+ ~BoneMapperItem();
+};
+
+class BoneMapper : public VBoxContainer {
+ GDCLASS(BoneMapper, VBoxContainer);
+
+ Skeleton3D *skeleton;
+ Ref<BoneMap> bone_map;
+
+ Vector<BoneMapperItem *> bone_mapper_items;
+
+ VBoxContainer *mapper_item_vbox;
+ HSeparator *separator;
+
+ int current_group_idx = 0;
+ int current_bone_idx = -1;
+
+ AspectRatioContainer *bone_mapper_field;
+ EditorPropertyEnum *profile_group_selector;
+ ColorRect *profile_bg;
+ TextureRect *profile_texture;
+ Vector<BoneMapperButton *> bone_mapper_buttons;
+
+ void create_editor();
+ void recreate_editor();
+ void clear_items();
+ void recreate_items();
+ void update_group_idx();
+ void _update_state();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+ virtual void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing);
+
+public:
+ void set_current_group_idx(int p_group_idx);
+ int get_current_group_idx() const;
+ void set_current_bone_idx(int p_bone_idx);
+ int get_current_bone_idx() const;
+
+ BoneMapper(Skeleton3D *p_skeleton, Ref<BoneMap> &p_bone_map);
+ ~BoneMapper();
+};
+
+class BoneMapEditor : public VBoxContainer {
+ GDCLASS(BoneMapEditor, VBoxContainer);
+
+ Skeleton3D *skeleton;
+ Ref<BoneMap> bone_map;
+ BoneMapper *bone_mapper;
+
+ void fetch_objects();
+ void clear_editors();
+ void create_editors();
+
+protected:
+ void _notification(int p_what);
+
+public:
+ BoneMapEditor(Ref<BoneMap> &p_bone_map);
+ ~BoneMapEditor();
+};
+
+class EditorInspectorPluginBoneMap : public EditorInspectorPlugin {
+ GDCLASS(EditorInspectorPluginBoneMap, EditorInspectorPlugin);
+ BoneMapEditor *editor;
+
+public:
+ virtual bool can_handle(Object *p_object) override;
+ virtual void parse_begin(Object *p_object) override;
+};
+
+class BoneMapEditorPlugin : public EditorPlugin {
+ GDCLASS(BoneMapEditorPlugin, EditorPlugin);
+
+public:
+ virtual String get_name() const override { return "BoneMap"; }
+ BoneMapEditorPlugin();
+};
+
+#endif // BONE_MAP_EDITOR_H
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
index 40fb5f8788..9ae01016cb 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
@@ -170,6 +170,21 @@ namespace Godot
}
/// <summary>
+ /// Returns all names concatenated with a slash character (<c>/</c>).
+ /// </summary>
+ /// <example>
+ /// <code>
+ /// var nodepath = new NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path");
+ /// GD.Print(nodepath.GetConcatenatedNames()); // Path2D/PathFollow2D/Sprite2D
+ /// </code>
+ /// </example>
+ /// <returns>The names concatenated with <c>/</c>.</returns>
+ public string GetConcatenatedNames()
+ {
+ return godot_icall_NodePath_get_concatenated_names(GetPtr(this));
+ }
+
+ /// <summary>
/// Returns all subnames concatenated with a colon character (<c>:</c>)
/// as separator, i.e. the right side of the first colon in a node path.
/// </summary>
@@ -269,6 +284,9 @@ namespace Godot
private static extern IntPtr godot_icall_NodePath_get_as_property_path(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string godot_icall_NodePath_get_concatenated_names(IntPtr ptr);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
private static extern string godot_icall_NodePath_get_concatenated_subnames(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
diff --git a/modules/mono/glue/nodepath_glue.cpp b/modules/mono/glue/nodepath_glue.cpp
index 0ea9814b1a..16e1509eb0 100644
--- a/modules/mono/glue/nodepath_glue.cpp
+++ b/modules/mono/glue/nodepath_glue.cpp
@@ -68,6 +68,10 @@ MonoString *godot_icall_NodePath_get_subname(NodePath *p_ptr, uint32_t p_idx) {
return GDMonoMarshal::mono_string_from_godot(p_ptr->get_subname(p_idx));
}
+MonoString *godot_icall_NodePath_get_concatenated_names(NodePath *p_ptr) {
+ return GDMonoMarshal::mono_string_from_godot(p_ptr->get_concatenated_names());
+}
+
MonoString *godot_icall_NodePath_get_concatenated_subnames(NodePath *p_ptr) {
return GDMonoMarshal::mono_string_from_godot(p_ptr->get_concatenated_subnames());
}
@@ -85,6 +89,7 @@ void godot_register_nodepath_icalls() {
GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_Dtor", godot_icall_NodePath_Dtor);
GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_operator_String", godot_icall_NodePath_operator_String);
GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_as_property_path", godot_icall_NodePath_get_as_property_path);
+ GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_concatenated_names", godot_icall_NodePath_get_concatenated_names);
GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_concatenated_subnames", godot_icall_NodePath_get_concatenated_subnames);
GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name", godot_icall_NodePath_get_name);
GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name_count", godot_icall_NodePath_get_name_count);
diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp
index d0aeffb166..fbd5b5b65b 100644
--- a/scene/3d/bone_attachment_3d.cpp
+++ b/scene/3d/bone_attachment_3d.cpp
@@ -376,6 +376,24 @@ void BoneAttachment3D::on_bone_pose_update(int p_bone_index) {
}
}
}
+#ifdef TOOLS_ENABLED
+void BoneAttachment3D::_notify_skeleton_bones_renamed(Node *p_base_scene, Skeleton3D *p_skeleton, Ref<BoneMap> p_bone_map) {
+ const Skeleton3D *parent = nullptr;
+ if (use_external_skeleton) {
+ if (external_skeleton_node_cache.is_valid()) {
+ parent = Object::cast_to<Skeleton3D>(ObjectDB::get_instance(external_skeleton_node_cache));
+ }
+ } else {
+ parent = Object::cast_to<Skeleton3D>(get_parent());
+ }
+ if (parent && parent == p_skeleton) {
+ StringName bn = p_bone_map->find_profile_bone_name(bone_name);
+ if (bn) {
+ set_bone_name(bn);
+ }
+ }
+}
+#endif // TOOLS_ENABLED
BoneAttachment3D::BoneAttachment3D() {
}
@@ -398,6 +416,9 @@ void BoneAttachment3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_use_external_skeleton"), &BoneAttachment3D::get_use_external_skeleton);
ClassDB::bind_method(D_METHOD("set_external_skeleton", "external_skeleton"), &BoneAttachment3D::set_external_skeleton);
ClassDB::bind_method(D_METHOD("get_external_skeleton"), &BoneAttachment3D::get_external_skeleton);
+#ifdef TOOLS_ENABLED
+ ClassDB::bind_method(D_METHOD("_notify_skeleton_bones_renamed"), &BoneAttachment3D::_notify_skeleton_bones_renamed);
+#endif // TOOLS_ENABLED
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bone_name"), "set_bone_name", "get_bone_name");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_idx"), "set_bone_idx", "get_bone_idx");
diff --git a/scene/3d/bone_attachment_3d.h b/scene/3d/bone_attachment_3d.h
index 395dfde1d7..137360b141 100644
--- a/scene/3d/bone_attachment_3d.h
+++ b/scene/3d/bone_attachment_3d.h
@@ -32,6 +32,9 @@
#define BONE_ATTACHMENT_H
#include "scene/3d/skeleton_3d.h"
+#ifdef TOOLS_ENABLED
+#include "scene/resources/bone_map.h"
+#endif // TOOLS_ENABLED
class BoneAttachment3D : public Node3D {
GDCLASS(BoneAttachment3D, Node3D);
@@ -68,6 +71,9 @@ protected:
void _notification(int p_what);
static void _bind_methods();
+#ifdef TOOLS_ENABLED
+ virtual void _notify_skeleton_bones_renamed(Node *p_base_scene, Skeleton3D *p_skeleton, Ref<BoneMap> p_bone_map);
+#endif // TOOLS_ENABLED
public:
virtual TypedArray<String> get_configuration_warnings() const override;
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index f70d57291f..4d0d6111ec 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -143,6 +143,7 @@
#include "scene/resources/animation_library.h"
#include "scene/resources/audio_stream_sample.h"
#include "scene/resources/bit_map.h"
+#include "scene/resources/bone_map.h"
#include "scene/resources/box_shape_3d.h"
#include "scene/resources/camera_effects.h"
#include "scene/resources/capsule_shape_2d.h"
@@ -189,6 +190,7 @@
#include "scene/resources/skeleton_modification_3d_twoboneik.h"
#include "scene/resources/skeleton_modification_stack_2d.h"
#include "scene/resources/skeleton_modification_stack_3d.h"
+#include "scene/resources/skeleton_profile.h"
#include "scene/resources/sky.h"
#include "scene/resources/sky_material.h"
#include "scene/resources/sphere_shape_3d.h"
@@ -871,6 +873,10 @@ void register_scene_types() {
GDREGISTER_CLASS(BitMap);
GDREGISTER_CLASS(Gradient);
+ GDREGISTER_CLASS(SkeletonProfile);
+ GDREGISTER_CLASS(SkeletonProfileHumanoid);
+ GDREGISTER_CLASS(BoneMap);
+
OS::get_singleton()->yield(); // may take time to init
GDREGISTER_CLASS(AudioStreamPlayer);
diff --git a/scene/resources/bone_map.cpp b/scene/resources/bone_map.cpp
new file mode 100644
index 0000000000..ce030934fa
--- /dev/null
+++ b/scene/resources/bone_map.cpp
@@ -0,0 +1,172 @@
+/*************************************************************************/
+/* bone_map.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 "bone_map.h"
+
+bool BoneMap::_set(const StringName &p_path, const Variant &p_value) {
+ String path = p_path;
+ if (path.begins_with("bone_map/")) {
+ String which = path.get_slicec('/', 1);
+ set_skeleton_bone_name(which, p_value);
+ return true;
+ }
+ return true;
+}
+
+bool BoneMap::_get(const StringName &p_path, Variant &r_ret) const {
+ String path = p_path;
+ if (path.begins_with("bone_map/")) {
+ String which = path.get_slicec('/', 1);
+ r_ret = get_skeleton_bone_name(which);
+ return true;
+ }
+ return true;
+}
+
+Ref<SkeletonProfile> BoneMap::get_profile() const {
+ return profile;
+}
+
+void BoneMap::set_profile(const Ref<SkeletonProfile> &p_profile) {
+ bool is_changed = profile != p_profile;
+ if (is_changed) {
+ if (!profile.is_null() && profile->is_connected("profile_updated", callable_mp(this, &BoneMap::_update_profile))) {
+ profile->disconnect("profile_updated", callable_mp(this, &BoneMap::_update_profile));
+ }
+ profile = p_profile;
+ if (!profile.is_null()) {
+ profile->connect("profile_updated", callable_mp(this, &BoneMap::_update_profile));
+ }
+ _update_profile();
+ }
+ notify_property_list_changed();
+}
+
+StringName BoneMap::get_skeleton_bone_name(StringName p_profile_bone_name) const {
+ ERR_FAIL_COND_V(!bone_map.has(p_profile_bone_name), StringName());
+ return bone_map.get(p_profile_bone_name);
+}
+
+void BoneMap::set_skeleton_bone_name(StringName p_profile_bone_name, const StringName p_skeleton_bone_name) {
+ ERR_FAIL_COND(!bone_map.has(p_profile_bone_name));
+ bone_map.insert(p_profile_bone_name, p_skeleton_bone_name);
+ emit_signal("bone_map_updated");
+}
+
+StringName BoneMap::find_profile_bone_name(StringName p_skeleton_bone_name) const {
+ StringName profile_bone_name = StringName();
+ HashMap<StringName, StringName>::ConstIterator E = bone_map.begin();
+ while (E) {
+ if (E->value == p_skeleton_bone_name) {
+ profile_bone_name = E->key;
+ break;
+ }
+ ++E;
+ }
+ return profile_bone_name;
+}
+
+int BoneMap::get_skeleton_bone_name_count(const StringName p_skeleton_bone_name) const {
+ int count = 0;
+ HashMap<StringName, StringName>::ConstIterator E = bone_map.begin();
+ while (E) {
+ if (E->value == p_skeleton_bone_name) {
+ ++count;
+ }
+ ++E;
+ }
+ return count;
+}
+
+void BoneMap::_update_profile() {
+ _validate_bone_map();
+ emit_signal("profile_updated");
+}
+
+void BoneMap::_validate_bone_map() {
+ Ref<SkeletonProfile> current_profile = get_profile();
+ if (current_profile.is_valid()) {
+ // Insert missing profile bones into bone map.
+ int len = current_profile->get_bone_size();
+ StringName profile_bone_name;
+ for (int i = 0; i < len; i++) {
+ profile_bone_name = current_profile->get_bone_name(i);
+ if (!bone_map.has(profile_bone_name)) {
+ bone_map.insert(profile_bone_name, StringName());
+ }
+ }
+ // Remove bones that do not exist in the profile from the map.
+ Vector<StringName> delete_bones;
+ StringName k;
+ HashMap<StringName, StringName>::ConstIterator E = bone_map.begin();
+ while (E) {
+ k = E->key;
+ if (!current_profile->has_bone(k)) {
+ delete_bones.push_back(k);
+ }
+ ++E;
+ }
+ len = delete_bones.size();
+ for (int i = 0; i < len; i++) {
+ bone_map.erase(delete_bones[i]);
+ }
+ } else {
+ bone_map.clear();
+ }
+ emit_signal("retarget_option_updated");
+}
+
+void BoneMap::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_profile"), &BoneMap::get_profile);
+ ClassDB::bind_method(D_METHOD("set_profile", "profile"), &BoneMap::set_profile);
+
+ ClassDB::bind_method(D_METHOD("get_skeleton_bone_name", "profile_bone_name"), &BoneMap::get_skeleton_bone_name);
+ ClassDB::bind_method(D_METHOD("set_skeleton_bone_name", "profile_bone_name", "skeleton_bone_name"), &BoneMap::set_skeleton_bone_name);
+
+ ClassDB::bind_method(D_METHOD("find_profile_bone_name", "skeleton_bone_name"), &BoneMap::find_profile_bone_name);
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "profile", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonProfile"), "set_profile", "get_profile");
+
+ ADD_SIGNAL(MethodInfo("bone_map_updated"));
+ ADD_SIGNAL(MethodInfo("profile_updated"));
+}
+
+void BoneMap::_validate_property(PropertyInfo &property) const {
+ //
+}
+
+BoneMap::BoneMap() {
+ _validate_bone_map();
+}
+
+BoneMap::~BoneMap() {
+}
+
+//////////////////////////////////////
diff --git a/scene/resources/bone_map.h b/scene/resources/bone_map.h
new file mode 100644
index 0000000000..4b7928015d
--- /dev/null
+++ b/scene/resources/bone_map.h
@@ -0,0 +1,69 @@
+/*************************************************************************/
+/* bone_map.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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 BONE_MAP_H
+#define BONE_MAP_H
+
+#include "skeleton_profile.h"
+
+class BoneMap : public Resource {
+ GDCLASS(BoneMap, Resource);
+
+ Ref<SkeletonProfile> profile;
+ HashMap<StringName, StringName> bone_map;
+
+ void _update_profile();
+ void _validate_bone_map();
+
+protected:
+ bool _get(const StringName &p_path, Variant &r_ret) const;
+ bool _set(const StringName &p_path, const Variant &p_value);
+ virtual void _validate_property(PropertyInfo &property) const override;
+ static void _bind_methods();
+
+public:
+ int get_profile_type() const;
+ void set_profile_type(const int p_profile_type);
+
+ Ref<SkeletonProfile> get_profile() const;
+ void set_profile(const Ref<SkeletonProfile> &p_profile);
+
+ int get_skeleton_bone_name_count(const StringName p_skeleton_bone_name) const;
+
+ StringName get_skeleton_bone_name(StringName p_profile_bone_name) const;
+ void set_skeleton_bone_name(StringName p_profile_bone_name, const StringName p_skeleton_bone_name);
+
+ StringName find_profile_bone_name(StringName p_skeleton_bone_name) const;
+
+ BoneMap();
+ ~BoneMap();
+};
+
+#endif // BONE_MAP_H
diff --git a/scene/resources/skeleton_profile.cpp b/scene/resources/skeleton_profile.cpp
new file mode 100644
index 0000000000..05d48f9545
--- /dev/null
+++ b/scene/resources/skeleton_profile.cpp
@@ -0,0 +1,514 @@
+/*************************************************************************/
+/* skeleton_profile.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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_profile.h"
+
+bool SkeletonProfile::_set(const StringName &p_path, const Variant &p_value) {
+ ERR_FAIL_COND_V(is_read_only, false);
+ String path = p_path;
+
+ if (path.begins_with("group/")) {
+ int which = path.get_slicec('/', 1).to_int();
+ String what = path.get_slicec('/', 2);
+ ERR_FAIL_INDEX_V(which, groups.size(), false);
+
+ if (what == "group_name") {
+ set_group_name(which, p_value);
+ } else if (what == "texture") {
+ set_texture(which, p_value);
+ }
+ return true;
+ }
+
+ if (path.begins_with("bone/")) {
+ int which = path.get_slicec('/', 1).to_int();
+ String what = path.get_slicec('/', 2);
+ ERR_FAIL_INDEX_V(which, bones.size(), false);
+
+ if (what == "bone_name") {
+ set_bone_name(which, p_value);
+ } else if (what == "handle_offset") {
+ set_handle_offset(which, p_value);
+ } else if (what == "group") {
+ set_group(which, p_value);
+ }
+ return true;
+ }
+ return true;
+}
+
+bool SkeletonProfile::_get(const StringName &p_path, Variant &r_ret) const {
+ String path = p_path;
+
+ if (path.begins_with("group/")) {
+ int which = path.get_slicec('/', 1).to_int();
+ String what = path.get_slicec('/', 2);
+ ERR_FAIL_INDEX_V(which, groups.size(), false);
+
+ if (what == "group_name") {
+ r_ret = get_group_name(which);
+ } else if (what == "texture") {
+ r_ret = get_texture(which);
+ }
+ return true;
+ }
+
+ if (path.begins_with("bone/")) {
+ int which = path.get_slicec('/', 1).to_int();
+ String what = path.get_slicec('/', 2);
+ ERR_FAIL_INDEX_V(which, bones.size(), false);
+
+ if (what == "bone_name") {
+ r_ret = get_bone_name(which);
+ } else if (what == "handle_offset") {
+ r_ret = get_handle_offset(which);
+ } else if (what == "group") {
+ r_ret = get_group(which);
+ }
+ return true;
+ }
+ return true;
+}
+
+void SkeletonProfile::_validate_property(PropertyInfo &property) const {
+ if (is_read_only) {
+ if (property.name == ("group_size") || property.name == ("bone_size")) {
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
+ return;
+ }
+ }
+}
+
+void SkeletonProfile::_get_property_list(List<PropertyInfo> *p_list) const {
+ if (is_read_only) {
+ return;
+ }
+ String group_names = "";
+ for (int i = 0; i < groups.size(); i++) {
+ String path = "group/" + itos(i) + "/";
+ p_list->push_back(PropertyInfo(Variant::STRING_NAME, path + "group_name"));
+ p_list->push_back(PropertyInfo(Variant::OBJECT, path + "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"));
+ if (i > 0) {
+ group_names = group_names + ",";
+ }
+ group_names = group_names + groups[i].group_name;
+ }
+ for (int i = 0; i < bones.size(); i++) {
+ String path = "bone/" + itos(i) + "/";
+ p_list->push_back(PropertyInfo(Variant::STRING_NAME, path + "bone_name"));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2, path + "handle_offset"));
+ p_list->push_back(PropertyInfo(Variant::STRING_NAME, path + "group", PROPERTY_HINT_ENUM, group_names));
+ }
+}
+
+int SkeletonProfile::get_group_size() {
+ return groups.size();
+}
+
+void SkeletonProfile::set_group_size(int p_size) {
+ if (is_read_only) {
+ return;
+ }
+ ERR_FAIL_COND(p_size < 0);
+ groups.resize(p_size);
+ emit_signal("profile_updated");
+ notify_property_list_changed();
+}
+
+StringName SkeletonProfile::get_group_name(int p_group_idx) const {
+ ERR_FAIL_INDEX_V(p_group_idx, groups.size(), StringName());
+ return groups[p_group_idx].group_name;
+}
+
+void SkeletonProfile::set_group_name(int p_group_idx, const StringName p_group_name) {
+ if (is_read_only) {
+ return;
+ }
+ ERR_FAIL_INDEX(p_group_idx, groups.size());
+ groups.write[p_group_idx].group_name = p_group_name;
+ emit_signal("profile_updated");
+}
+
+Ref<Texture2D> SkeletonProfile::get_texture(int p_group_idx) const {
+ ERR_FAIL_INDEX_V(p_group_idx, groups.size(), Ref<Texture2D>());
+ return groups[p_group_idx].texture;
+}
+
+void SkeletonProfile::set_texture(int p_group_idx, const Ref<Texture2D> &p_texture) {
+ if (is_read_only) {
+ return;
+ }
+ ERR_FAIL_INDEX(p_group_idx, groups.size());
+ groups.write[p_group_idx].texture = p_texture;
+ emit_signal("profile_updated");
+}
+
+int SkeletonProfile::get_bone_size() {
+ return bones.size();
+}
+
+void SkeletonProfile::set_bone_size(int p_size) {
+ if (is_read_only) {
+ return;
+ }
+ ERR_FAIL_COND(p_size < 0);
+ bones.resize(p_size);
+ emit_signal("profile_updated");
+ notify_property_list_changed();
+}
+
+StringName SkeletonProfile::get_bone_name(int p_bone_idx) const {
+ ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), StringName());
+ return bones[p_bone_idx].bone_name;
+}
+
+void SkeletonProfile::set_bone_name(int p_bone_idx, const StringName p_bone_name) {
+ if (is_read_only) {
+ return;
+ }
+ ERR_FAIL_INDEX(p_bone_idx, bones.size());
+ bones.write[p_bone_idx].bone_name = p_bone_name;
+ emit_signal("profile_updated");
+}
+
+Vector2 SkeletonProfile::get_handle_offset(int p_bone_idx) const {
+ ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), Vector2());
+ return bones[p_bone_idx].handle_offset;
+}
+
+void SkeletonProfile::set_handle_offset(int p_bone_idx, const Vector2 p_handle_offset) {
+ if (is_read_only) {
+ return;
+ }
+ ERR_FAIL_INDEX(p_bone_idx, bones.size());
+ bones.write[p_bone_idx].handle_offset = p_handle_offset;
+ emit_signal("profile_updated");
+}
+
+StringName SkeletonProfile::get_group(int p_bone_idx) const {
+ ERR_FAIL_INDEX_V(p_bone_idx, bones.size(), StringName());
+ return bones[p_bone_idx].group;
+}
+
+void SkeletonProfile::set_group(int p_bone_idx, const StringName p_group) {
+ if (is_read_only) {
+ return;
+ }
+ ERR_FAIL_INDEX(p_bone_idx, bones.size());
+ bones.write[p_bone_idx].group = p_group;
+ emit_signal("profile_updated");
+}
+
+bool SkeletonProfile::has_bone(StringName p_bone_name) {
+ bool is_found = false;
+ for (int i = 0; i < bones.size(); i++) {
+ if (bones[i].bone_name == p_bone_name) {
+ is_found = true;
+ break;
+ }
+ }
+ return is_found;
+}
+
+void SkeletonProfile::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_group_size", "size"), &SkeletonProfile::set_group_size);
+ ClassDB::bind_method(D_METHOD("get_group_size"), &SkeletonProfile::get_group_size);
+
+ ClassDB::bind_method(D_METHOD("get_group_name", "group_idx"), &SkeletonProfile::get_group_name);
+ ClassDB::bind_method(D_METHOD("set_group_name", "group_idx", "group_name"), &SkeletonProfile::set_group_name);
+
+ ClassDB::bind_method(D_METHOD("get_texture", "group_idx"), &SkeletonProfile::get_texture);
+ ClassDB::bind_method(D_METHOD("set_texture", "group_idx", "texture"), &SkeletonProfile::set_texture);
+
+ ClassDB::bind_method(D_METHOD("set_bone_size", "size"), &SkeletonProfile::set_bone_size);
+ ClassDB::bind_method(D_METHOD("get_bone_size"), &SkeletonProfile::get_bone_size);
+
+ ClassDB::bind_method(D_METHOD("get_bone_name", "bone_idx"), &SkeletonProfile::get_bone_name);
+ ClassDB::bind_method(D_METHOD("set_bone_name", "bone_idx", "bone_name"), &SkeletonProfile::set_bone_name);
+
+ ClassDB::bind_method(D_METHOD("get_handle_offset", "bone_idx"), &SkeletonProfile::get_handle_offset);
+ ClassDB::bind_method(D_METHOD("set_handle_offset", "bone_idx", "handle_offset"), &SkeletonProfile::set_handle_offset);
+
+ ClassDB::bind_method(D_METHOD("get_group", "bone_idx"), &SkeletonProfile::get_group);
+ ClassDB::bind_method(D_METHOD("set_group", "bone_idx", "group"), &SkeletonProfile::set_group);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "group_size", PROPERTY_HINT_RANGE, "0,100,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Groups,group/"), "set_group_size", "get_group_size");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_size", PROPERTY_HINT_RANGE, "0,100,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Bones,bone/"), "set_bone_size", "get_bone_size");
+
+ ADD_SIGNAL(MethodInfo("profile_updated"));
+}
+
+SkeletonProfile::SkeletonProfile() {
+}
+
+SkeletonProfile::~SkeletonProfile() {
+}
+
+SkeletonProfileHumanoid::SkeletonProfileHumanoid() {
+ is_read_only = true;
+
+ groups.resize(4);
+
+ groups.write[0].group_name = "Body";
+ groups.write[1].group_name = "Face";
+ groups.write[2].group_name = "LeftHand";
+ groups.write[3].group_name = "RightHand";
+
+ bones.resize(56);
+
+ bones.write[0].bone_name = "Root";
+ bones.write[0].handle_offset = Vector2(0.5, 0.91);
+ bones.write[0].group = "Body";
+
+ bones.write[1].bone_name = "Hips";
+ bones.write[1].handle_offset = Vector2(0.5, 0.5);
+ bones.write[1].group = "Body";
+
+ bones.write[2].bone_name = "Spine";
+ bones.write[2].handle_offset = Vector2(0.5, 0.43);
+ bones.write[2].group = "Body";
+
+ bones.write[3].bone_name = "Chest";
+ bones.write[3].handle_offset = Vector2(0.5, 0.36);
+ bones.write[3].group = "Body";
+
+ bones.write[4].bone_name = "UpperChest";
+ bones.write[4].handle_offset = Vector2(0.5, 0.29);
+ bones.write[4].group = "Body";
+
+ bones.write[5].bone_name = "Neck";
+ bones.write[5].handle_offset = Vector2(0.5, 0.23);
+ bones.write[5].group = "Body";
+
+ bones.write[6].bone_name = "Head";
+ bones.write[6].handle_offset = Vector2(0.5, 0.18);
+ bones.write[6].group = "Body";
+
+ bones.write[7].bone_name = "LeftEye";
+ bones.write[7].handle_offset = Vector2(0.6, 0.46);
+ bones.write[7].group = "Face";
+
+ bones.write[8].bone_name = "RightEye";
+ bones.write[8].handle_offset = Vector2(0.37, 0.46);
+ bones.write[8].group = "Face";
+
+ bones.write[9].bone_name = "Jaw";
+ bones.write[9].handle_offset = Vector2(0.46, 0.75);
+ bones.write[9].group = "Face";
+
+ bones.write[10].bone_name = "LeftShoulder";
+ bones.write[10].handle_offset = Vector2(0.55, 0.235);
+ bones.write[10].group = "Body";
+
+ bones.write[11].bone_name = "LeftUpperArm";
+ bones.write[11].handle_offset = Vector2(0.6, 0.24);
+ bones.write[11].group = "Body";
+
+ bones.write[12].bone_name = "LeftLowerArm";
+ bones.write[12].handle_offset = Vector2(0.7, 0.24);
+ bones.write[12].group = "Body";
+
+ bones.write[13].bone_name = "LeftHand";
+ bones.write[13].handle_offset = Vector2(0.82, 0.235);
+ bones.write[13].group = "Body";
+
+ bones.write[14].bone_name = "LeftThumbProximal";
+ bones.write[14].handle_offset = Vector2(0.4, 0.8);
+ bones.write[14].group = "LeftHand";
+
+ bones.write[15].bone_name = "LeftThumbIntermediate";
+ bones.write[15].handle_offset = Vector2(0.3, 0.69);
+ bones.write[15].group = "LeftHand";
+
+ bones.write[16].bone_name = "LeftThumbDistal";
+ bones.write[16].handle_offset = Vector2(0.23, 0.555);
+ bones.write[16].group = "LeftHand";
+
+ bones.write[17].bone_name = "LeftIndexProximal";
+ bones.write[17].handle_offset = Vector2(0.413, 0.52);
+ bones.write[17].group = "LeftHand";
+
+ bones.write[18].bone_name = "LeftIndexIntermediate";
+ bones.write[18].handle_offset = Vector2(0.403, 0.36);
+ bones.write[18].group = "LeftHand";
+
+ bones.write[19].bone_name = "LeftIndexDistal";
+ bones.write[19].handle_offset = Vector2(0.403, 0.255);
+ bones.write[19].group = "LeftHand";
+
+ bones.write[20].bone_name = "LeftMiddleProximal";
+ bones.write[20].handle_offset = Vector2(0.5, 0.51);
+ bones.write[20].group = "LeftHand";
+
+ bones.write[21].bone_name = "LeftMiddleIntermediate";
+ bones.write[21].handle_offset = Vector2(0.5, 0.345);
+ bones.write[21].group = "LeftHand";
+
+ bones.write[22].bone_name = "LeftMiddleDistal";
+ bones.write[22].handle_offset = Vector2(0.5, 0.22);
+ bones.write[22].group = "LeftHand";
+
+ bones.write[23].bone_name = "LeftRingProximal";
+ bones.write[23].handle_offset = Vector2(0.586, 0.52);
+ bones.write[23].group = "LeftHand";
+
+ bones.write[24].bone_name = "LeftRingIntermediate";
+ bones.write[24].handle_offset = Vector2(0.59, 0.36);
+ bones.write[24].group = "LeftHand";
+
+ bones.write[25].bone_name = "LeftRingDistal";
+ bones.write[25].handle_offset = Vector2(0.591, 0.25);
+ bones.write[25].group = "LeftHand";
+
+ bones.write[26].bone_name = "LeftLittleProximal";
+ bones.write[26].handle_offset = Vector2(0.663, 0.543);
+ bones.write[26].group = "LeftHand";
+
+ bones.write[27].bone_name = "LeftLittleIntermediate";
+ bones.write[27].handle_offset = Vector2(0.672, 0.415);
+ bones.write[27].group = "LeftHand";
+
+ bones.write[28].bone_name = "LeftLittleDistal";
+ bones.write[28].handle_offset = Vector2(0.672, 0.32);
+ bones.write[28].group = "LeftHand";
+
+ bones.write[29].bone_name = "RightShoulder";
+ bones.write[29].handle_offset = Vector2(0.45, 0.235);
+ bones.write[29].group = "Body";
+
+ bones.write[30].bone_name = "RightUpperArm";
+ bones.write[30].handle_offset = Vector2(0.4, 0.24);
+ bones.write[30].group = "Body";
+
+ bones.write[31].bone_name = "RightLowerArm";
+ bones.write[31].handle_offset = Vector2(0.3, 0.24);
+ bones.write[31].group = "Body";
+
+ bones.write[32].bone_name = "RightHand";
+ bones.write[32].handle_offset = Vector2(0.18, 0.235);
+ bones.write[32].group = "Body";
+
+ bones.write[33].bone_name = "RightThumbProximal";
+ bones.write[33].handle_offset = Vector2(0.6, 0.8);
+ bones.write[33].group = "RightHand";
+
+ bones.write[34].bone_name = "RightThumbIntermediate";
+ bones.write[34].handle_offset = Vector2(0.7, 0.69);
+ bones.write[34].group = "RightHand";
+
+ bones.write[35].bone_name = "RightThumbDistal";
+ bones.write[35].handle_offset = Vector2(0.77, 0.555);
+ bones.write[35].group = "RightHand";
+
+ bones.write[36].bone_name = "RightIndexProximal";
+ bones.write[36].handle_offset = Vector2(0.587, 0.52);
+ bones.write[36].group = "RightHand";
+
+ bones.write[37].bone_name = "RightIndexIntermediate";
+ bones.write[37].handle_offset = Vector2(0.597, 0.36);
+ bones.write[37].group = "RightHand";
+
+ bones.write[38].bone_name = "RightIndexDistal";
+ bones.write[38].handle_offset = Vector2(0.597, 0.255);
+ bones.write[38].group = "RightHand";
+
+ bones.write[39].bone_name = "RightMiddleProximal";
+ bones.write[39].handle_offset = Vector2(0.5, 0.51);
+ bones.write[39].group = "RightHand";
+
+ bones.write[40].bone_name = "RightMiddleIntermediate";
+ bones.write[40].handle_offset = Vector2(0.5, 0.345);
+ bones.write[40].group = "RightHand";
+
+ bones.write[41].bone_name = "RightMiddleDistal";
+ bones.write[41].handle_offset = Vector2(0.5, 0.22);
+ bones.write[41].group = "RightHand";
+
+ bones.write[42].bone_name = "RightRingProximal";
+ bones.write[42].handle_offset = Vector2(0.414, 0.52);
+ bones.write[42].group = "RightHand";
+
+ bones.write[43].bone_name = "RightRingIntermediate";
+ bones.write[43].handle_offset = Vector2(0.41, 0.36);
+ bones.write[43].group = "RightHand";
+
+ bones.write[44].bone_name = "RightRingDistal";
+ bones.write[44].handle_offset = Vector2(0.409, 0.25);
+ bones.write[44].group = "RightHand";
+
+ bones.write[45].bone_name = "RightLittleProximal";
+ bones.write[45].handle_offset = Vector2(0.337, 0.543);
+ bones.write[45].group = "RightHand";
+
+ bones.write[46].bone_name = "RightLittleIntermediate";
+ bones.write[46].handle_offset = Vector2(0.328, 0.415);
+ bones.write[46].group = "RightHand";
+
+ bones.write[47].bone_name = "RightLittleDistal";
+ bones.write[47].handle_offset = Vector2(0.328, 0.32);
+ bones.write[47].group = "RightHand";
+
+ bones.write[48].bone_name = "LeftUpperLeg";
+ bones.write[48].handle_offset = Vector2(0.549, 0.49);
+ bones.write[48].group = "Body";
+
+ bones.write[49].bone_name = "LeftLowerLeg";
+ bones.write[49].handle_offset = Vector2(0.548, 0.683);
+ bones.write[49].group = "Body";
+
+ bones.write[50].bone_name = "LeftFoot";
+ bones.write[50].handle_offset = Vector2(0.545, 0.9);
+ bones.write[50].group = "Body";
+
+ bones.write[51].bone_name = "LeftToes";
+ bones.write[51].handle_offset = Vector2(0.545, 0.95);
+ bones.write[51].group = "Body";
+
+ bones.write[52].bone_name = "RightUpperLeg";
+ bones.write[52].handle_offset = Vector2(0.451, 0.49);
+ bones.write[52].group = "Body";
+
+ bones.write[53].bone_name = "RightLowerLeg";
+ bones.write[53].handle_offset = Vector2(0.452, 0.683);
+ bones.write[53].group = "Body";
+
+ bones.write[54].bone_name = "RightFoot";
+ bones.write[54].handle_offset = Vector2(0.455, 0.9);
+ bones.write[54].group = "Body";
+
+ bones.write[55].bone_name = "RightToes";
+ bones.write[55].handle_offset = Vector2(0.455, 0.95);
+ bones.write[55].group = "Body";
+}
+
+SkeletonProfileHumanoid::~SkeletonProfileHumanoid() {
+}
+
+//////////////////////////////////////
diff --git a/scene/resources/skeleton_profile.h b/scene/resources/skeleton_profile.h
new file mode 100644
index 0000000000..920aaa2b8d
--- /dev/null
+++ b/scene/resources/skeleton_profile.h
@@ -0,0 +1,100 @@
+/*************************************************************************/
+/* skeleton_profile.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 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_PROFILE_H
+#define SKELETON_PROFILE_H
+
+#include "texture.h"
+
+class SkeletonProfile : public Resource {
+ GDCLASS(SkeletonProfile, Resource);
+
+protected:
+ // Note: SkeletonProfileHumanoid which extends SkeletonProfile exists to unify standard bone names.
+ // That is what is_read_only is for, so don't make it public.
+ bool is_read_only = false;
+
+ struct SkeletonProfileGroup {
+ StringName group_name;
+ Ref<Texture2D> texture;
+ };
+
+ struct SkeletonProfileBone {
+ StringName bone_name;
+ Vector2 handle_offset;
+ StringName group;
+ };
+
+ Vector<SkeletonProfileGroup> groups;
+ Vector<SkeletonProfileBone> bones;
+
+ bool _get(const StringName &p_path, Variant &r_ret) const;
+ bool _set(const StringName &p_path, const Variant &p_value);
+ virtual void _validate_property(PropertyInfo &property) const override;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+ static void _bind_methods();
+
+public:
+ int get_group_size();
+ void set_group_size(int p_size);
+
+ StringName get_group_name(int p_group_idx) const;
+ void set_group_name(int p_group_idx, const StringName p_group_name);
+
+ Ref<Texture2D> get_texture(int p_group_idx) const;
+ void set_texture(int p_group_idx, const Ref<Texture2D> &p_texture);
+
+ int get_bone_size();
+ void set_bone_size(int p_size);
+
+ StringName get_bone_name(int p_bone_idx) const;
+ void set_bone_name(int p_bone_idx, const StringName p_bone_name);
+
+ Vector2 get_handle_offset(int p_bone_idx) const;
+ void set_handle_offset(int p_bone_idx, const Vector2 p_handle_offset);
+
+ StringName get_group(int p_bone_idx) const;
+ void set_group(int p_bone_idx, const StringName p_group);
+
+ bool has_bone(StringName p_bone_name);
+
+ SkeletonProfile();
+ ~SkeletonProfile();
+};
+
+class SkeletonProfileHumanoid : public SkeletonProfile {
+ GDCLASS(SkeletonProfileHumanoid, SkeletonProfile);
+
+public:
+ SkeletonProfileHumanoid();
+ ~SkeletonProfileHumanoid();
+};
+
+#endif // SKELETON_PROFILE_H