diff options
69 files changed, 2275 insertions, 110 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..9b8ffc45a7 100644 --- a/.github/workflows/macos_builds.yml +++ b/.github/workflows/macos_builds.yml @@ -5,7 +5,7 @@ on: [push, pull_request] env: # Only used for the cache key. Increment version to force clean build. GODOT_BASE_BRANCH: master-v3 - SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes + SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes use_volk=yes concurrency: group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-macos @@ -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/input/input.h b/core/input/input.h index f02f2abae5..3ad8c91ddf 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -110,7 +110,7 @@ private: bool emulate_touch_from_mouse = false; bool emulate_mouse_from_touch = false; bool use_input_buffering = false; - bool use_accumulated_input = false; + bool use_accumulated_input = true; int mouse_from_touch_index = -1; 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/Input.xml b/doc/classes/Input.xml index 66683fa0ee..e73021ead4 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -375,6 +375,7 @@ <member name="use_accumulated_input" type="bool" setter="set_use_accumulated_input" getter="is_using_accumulated_input"> If [code]true[/code], similar input events sent by the operating system are accumulated. When input accumulation is enabled, all input events generated during a frame will be merged and emitted when the frame is done rendering. Therefore, this limits the number of input method calls per second to the rendering FPS. Input accumulation can be disabled to get slightly more precise/reactive input at the cost of increased CPU usage. In applications where drawing freehand lines is required, input accumulation should generally be disabled while the user is drawing the line to get results that closely follow the actual input. + [b]Note:[/b] Input accumulation is [i]enabled[/i] by default. </member> </members> <signals> diff --git a/doc/classes/Node3D.xml b/doc/classes/Node3D.xml index ac434af4fa..ff2afd595a 100644 --- a/doc/classes/Node3D.xml +++ b/doc/classes/Node3D.xml @@ -281,6 +281,13 @@ <member name="basis" type="Basis" setter="set_basis" getter="get_basis"> Direct access to the 3x3 basis of the [Transform3D] property. </member> + <member name="global_position" type="Vector3" setter="set_global_position" getter="get_global_position"> + Global position of this node. This is equivalent to [code]global_transform.origin[/code]. + </member> + <member name="global_rotation" type="Vector3" setter="set_global_rotation" getter="get_global_rotation"> + Rotation part of the global transformation in radians, specified in terms of YXZ-Euler angles in the format (X angle, Y angle, Z angle). + [b]Note:[/b] In the mathematical sense, rotation is a matrix and not a vector. The three Euler angles, which are the three independent parameters of the Euler-angle parametrization of the rotation matrix, are stored in a [Vector3] data structure not because the rotation is a vector, but only because [Vector3] exists as a convenient data-structure to store 3 floating-point numbers. Therefore, applying affine operations on the rotation "vector" is not meaningful. + </member> <member name="global_transform" type="Transform3D" setter="set_global_transform" getter="get_global_transform"> World3D space (global) [Transform3D] of this node. </member> 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/drivers/gles3/shaders/SCsub b/drivers/gles3/shaders/SCsub index d8dd573f57..83ffe8b1e1 100644 --- a/drivers/gles3/shaders/SCsub +++ b/drivers/gles3/shaders/SCsub @@ -10,7 +10,7 @@ if "GLES3_GLSL" in env["BUILDERS"]: glsl_files = [str(f) for f in Glob("*.glsl") if str(f) not in gl_include_files] # make sure we recompile shaders if include files change - env.Depends([f + ".gen.h" for f in glsl_files], gl_include_files) + env.Depends([f + ".gen.h" for f in glsl_files], gl_include_files + ["#gles3_builders.py"]) env.GLES3_GLSL("canvas.glsl") env.GLES3_GLSL("copy.glsl") diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index 0301f5b7fa..2bf173a398 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -72,20 +72,20 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanContext::_debug_messenger_callback( strstr(pCallbackData->pMessage, "must be a memory object") != nullptr) { return VK_FALSE; } - /* - // This is a valid warning because its illegal in Vulkan, but in practice it should work according to VK_KHR_maintenance2 - if (strstr(pCallbackData->pMessage, "VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 with tiling VK_IMAGE_TILING_OPTIMAL does not support usage that includes VK_IMAGE_USAGE_STORAGE_BIT") != nullptr) { - return VK_FALSE; - } - if (strstr(pCallbackData->pMessage, "VK_FORMAT_R4G4B4A4_UNORM_PACK16 with tiling VK_IMAGE_TILING_OPTIMAL does not support usage that includes VK_IMAGE_USAGE_STORAGE_BIT") != nullptr) { - return VK_FALSE; - } -*/ // Workaround for Vulkan-Loader usability bug: https://github.com/KhronosGroup/Vulkan-Loader/issues/262. if (strstr(pCallbackData->pMessage, "wrong ELF class: ELFCLASS32") != nullptr) { return VK_FALSE; } + +#ifdef WINDOWS_ENABLED + // Some software installs Vulkan overlays in Windows registry and never cleans them up on uninstall. + // So we get spammy error level messages from the loader about those - make them verbose instead. + if (strstr(pCallbackData->pMessage, "loader_get_json: Failed to open JSON file") != nullptr) { + messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; + } +#endif + if (pCallbackData->pMessageIdName && strstr(pCallbackData->pMessageIdName, "UNASSIGNED-CoreValidation-DrawState-ClearCmdBeforeDraw") != nullptr) { return VK_FALSE; } 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/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp index 362940dc17..f0ba1eb7a1 100644 --- a/editor/import/resource_importer_wav.cpp +++ b/editor/import/resource_importer_wav.cpp @@ -162,7 +162,7 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s //Consider revision for engine version 3.0 compression_code = file->get_16(); if (compression_code != 1 && compression_code != 3) { - ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Format not supported for WAVE file (not PCM). Save WAVE files as uncompressed PCM instead."); + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Format not supported for WAVE file (not PCM). Save WAVE files as uncompressed PCM or IEEE float instead."); } format_channels = file->get_16(); @@ -180,6 +180,10 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Invalid amount of bits in the sample (should be one of 8, 16, 24 or 32)."); } + if (compression_code == 3 && format_bits % 32) { + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Invalid amount of bits in the IEEE float sample (should be 32 or 64)."); + } + /* Don't need anything else, continue */ format_found = true; } @@ -208,36 +212,46 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s data.resize(frames * format_channels); - if (format_bits == 8) { - for (int i = 0; i < frames * format_channels; i++) { - // 8 bit samples are UNSIGNED + if (compression_code == 1) { + if (format_bits == 8) { + for (int i = 0; i < frames * format_channels; i++) { + // 8 bit samples are UNSIGNED - data.write[i] = int8_t(file->get_8() - 128) / 128.f; - } - } else if (format_bits == 32 && compression_code == 3) { - for (int i = 0; i < frames * format_channels; i++) { - //32 bit IEEE Float + data.write[i] = int8_t(file->get_8() - 128) / 128.f; + } + } else if (format_bits == 16) { + for (int i = 0; i < frames * format_channels; i++) { + //16 bit SIGNED - data.write[i] = file->get_float(); - } - } else if (format_bits == 16) { - for (int i = 0; i < frames * format_channels; i++) { - //16 bit SIGNED + data.write[i] = int16_t(file->get_16()) / 32768.f; + } + } else { + for (int i = 0; i < frames * format_channels; i++) { + //16+ bits samples are SIGNED + // if sample is > 16 bits, just read extra bytes + + uint32_t s = 0; + for (int b = 0; b < (format_bits >> 3); b++) { + s |= ((uint32_t)file->get_8()) << (b * 8); + } + s <<= (32 - format_bits); - data.write[i] = int16_t(file->get_16()) / 32768.f; + data.write[i] = (int32_t(s) >> 16) / 32768.f; + } } - } else { - for (int i = 0; i < frames * format_channels; i++) { - //16+ bits samples are SIGNED - // if sample is > 16 bits, just read extra bytes - - uint32_t s = 0; - for (int b = 0; b < (format_bits >> 3); b++) { - s |= ((uint32_t)file->get_8()) << (b * 8); + } else if (compression_code == 3) { + if (format_bits == 32) { + for (int i = 0; i < frames * format_channels; i++) { + //32 bit IEEE Float + + data.write[i] = file->get_float(); } - s <<= (32 - format_bits); + } else if (format_bits == 64) { + for (int i = 0; i < frames * format_channels; i++) { + //64 bit IEEE Float - data.write[i] = (int32_t(s) >> 16) / 32768.f; + data.write[i] = file->get_double(); + } } } diff --git a/editor/import/scene_import_settings.cpp b/editor/import/scene_import_settings.cpp index 99d1658405..8ae05f046e 100644 --- a/editor/import/scene_import_settings.cpp +++ b/editor/import/scene_import_settings.cpp @@ -135,6 +135,12 @@ void SceneImportSettings::_fill_material(Tree *p_tree, const Ref<Material> &p_ma String import_id; bool has_import_id = false; + bool created = false; + if (!material_set.has(p_material)) { + material_set.insert(p_material); + created = true; + } + if (p_material->has_meta("import_id")) { import_id = p_material->get_meta("import_id"); has_import_id = true; @@ -142,7 +148,7 @@ void SceneImportSettings::_fill_material(Tree *p_tree, const Ref<Material> &p_ma import_id = p_material->get_name(); has_import_id = true; } else { - import_id = "@MATERIAL:" + itos(material_set.size()); + import_id = "@MATERIAL:" + itos(material_set.size() - 1); } if (!material_map.has(import_id)) { @@ -160,14 +166,12 @@ void SceneImportSettings::_fill_material(Tree *p_tree, const Ref<Material> &p_ma Ref<Texture2D> icon = get_theme_icon(SNAME("StandardMaterial3D"), SNAME("EditorIcons")); TreeItem *item = p_tree->create_item(p_parent); - item->set_text(0, p_material->get_name()); - item->set_icon(0, icon); - - bool created = false; - if (!material_set.has(p_material)) { - material_set.insert(p_material); - created = true; + if (p_material->get_name().is_empty()) { + item->set_text(0, TTR("<Unnamed Material>")); + } else { + item->set_text(0, p_material->get_name()); } + item->set_icon(0, icon); item->set_meta("type", "Material"); item->set_meta("import_id", import_id); @@ -339,6 +343,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; } @@ -604,6 +610,9 @@ void SceneImportSettings::open_settings(const String &p_path, bool p_for_animati _update_view_gizmos(); _update_camera(); + // Start with the root item (Scene) selected. + scene_tree->get_root()->select(0); + if (p_for_animation) { set_title(vformat(TTR("Advanced Import Settings for AnimationLibrary '%s'"), base_path.get_file())); } else { @@ -617,6 +626,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 +673,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/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index e9547e02e8..57c7f34018 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -624,6 +624,10 @@ void EditorAssetLibrary::_notification(int p_what) { } break; + case NOTIFICATION_RESIZED: { + _update_asset_items_columns(); + } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { _update_repository_options(); setup_http_request(request); @@ -1213,7 +1217,7 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const library_vb->add_child(asset_top_page); asset_items = memnew(GridContainer); - asset_items->set_columns(2); + _update_asset_items_columns(); asset_items->add_theme_constant_override("h_separation", 10 * EDSCALE); asset_items->add_theme_constant_override("v_separation", 10 * EDSCALE); @@ -1379,12 +1383,17 @@ void EditorAssetLibrary::_install_external_asset(String p_zip_path, String p_tit emit_signal(SNAME("install_asset"), p_zip_path, p_title); } -void EditorAssetLibrary::disable_community_support() { - support->get_popup()->set_item_checked(SUPPORT_COMMUNITY, false); +void EditorAssetLibrary::_update_asset_items_columns() { + int new_columns = get_size().x / (450.0 * EDSCALE); + new_columns = MAX(1, new_columns); + + if (new_columns != asset_items->get_columns()) { + asset_items->set_columns(new_columns); + } } -void EditorAssetLibrary::set_columns(const int p_columns) { - asset_items->set_columns(p_columns); +void EditorAssetLibrary::disable_community_support() { + support->get_popup()->set_item_checked(SUPPORT_COMMUNITY, false); } void EditorAssetLibrary::_bind_methods() { @@ -1542,7 +1551,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { library_vb->add_child(asset_top_page); asset_items = memnew(GridContainer); - asset_items->set_columns(2); + _update_asset_items_columns(); asset_items->add_theme_constant_override("h_separation", 10 * EDSCALE); asset_items->add_theme_constant_override("v_separation", 10 * EDSCALE); diff --git a/editor/plugins/asset_library_editor_plugin.h b/editor/plugins/asset_library_editor_plugin.h index 2b43719cdd..e02662b8db 100644 --- a/editor/plugins/asset_library_editor_plugin.h +++ b/editor/plugins/asset_library_editor_plugin.h @@ -301,6 +301,8 @@ class EditorAssetLibrary : public PanelContainer { void _install_external_asset(String p_zip_path, String p_title); + void _update_asset_items_columns(); + friend class EditorAssetLibraryItemDescription; friend class EditorAssetLibraryItem; @@ -311,7 +313,6 @@ protected: public: void disable_community_support(); - void set_columns(int p_columns); EditorAssetLibrary(bool p_templates_only = false); }; 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/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 64aeb9f2a8..77cf1f0064 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -245,6 +245,7 @@ void EditorNode3DGizmo::Instance::create_instance(Node3D *p_base, bool p_hidden) int layer = p_hidden ? 0 : 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER; RS::get_singleton()->instance_set_layer_mask(instance, layer); //gizmos are 26 RS::get_singleton()->instance_geometry_set_flag(instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); } void EditorNode3DGizmo::add_mesh(const Ref<Mesh> &p_mesh, const Ref<Material> &p_material, const Transform3D &p_xform, const Ref<SkinReference> &p_skin_reference) { diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 815d0a2425..b6bec79375 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -3251,6 +3251,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(move_gizmo_instance[i], layer); RS::get_singleton()->instance_geometry_set_flag(move_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(move_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); move_plane_gizmo_instance[i] = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_base(move_plane_gizmo_instance[i], spatial_editor->get_move_plane_gizmo(i)->get_rid()); @@ -3259,6 +3260,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_plane_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(move_plane_gizmo_instance[i], layer); RS::get_singleton()->instance_geometry_set_flag(move_plane_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(move_plane_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); rotate_gizmo_instance[i] = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_base(rotate_gizmo_instance[i], spatial_editor->get_rotate_gizmo(i)->get_rid()); @@ -3267,6 +3269,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[i], layer); RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); scale_gizmo_instance[i] = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_base(scale_gizmo_instance[i], spatial_editor->get_scale_gizmo(i)->get_rid()); @@ -3275,6 +3278,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(scale_gizmo_instance[i], layer); RS::get_singleton()->instance_geometry_set_flag(scale_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(scale_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); scale_plane_gizmo_instance[i] = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_base(scale_plane_gizmo_instance[i], spatial_editor->get_scale_plane_gizmo(i)->get_rid()); @@ -3283,6 +3287,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_plane_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(scale_plane_gizmo_instance[i], layer); RS::get_singleton()->instance_geometry_set_flag(scale_plane_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(scale_plane_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); axis_gizmo_instance[i] = RS::get_singleton()->instance_create(); RS::get_singleton()->instance_set_base(axis_gizmo_instance[i], spatial_editor->get_axis_gizmo(i)->get_rid()); @@ -3290,6 +3295,8 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_set_visible(axis_gizmo_instance[i], true); RS::get_singleton()->instance_geometry_set_cast_shadows_setting(axis_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(axis_gizmo_instance[i], layer); + RS::get_singleton()->instance_geometry_set_flag(axis_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(axis_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); } // Rotation white outline @@ -3300,6 +3307,7 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[3], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[3], layer); RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[3], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[3], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); } void Node3DEditorViewport::_finish_gizmo_instances() { @@ -5188,7 +5196,9 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) { RS::get_singleton()->instance_set_layer_mask(si->sbox_instance, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); si->sbox_instance_xray = RenderingServer::get_singleton()->instance_create2( selection_box_xray->get_rid(), sp->get_world_3d()->get_scenario()); @@ -5206,7 +5216,9 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) { RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); return si; } @@ -5993,6 +6005,7 @@ void fragment() { origin_instance = RenderingServer::get_singleton()->instance_create2(origin, get_tree()->get_root()->get_world_3d()->get_scenario()); RS::get_singleton()->instance_set_layer_mask(origin_instance, 1 << Node3DEditorViewport::GIZMO_GRID_LAYER); RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(origin_instance, RS::SHADOW_CASTING_SETTING_OFF); } @@ -6603,6 +6616,7 @@ void Node3DEditor::_init_grid() { RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(grid_instance[c], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(grid_instance[c], 1 << Node3DEditorViewport::GIZMO_GRID_LAYER); RS::get_singleton()->instance_geometry_set_flag(grid_instance[c], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(grid_instance[c], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); } } diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index ef91128146..49a3cbe185 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1895,7 +1895,6 @@ void ProjectManager::_notification(int p_what) { } if (asset_library) { real_t size = get_size().x / EDSCALE; - asset_library->set_columns(size < 1000 ? 1 : 2); // Adjust names of tabs to fit the new size. if (size < 650) { local_projects_hb->set_name(TTR("Local")); diff --git a/main/main.cpp b/main/main.cpp index 00b7483406..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 @@ -1460,11 +1460,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph /* Determine audio and video drivers */ // Display driver, e.g. X11, Wayland. - // print_line("requested display driver : " + display_driver); + // Make sure that headless is the last one, which it is assumed to be by design. + DEV_ASSERT(String("headless") == DisplayServer::get_create_function_name(DisplayServer::get_create_function_count() - 1)); for (int i = 0; i < DisplayServer::get_create_function_count(); i++) { String name = DisplayServer::get_create_function_name(i); - // print_line("\t" + itos(i) + " : " + name); - if (display_driver == name) { display_driver_idx = i; break; @@ -1472,6 +1471,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } if (display_driver_idx < 0) { + // If the requested driver wasn't found, pick the first entry. + // If all else failed it would be the headless server. display_driver_idx = 0; } @@ -1484,6 +1485,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph audio_driver = GLOBAL_GET("audio/driver/driver"); } + // Make sure that dummy is the last one, which it is assumed to be by design. + DEV_ASSERT(String("Dummy") == AudioDriverManager::get_driver(AudioDriverManager::get_driver_count() - 1)->get_name()); for (int i = 0; i < AudioDriverManager::get_driver_count(); i++) { if (audio_driver == AudioDriverManager::get_driver(i)->get_name()) { audio_driver_idx = i; @@ -1492,7 +1495,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } if (audio_driver_idx < 0) { - audio_driver_idx = 0; // 0 Is always available as the dummy driver (no sound) + // If the requested driver wasn't found, pick the first entry. + // If all else failed it would be the dummy driver (no sound). + audio_driver_idx = 0; } if (write_movie_path != String()) { @@ -1685,10 +1690,12 @@ Error Main::setup2(Thread::ID p_main_tid_override) { Error err; display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_vsync_mode, window_flags, window_size, err); if (err != OK || display_server == nullptr) { - //ok i guess we can't use this display server, try other ones - for (int i = 0; i < DisplayServer::get_create_function_count(); i++) { + // We can't use this display server, try other ones as fallback. + // Skip headless (always last registered) because that's not what users + // would expect if they didn't request it explicitly. + for (int i = 0; i < DisplayServer::get_create_function_count() - 1; i++) { if (i == display_driver_idx) { - continue; //don't try the same twice + continue; // Don't try the same twice. } display_server = DisplayServer::create(i, rendering_driver, window_mode, window_vsync_mode, window_flags, window_size, err); if (err == OK && display_server != nullptr) { diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 2017355717..36d985eff3 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -3080,9 +3080,9 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> state, const String &p_base_pat // We'll assume that we use either URI or bufferView, so let's warn the user // if their image somehow uses both. And fail if it has neither. - ERR_CONTINUE_MSG(!d.has("uri") && !d.has("bufferView"), "Invalid image definition in glTF file, it should specific an 'uri' or 'bufferView'."); + ERR_CONTINUE_MSG(!d.has("uri") && !d.has("bufferView"), "Invalid image definition in glTF file, it should specify an 'uri' or 'bufferView'."); if (d.has("uri") && d.has("bufferView")) { - WARN_PRINT("Invalid image definition in glTF file using both 'uri' and 'bufferView'. 'bufferView' will take precedence."); + WARN_PRINT("Invalid image definition in glTF file using both 'uri' and 'bufferView'. 'uri' will take precedence."); } String mimetype; diff --git a/modules/lightmapper_rd/SCsub b/modules/lightmapper_rd/SCsub index 5cc9d8ee8b..fe9737b36f 100644 --- a/modules/lightmapper_rd/SCsub +++ b/modules/lightmapper_rd/SCsub @@ -7,9 +7,7 @@ env_lightmapper_rd = env_modules.Clone() env_lightmapper_rd.GLSL_HEADER("lm_raster.glsl") env_lightmapper_rd.GLSL_HEADER("lm_compute.glsl") env_lightmapper_rd.GLSL_HEADER("lm_blendseams.glsl") -env_lightmapper_rd.Depends("lm_raster.glsl.gen.h", "lm_common_inc.glsl") -env_lightmapper_rd.Depends("lm_compute.glsl.gen.h", "lm_common_inc.glsl") -env_lightmapper_rd.Depends("lm_blendseams.glsl.gen.h", "lm_common_inc.glsl") +env_lightmapper_rd.Depends(Glob("*.glsl.gen.h"), ["lm_common_inc.glsl", "#glsl_builders.py"]) # Godot source files env_lightmapper_rd.add_source_files(env.modules_sources, "*.cpp") 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/modules/webrtc/webrtc_peer_connection_js.cpp b/modules/webrtc/webrtc_peer_connection_js.cpp index 90e19fe4f1..ee3a302fa2 100644 --- a/modules/webrtc/webrtc_peer_connection_js.cpp +++ b/modules/webrtc/webrtc_peer_connection_js.cpp @@ -57,7 +57,7 @@ void WebRTCPeerConnectionJS::_on_error(void *p_obj) { void WebRTCPeerConnectionJS::_on_data_channel(void *p_obj, int p_id) { WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(p_obj); - peer->emit_signal(SNAME("data_channel_received"), Ref<WebRTCDataChannelJS>(new WebRTCDataChannelJS(p_id))); + peer->emit_signal(SNAME("data_channel_received"), Ref<WebRTCDataChannel>(memnew(WebRTCDataChannelJS(p_id)))); } void WebRTCPeerConnectionJS::close() { diff --git a/modules/webrtc/webrtc_peer_connection_js.h b/modules/webrtc/webrtc_peer_connection_js.h index 8fa5ea7779..76b8c7fff8 100644 --- a/modules/webrtc/webrtc_peer_connection_js.h +++ b/modules/webrtc/webrtc_peer_connection_js.h @@ -52,6 +52,8 @@ extern int godot_js_rtc_pc_datachannel_create(int p_id, const char *p_label, con } class WebRTCPeerConnectionJS : public WebRTCPeerConnection { + GDCLASS(WebRTCPeerConnectionJS, WebRTCPeerConnection); + private: int _js_id; ConnectionState _conn_state; 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/detect.py b/platform/osx/detect.py index 67c2b9af7a..47765cff71 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -24,7 +24,7 @@ def get_opts(): return [ ("osxcross_sdk", "OSXCross SDK version", "darwin16"), ("MACOS_SDK_PATH", "Path to the macOS SDK", ""), - ("VULKAN_SDK_PATH", "Path to the Vulkan SDK", ""), + ("vulkan_sdk_path", "Path to the Vulkan SDK", ""), EnumVariable("macports_clang", "Build using Clang from MacPorts", "no", ("no", "5.0", "devel")), BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), @@ -36,7 +36,38 @@ def get_opts(): def get_flags(): - return [] + return [ + ("use_volk", False), + ] + + +def get_mvk_sdk_path(): + def int_or_zero(i): + try: + return int(i) + except: + return 0 + + def ver_parse(a): + return [int_or_zero(i) for i in a.split(".")] + + dirname = os.path.expanduser("~/VulkanSDK") + files = os.listdir(dirname) + + ver_file = "0.0.0.0" + ver_num = ver_parse(ver_file) + + for file in files: + if os.path.isdir(os.path.join(dirname, file)): + ver_comp = ver_parse(file) + lib_name = os.path.join( + os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/libMoltenVK.a" + ) + if os.path.isfile(lib_name) and ver_comp > ver_num: + ver_num = ver_comp + ver_file = file + + return os.path.join(os.path.join(dirname, ver_file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/") def configure(env): @@ -199,4 +230,22 @@ def configure(env): env.Append(CPPDEFINES=["VULKAN_ENABLED"]) env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "QuartzCore", "-framework", "IOSurface"]) if not env["use_volk"]: - env.Append(LINKFLAGS=["-L$VULKAN_SDK_PATH/MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/", "-lMoltenVK"]) + env.Append(LINKFLAGS=["-lMoltenVK"]) + mvk_found = False + if env["vulkan_sdk_path"] != "": + mvk_path = os.path.join( + os.path.expanduser(env["vulkan_sdk_path"]), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/" + ) + if os.path.isfile(os.path.join(mvk_path, "libMoltenVK.a")): + mvk_found = True + env.Append(LINKFLAGS=["-L" + mvk_path]) + if not mvk_found: + mvk_path = get_mvk_sdk_path() + if os.path.isfile(os.path.join(mvk_path, "libMoltenVK.a")): + mvk_found = True + env.Append(LINKFLAGS=["-L" + mvk_path]) + if not mvk_found: + print( + "MoltenVK SDK installation directory not found, use 'vulkan_sdk_path' SCons parameter to specify SDK path." + ) + sys.exit(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/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index 221d52bc20..d56c7b8811 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -138,8 +138,11 @@ void AnimatedSprite2D::_validate_property(PropertyInfo &property) const { if (property.name == "frame") { property.hint = PROPERTY_HINT_RANGE; - if (frames->has_animation(animation) && frames->get_frame_count(animation) > 1) { + if (frames->has_animation(animation) && frames->get_frame_count(animation) > 0) { property.hint_string = "0," + itos(frames->get_frame_count(animation) - 1) + ",1"; + } else { + // Avoid an error, `hint_string` is required for `PROPERTY_HINT_RANGE`. + property.hint_string = "0,0,1"; } property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } 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/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 4c00250162..04b1081516 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -237,6 +237,28 @@ void Node3D::set_quaternion(const Quaternion &p_quaternion) { } } +Vector3 Node3D::get_global_position() const { + return get_global_transform().get_origin(); +} + +void Node3D::set_global_position(const Vector3 &p_position) { + Transform3D transform = get_global_transform(); + transform.set_origin(p_position); + set_global_transform(transform); +} + +Vector3 Node3D::get_global_rotation() const { + return get_global_transform().get_basis().get_euler(); +} + +void Node3D::set_global_rotation(const Vector3 &p_euler_rad) { + Transform3D transform = get_global_transform(); + Basis new_basis = transform.get_basis(); + new_basis.set_euler(p_euler_rad); + transform.set_basis(new_basis); + set_global_transform(transform); +} + void Node3D::set_transform(const Transform3D &p_transform) { data.local_transform = p_transform; data.dirty = DIRTY_EULER_ROTATION_AND_SCALE; // Make rot/scale dirty. @@ -950,8 +972,14 @@ void Node3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_quaternion"), &Node3D::get_quaternion); ClassDB::bind_method(D_METHOD("set_basis", "basis"), &Node3D::set_basis); ClassDB::bind_method(D_METHOD("get_basis"), &Node3D::get_basis); + ClassDB::bind_method(D_METHOD("set_global_transform", "global"), &Node3D::set_global_transform); ClassDB::bind_method(D_METHOD("get_global_transform"), &Node3D::get_global_transform); + ClassDB::bind_method(D_METHOD("set_global_position", "position"), &Node3D::set_global_position); + ClassDB::bind_method(D_METHOD("get_global_position"), &Node3D::get_global_position); + ClassDB::bind_method(D_METHOD("set_global_rotation", "radians"), &Node3D::set_global_rotation); + ClassDB::bind_method(D_METHOD("get_global_rotation"), &Node3D::get_global_rotation); + ClassDB::bind_method(D_METHOD("get_parent_node_3d"), &Node3D::get_parent_node_3d); ClassDB::bind_method(D_METHOD("set_ignore_transform_notification", "enabled"), &Node3D::set_ignore_transform_notification); ClassDB::bind_method(D_METHOD("set_as_top_level", "enable"), &Node3D::set_as_top_level); @@ -1034,6 +1062,9 @@ void Node3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_edit_mode", PROPERTY_HINT_ENUM, "Euler,Quaternion,Basis"), "set_rotation_edit_mode", "get_rotation_edit_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_order", PROPERTY_HINT_ENUM, "XYZ,XZY,YXZ,YZX,ZXY,ZYX"), "set_rotation_order", "get_rotation_order"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "top_level"), "set_as_top_level", "is_set_as_top_level"); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "global_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_position", "get_global_position"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "global_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_rotation", "get_global_rotation"); ADD_GROUP("Visibility", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "visibility_parent", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GeometryInstance3D"), "set_visibility_parent", "get_visibility_parent"); diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index cfd88585e4..b1e129798d 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -182,12 +182,18 @@ public: void set_rotation(const Vector3 &p_euler_rad); void set_scale(const Vector3 &p_scale); + void set_global_position(const Vector3 &p_position); + void set_global_rotation(const Vector3 &p_euler_rad); + Vector3 get_position() const; RotationOrder get_rotation_order() const; Vector3 get_rotation() const; Vector3 get_scale() const; + Vector3 get_global_position() const; + Vector3 get_global_rotation() const; + void set_transform(const Transform3D &p_transform); void set_basis(const Basis &p_basis); void set_quaternion(const Quaternion &p_quaternion); diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 55b55d924c..cb6354f7a8 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -1034,8 +1034,11 @@ void AnimatedSprite3D::_validate_property(PropertyInfo &property) const { if (property.name == "frame") { property.hint = PROPERTY_HINT_RANGE; - if (frames->has_animation(animation) && frames->get_frame_count(animation) > 1) { + if (frames->has_animation(animation) && frames->get_frame_count(animation) > 0) { property.hint_string = "0," + itos(frames->get_frame_count(animation) - 1) + ",1"; + } else { + // Avoid an error, `hint_string` is required for `PROPERTY_HINT_RANGE`. + property.hint_string = "0,0,1"; } property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS; } 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 diff --git a/servers/rendering/renderer_rd/shaders/SCsub b/servers/rendering/renderer_rd/shaders/SCsub index 05663226c0..d352743908 100644 --- a/servers/rendering/renderer_rd/shaders/SCsub +++ b/servers/rendering/renderer_rd/shaders/SCsub @@ -10,7 +10,7 @@ if "RD_GLSL" in env["BUILDERS"]: glsl_files = [str(f) for f in Glob("*.glsl") if str(f) not in gl_include_files] # make sure we recompile shaders if include files change - env.Depends([f + ".gen.h" for f in glsl_files], gl_include_files) + env.Depends([f + ".gen.h" for f in glsl_files], gl_include_files + ["#glsl_builders.py"]) # compile shaders for glsl_file in glsl_files: diff --git a/servers/rendering/renderer_rd/shaders/effects/SCsub b/servers/rendering/renderer_rd/shaders/effects/SCsub index fc513d3fb9..741da8fe69 100644 --- a/servers/rendering/renderer_rd/shaders/effects/SCsub +++ b/servers/rendering/renderer_rd/shaders/effects/SCsub @@ -10,7 +10,7 @@ if "RD_GLSL" in env["BUILDERS"]: glsl_files = [str(f) for f in Glob("*.glsl") if str(f) not in gl_include_files] # make sure we recompile shaders if include files change - env.Depends([f + ".gen.h" for f in glsl_files], gl_include_files) + env.Depends([f + ".gen.h" for f in glsl_files], gl_include_files + ["#glsl_builders.py"]) # compile shaders for glsl_file in glsl_files: diff --git a/servers/rendering/renderer_rd/shaders/environment/SCsub b/servers/rendering/renderer_rd/shaders/environment/SCsub index fc513d3fb9..741da8fe69 100644 --- a/servers/rendering/renderer_rd/shaders/environment/SCsub +++ b/servers/rendering/renderer_rd/shaders/environment/SCsub @@ -10,7 +10,7 @@ if "RD_GLSL" in env["BUILDERS"]: glsl_files = [str(f) for f in Glob("*.glsl") if str(f) not in gl_include_files] # make sure we recompile shaders if include files change - env.Depends([f + ".gen.h" for f in glsl_files], gl_include_files) + env.Depends([f + ".gen.h" for f in glsl_files], gl_include_files + ["#glsl_builders.py"]) # compile shaders for glsl_file in glsl_files: |