diff options
46 files changed, 2049 insertions, 36 deletions
diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index 3df4409041..0c6a140e28 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -127,6 +127,8 @@ jobs: - name: Unit tests if: ${{ matrix.tests }} run: | + ${{ matrix.bin }} --version + ${{ matrix.bin }} --help ${{ matrix.bin }} --test --headless # Check class reference diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml index 68623f2770..fab8e467e8 100644 --- a/.github/workflows/macos_builds.yml +++ b/.github/workflows/macos_builds.yml @@ -58,6 +58,8 @@ jobs: - name: Unit tests if: ${{ matrix.tests }} run: | + ${{ matrix.bin }} --version + ${{ matrix.bin }} --help ${{ matrix.bin }} --test - name: Prepare artifact diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index 4919f34cf0..e04d49adde 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -64,6 +64,8 @@ jobs: - name: Unit tests if: ${{ matrix.tests }} run: | + ${{ matrix.bin }} --version + ${{ matrix.bin }} --help ${{ matrix.bin }} --test - name: Prepare artifact 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/main/main.cpp b/main/main.cpp index a6e304cafd..f7c192001b 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -691,12 +691,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph if (I->get() == "-h" || I->get() == "--help" || I->get() == "/?") { // display help show_help = true; - exit_code = OK; + exit_code = ERR_HELP; // Hack to force an early exit in `main()` with a success code. goto error; } else if (I->get() == "--version") { print_line(get_full_version_string()); - exit_code = OK; + exit_code = ERR_HELP; // Hack to force an early exit in `main()` with a success code. goto error; } else if (I->get() == "-v" || I->get() == "--verbose") { // verbose output 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/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index eaffe14b13..6e716c34a6 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -157,6 +157,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jc memfree(cmdline); } + // Note: --help and --version return ERR_HELP, but this should be translated to 0 if exit codes are propagated. if (err != OK) { return; // should exit instead and print the error } diff --git a/platform/iphone/godot_iphone.mm b/platform/iphone/godot_iphone.mm index 49474ef554..59fdfa9dcd 100644 --- a/platform/iphone/godot_iphone.mm +++ b/platform/iphone/godot_iphone.mm @@ -112,7 +112,10 @@ int iphone_main(int argc, char **argv, String data_dir, String cache_dir) { Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false); printf("setup %i\n", err); - if (err != OK) { + + if (err == ERR_HELP) { // Returned by --help and --version, so success. + return 0; + } else if (err != OK) { return 255; } diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index a96c539a1f..4edd6c793a 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -236,7 +236,7 @@ void DisplayServerJavaScript::mouse_move_callback(double p_x, double p_y, double const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape p_shape) { switch (p_shape) { case DisplayServer::CURSOR_ARROW: - return "auto"; + return "default"; case DisplayServer::CURSOR_IBEAM: return "text"; case DisplayServer::CURSOR_POINTING_HAND: @@ -270,7 +270,7 @@ const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape case DisplayServer::CURSOR_HELP: return "help"; default: - return "auto"; + return "default"; } } diff --git a/platform/linuxbsd/godot_linuxbsd.cpp b/platform/linuxbsd/godot_linuxbsd.cpp index 9fe00568fb..91a260182e 100644 --- a/platform/linuxbsd/godot_linuxbsd.cpp +++ b/platform/linuxbsd/godot_linuxbsd.cpp @@ -61,6 +61,10 @@ int main(int argc, char *argv[]) { Error err = Main::setup(argv[0], argc - 1, &argv[1]); if (err != OK) { free(cwd); + + if (err == ERR_HELP) { // Returned by --help and --version, so success. + return 0; + } return 255; } diff --git a/platform/osx/export/export_plugin.cpp b/platform/osx/export/export_plugin.cpp index 7010709123..00a7e54131 100644 --- a/platform/osx/export/export_plugin.cpp +++ b/platform/osx/export/export_plugin.cpp @@ -1086,6 +1086,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p Ref<FileAccess> f = FileAccess::open(file, FileAccess::WRITE); if (f.is_valid()) { f->store_buffer(data.ptr(), data.size()); + f.unref(); if (is_execute) { // chmod with 0755 if the file is executable. FileAccess::set_unix_permissions(file, 0755); diff --git a/platform/osx/godot_main_osx.mm b/platform/osx/godot_main_osx.mm index 053a7f4a1d..354edca096 100644 --- a/platform/osx/godot_main_osx.mm +++ b/platform/osx/godot_main_osx.mm @@ -83,7 +83,9 @@ int main(int argc, char **argv) { err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]); } - if (err != OK) { + if (err == ERR_HELP) { // Returned by --help and --version, so success. + return 0; + } else if (err != OK) { return 255; } diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp index 8de3ef294a..72920d2816 100644 --- a/platform/windows/godot_windows.cpp +++ b/platform/windows/godot_windows.cpp @@ -166,6 +166,10 @@ int widechar_main(int argc, wchar_t **argv) { delete[] argv_utf8[i]; } delete[] argv_utf8; + + if (err == ERR_HELP) { // Returned by --help and --version, so success. + return 0; + } return 255; } 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/main/node.cpp b/scene/main/node.cpp index 545ff68b72..b4701637a4 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -439,9 +439,9 @@ void Node::set_physics_process(bool p_process) { data.physics_process = p_process; if (data.physics_process) { - add_to_group("physics_process", false); + add_to_group(SNAME("_physics_process"), false); } else { - remove_from_group("physics_process"); + remove_from_group(SNAME("_physics_process")); } } @@ -457,9 +457,9 @@ void Node::set_physics_process_internal(bool p_process_internal) { data.physics_process_internal = p_process_internal; if (data.physics_process_internal) { - add_to_group("physics_process_internal", false); + add_to_group(SNAME("_physics_process_internal"), false); } else { - remove_from_group("physics_process_internal"); + remove_from_group(SNAME("_physics_process_internal")); } } @@ -770,9 +770,9 @@ void Node::set_process(bool p_process) { data.process = p_process; if (data.process) { - add_to_group("process", false); + add_to_group(SNAME("_process"), false); } else { - remove_from_group("process"); + remove_from_group(SNAME("_process")); } } @@ -788,9 +788,9 @@ void Node::set_process_internal(bool p_process_internal) { data.process_internal = p_process_internal; if (data.process_internal) { - add_to_group("process_internal", false); + add_to_group(SNAME("_process_internal"), false); } else { - remove_from_group("process_internal"); + remove_from_group(SNAME("_process_internal")); } } @@ -807,19 +807,19 @@ void Node::set_process_priority(int p_priority) { } if (is_processing()) { - data.tree->make_group_changed("process"); + data.tree->make_group_changed(SNAME("_process")); } if (is_processing_internal()) { - data.tree->make_group_changed("process_internal"); + data.tree->make_group_changed(SNAME("_process_internal")); } if (is_physics_processing()) { - data.tree->make_group_changed("physics_process"); + data.tree->make_group_changed(SNAME("_physics_process")); } if (is_physics_processing_internal()) { - data.tree->make_group_changed("physics_process_internal"); + data.tree->make_group_changed(SNAME("_physics_process_internal")); } } diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 18f69ecc82..a76c00efcb 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -412,9 +412,9 @@ bool SceneTree::physics_process(double p_time) { emit_signal(SNAME("physics_frame")); - _notify_group_pause(SNAME("physics_process_internal"), Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS); + _notify_group_pause(SNAME("_physics_process_internal"), Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS); call_group(SNAME("_picking_viewports"), SNAME("_process_picking")); - _notify_group_pause(SNAME("physics_process"), Node::NOTIFICATION_PHYSICS_PROCESS); + _notify_group_pause(SNAME("_physics_process"), Node::NOTIFICATION_PHYSICS_PROCESS); _flush_ugc(); MessageQueue::get_singleton()->flush(); //small little hack @@ -449,8 +449,8 @@ bool SceneTree::process(double p_time) { flush_transform_notifications(); - _notify_group_pause(SNAME("process_internal"), Node::NOTIFICATION_INTERNAL_PROCESS); - _notify_group_pause(SNAME("process"), Node::NOTIFICATION_PROCESS); + _notify_group_pause(SNAME("_process_internal"), Node::NOTIFICATION_INTERNAL_PROCESS); + _notify_group_pause(SNAME("_process"), Node::NOTIFICATION_PROCESS); _flush_ugc(); MessageQueue::get_singleton()->flush(); //small little hack 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 |