summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/macos_builds.yml18
-rw-r--r--core/config/project_settings.cpp20
-rw-r--r--core/config/project_settings.h1
-rw-r--r--core/extension/gdextension.cpp4
-rw-r--r--core/input/gamecontrollerdb.txt4
-rw-r--r--core/io/logger.cpp16
-rw-r--r--core/io/logger.h8
-rw-r--r--core/io/resource_loader.cpp12
-rw-r--r--core/object/class_db.h10
-rw-r--r--core/os/os.cpp5
-rw-r--r--core/os/os.h1
-rw-r--r--core/variant/type_info.h37
-rw-r--r--core/variant/variant.cpp44
-rw-r--r--core/variant/variant.h23
-rw-r--r--doc/classes/CanvasItem.xml1
-rw-r--r--doc/classes/FileAccess.xml8
-rw-r--r--doc/classes/Node.xml6
-rw-r--r--doc/classes/ParticleProcessMaterial.xml1
-rw-r--r--doc/classes/ProjectSettings.xml8
-rw-r--r--doc/classes/RenderingServer.xml8
-rw-r--r--doc/classes/ScriptEditorBase.xml6
-rw-r--r--doc/classes/TabContainer.xml15
-rw-r--r--doc/classes/Tree.xml8
-rw-r--r--doc/classes/XRHandTracker.xml23
-rw-r--r--drivers/gles3/effects/post_effects.cpp4
-rw-r--r--drivers/gles3/effects/post_effects.h2
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp42
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.h5
-rw-r--r--drivers/gles3/shaders/canvas.glsl10
-rw-r--r--drivers/gles3/shaders/effects/post.glsl42
-rw-r--r--drivers/gles3/shaders/scene.glsl41
-rw-r--r--drivers/gles3/shaders/sky.glsl8
-rw-r--r--drivers/gles3/shaders/tonemap.glsl333
-rw-r--r--drivers/gles3/shaders/tonemap_inc.glsl67
-rw-r--r--drivers/gles3/storage/material_storage.cpp2
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.cpp2
-rw-r--r--editor/code_editor.cpp19
-rw-r--r--editor/code_editor.h4
-rw-r--r--editor/editor_help.cpp2
-rw-r--r--editor/editor_node.cpp3
-rw-r--r--editor/editor_properties.cpp2
-rw-r--r--editor/editor_resource_picker.cpp3
-rw-r--r--editor/editor_resource_picker.h1
-rw-r--r--editor/export/editor_export_platform.cpp6
-rw-r--r--editor/export/project_export.cpp6
-rw-r--r--editor/filesystem_dock.cpp34
-rw-r--r--editor/filesystem_dock.h1
-rw-r--r--editor/gui/editor_dir_dialog.cpp15
-rw-r--r--editor/gui/editor_scene_tabs.cpp2
-rw-r--r--editor/gui/scene_tree_editor.cpp1
-rw-r--r--editor/import_dock.cpp24
-rw-r--r--editor/import_dock.h1
-rw-r--r--editor/plugin_config_dialog.cpp1
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp81
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h2
-rw-r--r--editor/plugins/editor_preview_plugins.cpp2
-rw-r--r--editor/plugins/editor_resource_tooltip_plugins.cpp27
-rw-r--r--editor/plugins/editor_resource_tooltip_plugins.h11
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.cpp320
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.h25
-rw-r--r--editor/plugins/path_3d_editor_plugin.cpp14
-rw-r--r--editor/plugins/script_editor_plugin.cpp50
-rw-r--r--editor/plugins/script_editor_plugin.h2
-rw-r--r--editor/plugins/script_text_editor.cpp27
-rw-r--r--editor/plugins/script_text_editor.h6
-rw-r--r--editor/plugins/tiles/tile_set_editor.cpp2
-rw-r--r--editor/project_manager.cpp14
-rw-r--r--editor/project_settings_editor.cpp2
-rw-r--r--editor/script_create_dialog.cpp1
-rw-r--r--editor/shader_create_dialog.cpp7
-rw-r--r--editor/shader_globals_editor.cpp4
-rw-r--r--main/main.cpp6
-rw-r--r--modules/fbx/fbx_document.cpp97
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp86
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h19
-rw-r--r--modules/gdscript/gdscript_codegen.h5
-rw-r--r--modules/gdscript/gdscript_compiler.cpp74
-rw-r--r--modules/gdscript/gdscript_compiler.h6
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp7
-rw-r--r--modules/gdscript/gdscript_function.h28
-rw-r--r--modules/gdscript/gdscript_vm.cpp39
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/object_iterators.gd49
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/object_iterators.out30
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/reset_uninit_local_vars.gd21
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/reset_uninit_local_vars.out4
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp1
-rw-r--r--modules/gltf/extensions/physics/gltf_document_extension_physics.cpp50
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.cpp2
-rw-r--r--modules/webxr/webxr_interface_js.cpp2
-rw-r--r--scene/2d/audio_listener_2d.cpp8
-rw-r--r--scene/3d/audio_listener_3d.cpp8
-rw-r--r--scene/3d/camera_3d.cpp6
-rw-r--r--scene/3d/node_3d.cpp6
-rw-r--r--scene/3d/physics/character_body_3d.cpp2
-rw-r--r--scene/3d/xr_body_modifier_3d.cpp14
-rw-r--r--scene/3d/xr_body_modifier_3d.h2
-rw-r--r--scene/gui/file_dialog.cpp2
-rw-r--r--scene/gui/graph_node.cpp23
-rw-r--r--scene/gui/line_edit.cpp2
-rw-r--r--scene/gui/popup_menu.cpp7
-rw-r--r--scene/gui/tab_container.cpp18
-rw-r--r--scene/gui/tab_container.h3
-rw-r--r--scene/gui/text_edit.cpp9
-rw-r--r--scene/gui/tree.cpp83
-rw-r--r--scene/main/node.cpp1
-rw-r--r--scene/main/node.h2
-rw-r--r--scene/main/scene_tree.cpp7
-rw-r--r--scene/main/scene_tree.h6
-rw-r--r--scene/main/viewport.cpp2
-rw-r--r--scene/resources/surface_tool.cpp2
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp2
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp2
-rw-r--r--servers/rendering/renderer_rd/shaders/canvas.glsl10
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl28
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl25
-rw-r--r--servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl10
-rw-r--r--servers/rendering/shader_types.cpp1
-rw-r--r--servers/rendering/storage/environment_storage.cpp6
-rw-r--r--servers/xr/xr_hand_tracker.cpp52
-rw-r--r--servers/xr/xr_hand_tracker.h11
-rw-r--r--tests/scene/test_graph_node.h59
-rw-r--r--tests/scene/test_navigation_region_3d.h2
122 files changed, 1418 insertions, 1096 deletions
diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml
index 04bdf971ca..70031ec4c3 100644
--- a/.github/workflows/macos_builds.yml
+++ b/.github/workflows/macos_builds.yml
@@ -14,7 +14,7 @@ concurrency:
jobs:
build-macos:
- runs-on: "macos-12"
+ runs-on: "macos-latest"
name: ${{ matrix.name }}
strategy:
fail-fast: false
@@ -24,7 +24,7 @@ jobs:
cache-name: macos-editor
target: editor
tests: true
- bin: "./bin/godot.macos.editor.x86_64"
+ bin: "./bin/godot.macos.editor.universal"
- name: Template (target=template_release)
cache-name: macos-template
@@ -50,16 +50,26 @@ jobs:
run: |
sh misc/scripts/install_vulkan_sdk_macos.sh
- - name: Compilation
+ - name: Compilation (x86_64)
uses: ./.github/actions/godot-build
with:
- sconsflags: ${{ env.SCONSFLAGS }}
+ sconsflags: ${{ env.SCONSFLAGS }} arch=x86_64
+ platform: macos
+ target: ${{ matrix.target }}
+ tests: ${{ matrix.tests }}
+
+ - name: Compilation (arm64)
+ uses: ./.github/actions/godot-build
+ with:
+ sconsflags: ${{ env.SCONSFLAGS }} arch=arm64
platform: macos
target: ${{ matrix.target }}
tests: ${{ matrix.tests }}
- name: Prepare artifact
run: |
+ lipo -create ./bin/godot.macos.${{ matrix.target }}.x86_64 ./bin/godot.macos.${{ matrix.target }}.arm64 -output ./bin/godot.macos.${{ matrix.target }}.universal
+ rm ./bin/godot.macos.${{ matrix.target }}.x86_64 ./bin/godot.macos.${{ matrix.target }}.arm64
strip bin/godot.*
chmod +x bin/godot.*
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 104b17961d..ee20aea35d 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -1017,7 +1017,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust
}
}
// Check for the existence of a csproj file.
- if (_csproj_exists(get_resource_path())) {
+ if (_csproj_exists(p_path.get_base_dir())) {
// If there is a csproj file, add the C# feature if it doesn't already exist.
if (!project_features.has("C#")) {
project_features.append("C#");
@@ -1473,7 +1473,9 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 0); // 8K resolution
GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true);
- GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor", false);
+#ifdef TOOLS_ENABLED
+ GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor_hint", false);
+#endif
GLOBAL_DEF("animation/warnings/check_invalid_track_paths", true);
GLOBAL_DEF("animation/warnings/check_angle_interpolation_type_conflicting", true);
@@ -1531,6 +1533,10 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_BASIC("internationalization/rendering/root_node_auto_translate", true);
GLOBAL_DEF(PropertyInfo(Variant::INT, "gui/timers/incremental_search_max_interval_msec", PROPERTY_HINT_RANGE, "0,10000,1,or_greater"), 2000);
+ GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "gui/timers/tooltip_delay_sec", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater"), 0.5);
+#ifdef TOOLS_ENABLED
+ GLOBAL_DEF("gui/timers/tooltip_delay_sec.editor_hint", 0.5);
+#endif
GLOBAL_DEF_BASIC("gui/common/snap_controls_to_pixels", true);
GLOBAL_DEF_BASIC("gui/fonts/dynamic_fonts/use_oversampling", true);
@@ -1568,6 +1574,14 @@ ProjectSettings::ProjectSettings() {
ProjectSettings::get_singleton()->add_hidden_prefix("input/");
}
+ProjectSettings::ProjectSettings(const String &p_path) {
+ if (load_custom(p_path) == OK) {
+ project_loaded = true;
+ }
+}
+
ProjectSettings::~ProjectSettings() {
- singleton = nullptr;
+ if (singleton == this) {
+ singleton = nullptr;
+ }
}
diff --git a/core/config/project_settings.h b/core/config/project_settings.h
index 1bad76acb1..922c88c151 100644
--- a/core/config/project_settings.h
+++ b/core/config/project_settings.h
@@ -224,6 +224,7 @@ public:
#endif
ProjectSettings();
+ ProjectSettings(const String &p_path);
~ProjectSettings();
};
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index 3ea5791282..22a5df9935 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -275,7 +275,7 @@ public:
ret_opaque = r_ret->get_type() == Variant::NIL ? r_ret : VariantInternal::get_opaque_pointer(r_ret);
}
- ptrcall(p_object, argptrs, ret_opaque);
+ ptrcall_func(method_userdata, extension_instance, reinterpret_cast<GDExtensionConstTypePtr *>(argptrs), (GDExtensionTypePtr)ret_opaque);
if (r_ret && r_ret->get_type() == Variant::OBJECT) {
VariantInternal::update_object_id(r_ret);
@@ -289,7 +289,7 @@ public:
ERR_FAIL_COND_MSG(p_object && p_object->is_extension_placeholder(), vformat("Cannot call GDExtension method bind '%s' on placeholder instance.", name));
#endif
ERR_FAIL_COND_MSG(vararg, "Vararg methods don't have ptrcall support. This is most likely an engine bug.");
- GDExtensionClassInstancePtr extension_instance = p_object->_get_extension_instance();
+ GDExtensionClassInstancePtr extension_instance = is_static() ? nullptr : p_object->_get_extension_instance();
ptrcall_func(method_userdata, extension_instance, reinterpret_cast<GDExtensionConstTypePtr *>(p_args), (GDExtensionTypePtr)r_ret);
}
diff --git a/core/input/gamecontrollerdb.txt b/core/input/gamecontrollerdb.txt
index a17e15a62c..7150911e75 100644
--- a/core/input/gamecontrollerdb.txt
+++ b/core/input/gamecontrollerdb.txt
@@ -917,6 +917,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000000d0f00003801000008010000,Hori PC Engine Mini Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,platform:Mac OS X,
030000000d0f00009200000000010000,Hori Pokken Tournament DX Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,
030000000d0f0000aa00000072050000,Hori Real Arcade Pro for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
+030000000d0f00000002000017010000,Hori Split Pad Fit,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000000d0f00000002000015010000,Hori Switch Split Pad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000000d0f00006e00000000010000,Horipad 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000000d0f00006600000000010000,Horipad 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
@@ -1282,6 +1283,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000000d0f00006b00000011010000,Hori Real Arcade Pro 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000000d0f00001600000000010000,Hori Real Arcade Pro EXSE,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux,
030000000d0f0000aa00000011010000,Hori Real Arcade Pro for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
+030000000d0f00008501000017010000,Hori Split Pad Fit,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000000d0f00008501000015010000,Hori Switch Split Pad Pro,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000000d0f00006e00000011010000,Horipad 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000000d0f00006600000011010000,Horipad 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
@@ -1696,6 +1698,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
060000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
050000005e040000130b000017050000,Xbox Series X Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
060000005e040000120b00000d050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000120b000011050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
+030000005e040000120b000014050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
050000005e040000200b000013050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000200b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000220b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
diff --git a/core/io/logger.cpp b/core/io/logger.cpp
index 441df7f5d1..1476b8ccac 100644
--- a/core/io/logger.cpp
+++ b/core/io/logger.cpp
@@ -37,6 +37,8 @@
#include "core/os/time.h"
#include "core/string/print_string.h"
+#include "modules/modules_enabled.gen.h" // For regex.
+
#if defined(MINGW_ENABLED) || defined(_MSC_VER)
#define sprintf sprintf_s
#endif
@@ -180,6 +182,12 @@ RotatedFileLogger::RotatedFileLogger(const String &p_base_path, int p_max_files)
base_path(p_base_path.simplify_path()),
max_files(p_max_files > 0 ? p_max_files : 1) {
rotate_file();
+
+#ifdef MODULE_REGEX_ENABLED
+ strip_ansi_regex.instantiate();
+ strip_ansi_regex->detach_from_objectdb(); // Note: This RegEx instance will exist longer than ObjectDB, therefore can't be registered in ObjectDB.
+ strip_ansi_regex->compile("\u001b\\[((?:\\d|;)*)([a-zA-Z])");
+#endif // MODULE_REGEX_ENABLED
}
void RotatedFileLogger::logv(const char *p_format, va_list p_list, bool p_err) {
@@ -199,7 +207,15 @@ void RotatedFileLogger::logv(const char *p_format, va_list p_list, bool p_err) {
vsnprintf(buf, len + 1, p_format, list_copy);
}
va_end(list_copy);
+
+#ifdef MODULE_REGEX_ENABLED
+ // Strip ANSI escape codes (such as those inserted by `print_rich()`)
+ // before writing to file, as text editors cannot display those
+ // correctly.
+ file->store_string(strip_ansi_regex->sub(String(buf), "", true));
+#else
file->store_buffer((uint8_t *)buf, len);
+#endif // MODULE_REGEX_ENABLED
if (len >= static_buf_size) {
Memory::free_static(buf);
diff --git a/core/io/logger.h b/core/io/logger.h
index 3cd18965c5..85ef3031ec 100644
--- a/core/io/logger.h
+++ b/core/io/logger.h
@@ -34,6 +34,10 @@
#include "core/io/file_access.h"
#include "core/string/ustring.h"
#include "core/templates/vector.h"
+#include "modules/modules_enabled.gen.h" // For regex.
+#ifdef MODULE_REGEX_ENABLED
+#include "modules/regex/regex.h"
+#endif // MODULE_REGEX_ENABLED
#include <stdarg.h>
@@ -86,6 +90,10 @@ class RotatedFileLogger : public Logger {
void clear_old_backups();
void rotate_file();
+#ifdef MODULE_REGEX_ENABLED
+ Ref<RegEx> strip_ansi_regex;
+#endif // MODULE_REGEX_ENABLED
+
public:
explicit RotatedFileLogger(const String &p_base_path, int p_max_files = 10);
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index eea6357084..c3c37aa89d 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -242,16 +242,20 @@ ResourceLoader::LoadToken::~LoadToken() {
}
Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress) {
+ const String &original_path = p_original_path.is_empty() ? p_path : p_original_path;
load_nesting++;
if (load_paths_stack->size()) {
thread_load_mutex.lock();
- HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(load_paths_stack->get(load_paths_stack->size() - 1));
- if (E) {
+ const String &parent_task_path = load_paths_stack->get(load_paths_stack->size() - 1);
+ HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(parent_task_path);
+ // Avoid double-tracking, for progress reporting, resources that boil down to a remapped path containing the real payload (e.g., imported resources).
+ bool is_remapped_load = original_path == parent_task_path;
+ if (E && !is_remapped_load) {
E->value.sub_tasks.insert(p_original_path);
}
thread_load_mutex.unlock();
}
- load_paths_stack->push_back(p_original_path);
+ load_paths_stack->push_back(original_path);
// Try all loaders and pick the first match for the type hint
bool found = false;
@@ -261,7 +265,7 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
continue;
}
found = true;
- res = loader[i]->load(p_path, !p_original_path.is_empty() ? p_original_path : p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode);
+ res = loader[i]->load(p_path, original_path, r_error, p_use_sub_threads, r_progress, p_cache_mode);
if (!res.is_null()) {
break;
}
diff --git a/core/object/class_db.h b/core/object/class_db.h
index adb525cbe8..37a864c109 100644
--- a/core/object/class_db.h
+++ b/core/object/class_db.h
@@ -195,7 +195,7 @@ public:
template <typename T>
static void register_class(bool p_virtual = false) {
GLOBAL_LOCK_FUNCTION;
- static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
+ static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
T::initialize_class();
ClassInfo *t = classes.getptr(T::get_class_static());
ERR_FAIL_NULL(t);
@@ -210,7 +210,7 @@ public:
template <typename T>
static void register_abstract_class() {
GLOBAL_LOCK_FUNCTION;
- static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
+ static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
T::initialize_class();
ClassInfo *t = classes.getptr(T::get_class_static());
ERR_FAIL_NULL(t);
@@ -223,7 +223,7 @@ public:
template <typename T>
static void register_internal_class() {
GLOBAL_LOCK_FUNCTION;
- static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
+ static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
T::initialize_class();
ClassInfo *t = classes.getptr(T::get_class_static());
ERR_FAIL_NULL(t);
@@ -238,7 +238,7 @@ public:
template <typename T>
static void register_runtime_class() {
GLOBAL_LOCK_FUNCTION;
- static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
+ static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
T::initialize_class();
ClassInfo *t = classes.getptr(T::get_class_static());
ERR_FAIL_NULL(t);
@@ -263,7 +263,7 @@ public:
template <typename T>
static void register_custom_instance_class() {
GLOBAL_LOCK_FUNCTION;
- static_assert(types_are_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
+ static_assert(std::is_same_v<typename T::self_type, T>, "Class not declared properly, please use GDCLASS.");
T::initialize_class();
ClassInfo *t = classes.getptr(T::get_class_static());
ERR_FAIL_NULL(t);
diff --git a/core/os/os.cpp b/core/os/os.cpp
index 8582888740..fa7f23ded0 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -398,6 +398,11 @@ bool OS::has_feature(const String &p_feature) {
if (p_feature == "editor") {
return true;
}
+ if (p_feature == "editor_hint") {
+ return _in_editor;
+ } else if (p_feature == "editor_runtime") {
+ return !_in_editor;
+ }
#else
if (p_feature == "template") {
return true;
diff --git a/core/os/os.h b/core/os/os.h
index 069a3876af..d20f84b4ff 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -63,6 +63,7 @@ class OS {
bool _stdout_enabled = true;
bool _stderr_enabled = true;
bool _writing_movie = false;
+ bool _in_editor = false;
CompositeLogger *_logger = nullptr;
diff --git a/core/variant/type_info.h b/core/variant/type_info.h
index 9c52db3345..15cb6c9c1a 100644
--- a/core/variant/type_info.h
+++ b/core/variant/type_info.h
@@ -33,31 +33,7 @@
#include "core/typedefs.h"
-template <bool C, typename T = void>
-struct EnableIf {
- typedef T type;
-};
-
-template <typename T>
-struct EnableIf<false, T> {
-};
-
-template <typename, typename>
-inline constexpr bool types_are_same_v = false;
-
-template <typename T>
-inline constexpr bool types_are_same_v<T, T> = true;
-
-template <typename B, typename D>
-struct TypeInherits {
- static D *get_d();
-
- static char (&test(B *))[1];
- static char (&test(...))[2];
-
- static bool const value = sizeof(test(get_d())) == sizeof(char) &&
- !types_are_same_v<B volatile const, void volatile const>;
-};
+#include <type_traits>
namespace GodotTypeInfo {
enum Metadata {
@@ -223,16 +199,7 @@ MAKE_TEMPLATE_TYPE_INFO(Vector, Face3, Variant::PACKED_VECTOR3_ARRAY)
MAKE_TEMPLATE_TYPE_INFO(Vector, StringName, Variant::PACKED_STRING_ARRAY)
template <typename T>
-struct GetTypeInfo<T *, typename EnableIf<TypeInherits<Object, T>::value>::type> {
- static const Variant::Type VARIANT_TYPE = Variant::OBJECT;
- static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
- static inline PropertyInfo get_class_info() {
- return PropertyInfo(StringName(T::get_class_static()));
- }
-};
-
-template <typename T>
-struct GetTypeInfo<const T *, typename EnableIf<TypeInherits<Object, T>::value>::type> {
+struct GetTypeInfo<T *, std::enable_if_t<std::is_base_of_v<Object, T>>> {
static const Variant::Type VARIANT_TYPE = Variant::OBJECT;
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
static inline PropertyInfo get_class_info() {
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index 8e702ce8bb..fcbfdd4741 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -3495,50 +3495,6 @@ bool Variant::is_ref_counted() const {
return type == OBJECT && _get_obj().id.is_ref_counted();
}
-Vector<Variant> varray() {
- return Vector<Variant>();
-}
-
-Vector<Variant> varray(const Variant &p_arg1) {
- Vector<Variant> v;
- v.push_back(p_arg1);
- return v;
-}
-
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2) {
- Vector<Variant> v;
- v.push_back(p_arg1);
- v.push_back(p_arg2);
- return v;
-}
-
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3) {
- Vector<Variant> v;
- v.push_back(p_arg1);
- v.push_back(p_arg2);
- v.push_back(p_arg3);
- return v;
-}
-
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4) {
- Vector<Variant> v;
- v.push_back(p_arg1);
- v.push_back(p_arg2);
- v.push_back(p_arg3);
- v.push_back(p_arg4);
- return v;
-}
-
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const Variant &p_arg5) {
- Vector<Variant> v;
- v.push_back(p_arg1);
- v.push_back(p_arg2);
- v.push_back(p_arg3);
- v.push_back(p_arg4);
- v.push_back(p_arg5);
- return v;
-}
-
void Variant::static_assign(const Variant &p_variant) {
}
diff --git a/core/variant/variant.h b/core/variant/variant.h
index e40df3171f..ea6ae02c1e 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -798,12 +798,23 @@ public:
//typedef Dictionary Dictionary; no
//typedef Array Array;
-Vector<Variant> varray();
-Vector<Variant> varray(const Variant &p_arg1);
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2);
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3);
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4);
-Vector<Variant> varray(const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const Variant &p_arg5);
+template <typename... VarArgs>
+Vector<Variant> varray(VarArgs... p_args) {
+ Vector<Variant> v;
+
+ Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
+ uint32_t argc = sizeof...(p_args);
+
+ if (argc > 0) {
+ v.resize(argc);
+ Variant *vw = v.ptrw();
+
+ for (uint32_t i = 0; i < argc; i++) {
+ vw[i] = args[i];
+ }
+ }
+ return v;
+}
struct VariantHasher {
static _FORCE_INLINE_ uint32_t hash(const Variant &p_variant) { return p_variant.hash(); }
diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml
index 1cfd44467f..18413c4be5 100644
--- a/doc/classes/CanvasItem.xml
+++ b/doc/classes/CanvasItem.xml
@@ -8,6 +8,7 @@
Any [CanvasItem] can draw. For this, [method queue_redraw] is called by the engine, then [constant NOTIFICATION_DRAW] will be received on idle time to request a redraw. Because of this, canvas items don't need to be redrawn on every frame, improving the performance significantly. Several functions for drawing on the [CanvasItem] are provided (see [code]draw_*[/code] functions). However, they can only be used inside [method _draw], its corresponding [method Object._notification] or methods connected to the [signal draw] signal.
Canvas items are drawn in tree order on their canvas layer. By default, children are on top of their parents, so a root [CanvasItem] will be drawn behind everything. This behavior can be changed on a per-item basis.
A [CanvasItem] can be hidden, which will also hide its children. By adjusting various other properties of a [CanvasItem], you can also modulate its color (via [member modulate] or [member self_modulate]), change its Z-index, blend mode, and more.
+ Note that properties like transform, modulation, and visibility are only propagated to [i]direct[/i] [CanvasItem] child nodes. If there is a non-[CanvasItem] node in between, like [Node] or [AnimationPlayer], the [CanvasItem] nodes below will have an independent position and [member modulate] chain. See also [member top_level].
</description>
<tutorials>
<link title="Viewport and canvas transforms">$DOCS_URL/tutorials/2d/2d_transforms.html</link>
diff --git a/doc/classes/FileAccess.xml b/doc/classes/FileAccess.xml
index fafc02734a..99574da808 100644
--- a/doc/classes/FileAccess.xml
+++ b/doc/classes/FileAccess.xml
@@ -8,23 +8,23 @@
Here's a sample on how to write and read from a file:
[codeblocks]
[gdscript]
- func save(content):
+ func save_to_file(content):
var file = FileAccess.open("user://save_game.dat", FileAccess.WRITE)
file.store_string(content)
- func load():
+ func load_from_file():
var file = FileAccess.open("user://save_game.dat", FileAccess.READ)
var content = file.get_as_text()
return content
[/gdscript]
[csharp]
- public void Save(string content)
+ public void SaveToFile(string content)
{
using var file = FileAccess.Open("user://save_game.dat", FileAccess.ModeFlags.Write);
file.StoreString(content);
}
- public string Load()
+ public string LoadFromFile()
{
using var file = FileAccess.Open("user://save_game.dat", FileAccess.ModeFlags.Read);
string content = file.GetAsText();
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index 37e64da8c8..3342e99ab6 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -619,6 +619,12 @@
[method request_ready] resets it back to [code]false[/code].
</description>
</method>
+ <method name="is_part_of_edited_scene" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if the node is part of the scene currently opened in the editor.
+ </description>
+ </method>
<method name="is_physics_interpolated" qualifiers="const">
<return type="bool" />
<description>
diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml
index 8d0ae317b9..1502690b45 100644
--- a/doc/classes/ParticleProcessMaterial.xml
+++ b/doc/classes/ParticleProcessMaterial.xml
@@ -185,6 +185,7 @@
</member>
<member name="emission_box_extents" type="Vector3" setter="set_emission_box_extents" getter="get_emission_box_extents">
The box's extents if [member emission_shape] is set to [constant EMISSION_SHAPE_BOX].
+ [b]Note:[/b] [member emission_box_extents] starts from the center point and applies the X, Y, and Z values in both directions. The size is twice the area of the extents.
</member>
<member name="emission_color_texture" type="Texture2D" setter="set_emission_color_texture" getter="get_emission_color_texture">
Particle color will be modulated by color determined by sampling this texture at the same point as the [member emission_point_texture].
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 1daa1b04e4..0d4d8bbebb 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -254,6 +254,7 @@
Path to an image used as the boot splash. If left empty, the default Godot Engine splash will be displayed instead.
[b]Note:[/b] Only effective if [member application/boot_splash/show_image] is [code]true[/code].
[b]Note:[/b] The only supported format is PNG. Using another image format will result in an error.
+ [b]Note:[/b] The image will also show when opening the project in the editor. If you want to display the default splash image in the editor, add an empty override for [code]editor_hint[/code] feature.
</member>
<member name="application/boot_splash/minimum_display_time" type="int" setter="" getter="" default="0">
Minimum boot splash display time (in milliseconds). It is not recommended to set too high values for this setting.
@@ -797,8 +798,8 @@
<member name="display/window/energy_saving/keep_screen_on" type="bool" setter="" getter="" default="true">
If [code]true[/code], keeps the screen on (even in case of inactivity), so the screensaver does not take over. Works on desktop and mobile platforms.
</member>
- <member name="display/window/energy_saving/keep_screen_on.editor" type="bool" setter="" getter="" default="false">
- Editor-only override for [member display/window/energy_saving/keep_screen_on]. Does not affect exported projects in debug or release mode.
+ <member name="display/window/energy_saving/keep_screen_on.editor_hint" type="bool" setter="" getter="" default="false">
+ Editor-only override for [member display/window/energy_saving/keep_screen_on]. Does not affect running project.
</member>
<member name="display/window/handheld/orientation" type="int" setter="" getter="" default="0">
The default screen orientation to use on mobile devices. See [enum DisplayServer.ScreenOrientation] for possible values.
@@ -1071,6 +1072,9 @@
<member name="gui/timers/tooltip_delay_sec" type="float" setter="" getter="" default="0.5">
Default delay for tooltips (in seconds).
</member>
+ <member name="gui/timers/tooltip_delay_sec.editor_hint" type="float" setter="" getter="" default="0.5">
+ Delay for tooltips in the editor.
+ </member>
<member name="input/ui_accept" type="Dictionary" setter="" getter="">
Default [InputEventAction] to confirm a focused button, menu or list item, or validate input.
[b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified.
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index d7a659c255..5d90cd6b92 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -92,13 +92,13 @@
Sets the exposure values that will be used by the renderers. The normalization amount is used to bake a given Exposure Value (EV) into rendering calculations to reduce the dynamic range of the scene.
The normalization factor can be calculated from exposure value (EV100) as follows:
[codeblock]
- func get_exposure_normalization(float ev100):
- return 1.0 / (pow(2.0, ev100) * 1.2)
+ func get_exposure_normalization(ev100: float):
+ return 1.0 / (pow(2.0, ev100) * 1.2)
[/codeblock]
The exposure value can be calculated from aperture (in f-stops), shutter speed (in seconds), and sensitivity (in ISO) as follows:
[codeblock]
- func get_exposure(float aperture, float shutter_speed, float sensitivity):
- return log2((aperture * aperture) / shutterSpeed * (100.0 / sensitivity))
+ func get_exposure(aperture: float, shutter_speed: float, sensitivity: float):
+ return log((aperture * aperture) / shutter_speed * (100.0 / sensitivity)) / log(2)
[/codeblock]
</description>
</method>
diff --git a/doc/classes/ScriptEditorBase.xml b/doc/classes/ScriptEditorBase.xml
index dca4fe9276..403608355a 100644
--- a/doc/classes/ScriptEditorBase.xml
+++ b/doc/classes/ScriptEditorBase.xml
@@ -71,6 +71,12 @@
Emitted when the user contextual goto and the item is in the same script.
</description>
</signal>
+ <signal name="request_save_previous_state">
+ <param index="0" name="line" type="int" />
+ <description>
+ Emitted when the user changes current script or moves caret by 10 or more columns within the same script.
+ </description>
+ </signal>
<signal name="search_in_files_requested">
<param index="0" name="text" type="String" />
<description>
diff --git a/doc/classes/TabContainer.xml b/doc/classes/TabContainer.xml
index f4d69c3076..090afa0220 100644
--- a/doc/classes/TabContainer.xml
+++ b/doc/classes/TabContainer.xml
@@ -64,6 +64,13 @@
Returns the [Texture2D] for the tab at index [param tab_idx] or [code]null[/code] if the tab has no [Texture2D].
</description>
</method>
+ <method name="get_tab_icon_max_width" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="tab_idx" type="int" />
+ <description>
+ Returns the maximum allowed width of the icon for the tab at index [param tab_idx].
+ </description>
+ </method>
<method name="get_tab_idx_at_point" qualifiers="const">
<return type="int" />
<param index="0" name="point" type="Vector2" />
@@ -164,6 +171,14 @@
Sets an icon for the tab at index [param tab_idx].
</description>
</method>
+ <method name="set_tab_icon_max_width">
+ <return type="void" />
+ <param index="0" name="tab_idx" type="int" />
+ <param index="1" name="width" type="int" />
+ <description>
+ Sets the maximum allowed width of the icon for the tab at index [param tab_idx]. This limit is applied on top of the default size of the icon and on top of [theme_item icon_max_width]. The height is adjusted according to the icon's ratio.
+ </description>
+ </method>
<method name="set_tab_metadata">
<return type="void" />
<param index="0" name="tab_idx" type="int" />
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index d95492479c..43cd1a8aaa 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -546,16 +546,16 @@
The maximum allowed width of the icon in item's cells. This limit is applied on top of the default size of the icon, but before the value set with [method TreeItem.set_icon_max_width]. The height is adjusted according to the icon's ratio.
</theme_item>
<theme_item name="inner_item_margin_bottom" data_type="constant" type="int" default="0">
- The inner bottom margin of an item.
+ The inner bottom margin of a cell.
</theme_item>
<theme_item name="inner_item_margin_left" data_type="constant" type="int" default="0">
- The inner left margin of an item.
+ The inner left margin of a cell.
</theme_item>
<theme_item name="inner_item_margin_right" data_type="constant" type="int" default="0">
- The inner right margin of an item.
+ The inner right margin of a cell.
</theme_item>
<theme_item name="inner_item_margin_top" data_type="constant" type="int" default="0">
- The inner top margin of an item.
+ The inner top margin of a cell.
</theme_item>
<theme_item name="item_margin" data_type="constant" type="int" default="16">
The horizontal margin at the start of an item. This is used when folding is enabled for the item.
diff --git a/doc/classes/XRHandTracker.xml b/doc/classes/XRHandTracker.xml
index 69390df696..636af6625b 100644
--- a/doc/classes/XRHandTracker.xml
+++ b/doc/classes/XRHandTracker.xml
@@ -11,12 +11,6 @@
<link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link>
</tutorials>
<methods>
- <method name="get_hand" qualifiers="const">
- <return type="int" enum="XRHandTracker.Hand" />
- <description>
- Returns the type of hand.
- </description>
- </method>
<method name="get_hand_joint_angular_velocity" qualifiers="const">
<return type="Vector3" />
<param index="0" name="joint" type="int" enum="XRHandTracker.HandJoint" />
@@ -52,13 +46,6 @@
Returns the transform for the given hand joint.
</description>
</method>
- <method name="set_hand">
- <return type="void" />
- <param index="0" name="hand" type="int" enum="XRHandTracker.Hand" />
- <description>
- Sets the type of hand.
- </description>
- </method>
<method name="set_hand_joint_angular_velocity">
<return type="void" />
<param index="0" name="joint" type="int" enum="XRHandTracker.HandJoint" />
@@ -101,6 +88,7 @@
</method>
</methods>
<members>
+ <member name="hand" type="int" setter="set_tracker_hand" getter="get_tracker_hand" overrides="XRPositionalTracker" enum="XRPositionalTracker.TrackerHand" default="1" />
<member name="hand_tracking_source" type="int" setter="set_hand_tracking_source" getter="get_hand_tracking_source" enum="XRHandTracker.HandTrackingSource" default="0">
The source of the hand tracking data.
</member>
@@ -110,15 +98,6 @@
<member name="type" type="int" setter="set_tracker_type" getter="get_tracker_type" overrides="XRTracker" enum="XRServer.TrackerType" default="16" />
</members>
<constants>
- <constant name="HAND_LEFT" value="0" enum="Hand">
- A left hand.
- </constant>
- <constant name="HAND_RIGHT" value="1" enum="Hand">
- A right hand.
- </constant>
- <constant name="HAND_MAX" value="2" enum="Hand">
- Represents the size of the [enum Hand] enum.
- </constant>
<constant name="HAND_TRACKING_SOURCE_UNKNOWN" value="0" enum="HandTrackingSource">
The source of hand tracking data is unknown.
</constant>
diff --git a/drivers/gles3/effects/post_effects.cpp b/drivers/gles3/effects/post_effects.cpp
index 8ad872f319..105c8f6b71 100644
--- a/drivers/gles3/effects/post_effects.cpp
+++ b/drivers/gles3/effects/post_effects.cpp
@@ -87,7 +87,7 @@ void PostEffects::_draw_screen_triangle() {
glBindVertexArray(0);
}
-void PostEffects::post_copy(GLuint p_dest_framebuffer, Size2i p_dest_size, GLuint p_source_color, Size2i p_source_size, float p_luminance_multiplier, const Glow::GLOWLEVEL *p_glow_buffers, float p_glow_intensity, uint32_t p_view, bool p_use_multiview) {
+void PostEffects::post_copy(GLuint p_dest_framebuffer, Size2i p_dest_size, GLuint p_source_color, Size2i p_source_size, float p_luminance_multiplier, const Glow::GLOWLEVEL *p_glow_buffers, float p_glow_intensity, uint32_t p_view, bool p_use_multiview, uint64_t p_spec_constants) {
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glDisable(GL_BLEND);
@@ -96,7 +96,7 @@ void PostEffects::post_copy(GLuint p_dest_framebuffer, Size2i p_dest_size, GLuin
glViewport(0, 0, p_dest_size.x, p_dest_size.y);
PostShaderGLES3::ShaderVariant mode = PostShaderGLES3::MODE_DEFAULT;
- uint64_t flags = 0;
+ uint64_t flags = p_spec_constants;
if (p_use_multiview) {
flags |= PostShaderGLES3::USE_MULTIVIEW;
}
diff --git a/drivers/gles3/effects/post_effects.h b/drivers/gles3/effects/post_effects.h
index b90c77d6c7..916d29a052 100644
--- a/drivers/gles3/effects/post_effects.h
+++ b/drivers/gles3/effects/post_effects.h
@@ -59,7 +59,7 @@ public:
PostEffects();
~PostEffects();
- void post_copy(GLuint p_dest_framebuffer, Size2i p_dest_size, GLuint p_source_color, Size2i p_source_size, float p_luminance_multiplier, const Glow::GLOWLEVEL *p_glow_buffers, float p_glow_intensity, uint32_t p_view = 0, bool p_use_multiview = false);
+ void post_copy(GLuint p_dest_framebuffer, Size2i p_dest_size, GLuint p_source_color, Size2i p_source_size, float p_luminance_multiplier, const Glow::GLOWLEVEL *p_glow_buffers, float p_glow_intensity, uint32_t p_view = 0, bool p_use_multiview = false, uint64_t p_spec_constants = 0);
};
} //namespace GLES3
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index bc1af86938..606bcee775 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -788,7 +788,6 @@ void RasterizerSceneGLES3::_draw_sky(RID p_env, const Projection &p_projection,
}
if (!p_apply_color_adjustments_in_post) {
spec_constants |= SkyShaderGLES3::APPLY_TONEMAPPING;
- // TODO add BCS and color corrections once supported.
}
RS::EnvironmentBG background = environment_get_background(p_env);
@@ -2336,9 +2335,18 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
SceneState::TonemapUBO tonemap_ubo;
if (render_data.environment.is_valid()) {
+ bool use_bcs = environment_get_adjustments_enabled(render_data.environment);
+ if (use_bcs) {
+ apply_color_adjustments_in_post = true;
+ }
+
tonemap_ubo.exposure = environment_get_exposure(render_data.environment);
tonemap_ubo.white = environment_get_white(render_data.environment);
tonemap_ubo.tonemapper = int32_t(environment_get_tone_mapper(render_data.environment));
+
+ tonemap_ubo.brightness = environment_get_adjustments_brightness(render_data.environment);
+ tonemap_ubo.contrast = environment_get_adjustments_contrast(render_data.environment);
+ tonemap_ubo.saturation = environment_get_adjustments_saturation(render_data.environment);
}
if (scene_state.tonemap_buffer == 0) {
@@ -2558,8 +2566,6 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
if (!apply_color_adjustments_in_post) {
spec_constant_base_flags |= SceneShaderGLES3::APPLY_TONEMAPPING;
-
- // TODO add BCS and Color corrections here once supported.
}
}
// Render Opaque Objects.
@@ -2700,6 +2706,29 @@ void RasterizerSceneGLES3::_render_post_processing(const RenderDataGLES3 *p_rend
rb->check_glow_buffers();
}
+ bool use_bcs = environment_get_adjustments_enabled(p_render_data->environment);
+ uint64_t bcs_spec_constants = 0;
+ RID color_correction_texture = environment_get_color_correction(p_render_data->environment);
+ if (use_bcs && color_correction_texture.is_valid()) {
+ bcs_spec_constants |= PostShaderGLES3::USE_BCS;
+ bcs_spec_constants |= PostShaderGLES3::USE_COLOR_CORRECTION;
+
+ bool use_1d_lut = environment_get_use_1d_color_correction(p_render_data->environment);
+ GLenum texture_target = GL_TEXTURE_3D;
+ if (use_1d_lut) {
+ bcs_spec_constants |= PostShaderGLES3::USE_1D_LUT;
+ texture_target = GL_TEXTURE_2D;
+ }
+
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(texture_target, texture_storage->texture_get_texid(color_correction_texture));
+ glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture_target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ }
+
if (view_count == 1) {
// Resolve if needed.
if (fbo_msaa_3d != 0 && msaa3d_needs_resolve) {
@@ -2735,7 +2764,7 @@ void RasterizerSceneGLES3::_render_post_processing(const RenderDataGLES3 *p_rend
}
// Copy color buffer
- post_effects->post_copy(fbo_rt, target_size, color, internal_size, p_render_data->luminance_multiplier, glow_buffers, glow_intensity);
+ post_effects->post_copy(fbo_rt, target_size, color, internal_size, p_render_data->luminance_multiplier, glow_buffers, glow_intensity, 0, false, bcs_spec_constants);
// Copy depth buffer
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_int);
@@ -2803,7 +2832,7 @@ void RasterizerSceneGLES3::_render_post_processing(const RenderDataGLES3 *p_rend
glBindFramebuffer(GL_FRAMEBUFFER, fbos[2]);
glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, write_color, 0, v);
- post_effects->post_copy(fbos[2], target_size, source_color, internal_size, p_render_data->luminance_multiplier, glow_buffers, glow_intensity, v, true);
+ post_effects->post_copy(fbos[2], target_size, source_color, internal_size, p_render_data->luminance_multiplier, glow_buffers, glow_intensity, v, true, bcs_spec_constants);
}
// Copy depth
@@ -2824,6 +2853,9 @@ void RasterizerSceneGLES3::_render_post_processing(const RenderDataGLES3 *p_rend
glBindFramebuffer(GL_FRAMEBUFFER, fbo_rt);
glDeleteFramebuffers(3, fbos);
}
+
+ glActiveTexture(GL_TEXTURE2);
+ glBindTexture(GL_TEXTURE_2D, 0);
}
template <PassMode p_pass_mode>
diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h
index cc479bd4e9..c656ee3cc7 100644
--- a/drivers/gles3/rasterizer_scene_gles3.h
+++ b/drivers/gles3/rasterizer_scene_gles3.h
@@ -434,6 +434,11 @@ private:
float white = 1.0;
int32_t tonemapper = 0;
int32_t pad = 0;
+
+ int32_t pad2 = 0;
+ float brightness = 1.0;
+ float contrast = 1.0;
+ float saturation = 1.0;
};
static_assert(sizeof(TonemapUBO) % 16 == 0, "Tonemap UBO size must be a multiple of 16 bytes");
diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl
index efddbe9ad2..65332c06be 100644
--- a/drivers/gles3/shaders/canvas.glsl
+++ b/drivers/gles3/shaders/canvas.glsl
@@ -776,6 +776,12 @@ void main() {
vec2 tex_uv = (vec4(vertex, 0.0, 1.0) * mat4(light_array[light_base].texture_matrix[0], light_array[light_base].texture_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
vec2 tex_uv_atlas = tex_uv * light_array[light_base].atlas_rect.zw + light_array[light_base].atlas_rect.xy;
+
+ if (any(lessThan(tex_uv, vec2(0.0, 0.0))) || any(greaterThanEqual(tex_uv, vec2(1.0, 1.0)))) {
+ //if outside the light texture, light color is zero
+ continue;
+ }
+
vec4 light_color = textureLod(atlas_texture, tex_uv_atlas, 0.0);
vec4 light_base_color = light_array[light_base].color;
@@ -800,10 +806,6 @@ void main() {
light_color.rgb *= base_color.rgb;
}
#endif
- if (any(lessThan(tex_uv, vec2(0.0, 0.0))) || any(greaterThanEqual(tex_uv, vec2(1.0, 1.0)))) {
- //if outside the light texture, light color is zero
- light_color.a = 0.0;
- }
if (bool(light_array[light_base].flags & LIGHT_FLAGS_HAS_SHADOW)) {
vec2 shadow_pos = (vec4(shadow_vertex, 0.0, 1.0) * mat4(light_array[light_base].shadow_matrix[0], light_array[light_base].shadow_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
diff --git a/drivers/gles3/shaders/effects/post.glsl b/drivers/gles3/shaders/effects/post.glsl
index e61171c92a..1d17510c52 100644
--- a/drivers/gles3/shaders/effects/post.glsl
+++ b/drivers/gles3/shaders/effects/post.glsl
@@ -1,13 +1,15 @@
/* clang-format off */
#[modes]
-mode_default = #define MODE_DEFAULT
-// mode_glow = #define MODE_GLOW
+mode_default =
#[specializations]
USE_MULTIVIEW = false
USE_GLOW = false
USE_LUMINANCE_MULTIPLIER = false
+USE_BCS = false
+USE_COLOR_CORRECTION = false
+USE_1D_LUT = false
#[vertex]
layout(location = 0) in vec2 vertex_attrib;
@@ -25,6 +27,9 @@ void main() {
#[fragment]
/* clang-format on */
+// If we reach this code, we always tonemap.
+#define APPLY_TONEMAPPING
+
#include "../tonemap_inc.glsl"
#ifdef USE_MULTIVIEW
@@ -57,6 +62,35 @@ vec4 get_glow_color(vec2 uv) {
}
#endif // USE_GLOW
+#ifdef USE_COLOR_CORRECTION
+#ifdef USE_1D_LUT
+uniform sampler2D source_color_correction; //texunit:2
+
+vec3 apply_color_correction(vec3 color) {
+ color.r = texture(source_color_correction, vec2(color.r, 0.0f)).r;
+ color.g = texture(source_color_correction, vec2(color.g, 0.0f)).g;
+ color.b = texture(source_color_correction, vec2(color.b, 0.0f)).b;
+ return color;
+}
+#else
+uniform sampler3D source_color_correction; //texunit:2
+
+vec3 apply_color_correction(vec3 color) {
+ return textureLod(source_color_correction, color, 0.0).rgb;
+}
+#endif // USE_1D_LUT
+#endif // USE_COLOR_CORRECTION
+
+#ifdef USE_BCS
+vec3 apply_bcs(vec3 color) {
+ color = mix(vec3(0.0), color, brightness);
+ color = mix(vec3(0.5), color, contrast);
+ color = mix(vec3(dot(vec3(1.0), color) * 0.33333), color, saturation);
+
+ return color;
+}
+#endif
+
in vec2 uv_interp;
layout(location = 0) out vec4 frag_color;
@@ -85,11 +119,11 @@ void main() {
color.rgb = linear_to_srgb(color.rgb);
#ifdef USE_BCS
- color.rgb = apply_bcs(color.rgb, bcs);
+ color.rgb = apply_bcs(color.rgb);
#endif
#ifdef USE_COLOR_CORRECTION
- color.rgb = apply_color_correction(color.rgb, color_correction);
+ color.rgb = apply_color_correction(color.rgb);
#endif
frag_color = color;
diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl
index 36bbca8728..797b9066a9 100644
--- a/drivers/gles3/shaders/scene.glsl
+++ b/drivers/gles3/shaders/scene.glsl
@@ -1511,16 +1511,31 @@ void main() {
float alpha_antialiasing_edge = 0.0;
vec2 alpha_texture_coordinate = vec2(0.0, 0.0);
#endif // ALPHA_ANTIALIASING_EDGE_USED
+
+#ifdef LIGHT_VERTEX_USED
+ vec3 light_vertex = vertex;
+#endif //LIGHT_VERTEX_USED
+
{
#CODE : FRAGMENT
}
+#ifdef LIGHT_VERTEX_USED
+ vertex = light_vertex;
+#ifdef USE_MULTIVIEW
+ view = -normalize(vertex - eye_offset);
+#else
+ view = -normalize(vertex);
+#endif //USE_MULTIVIEW
+#endif //LIGHT_VERTEX_USED
+
#ifndef USE_SHADOW_TO_OPACITY
#if defined(ALPHA_SCISSOR_USED)
if (alpha < alpha_scissor_threshold) {
discard;
}
+ alpha = 1.0;
#else
#ifdef MODE_RENDER_DEPTH
#ifdef USE_OPAQUE_PREPASS
@@ -1844,23 +1859,16 @@ void main() {
#endif // !MODE_RENDER_DEPTH
#if defined(USE_SHADOW_TO_OPACITY)
+#ifndef MODE_RENDER_DEPTH
alpha = min(alpha, clamp(length(ambient_light), 0.0, 1.0));
#if defined(ALPHA_SCISSOR_USED)
if (alpha < alpha_scissor) {
discard;
}
-#else
-#ifdef MODE_RENDER_DEPTH
-#ifdef USE_OPAQUE_PREPASS
-
- if (alpha < opaque_prepass_threshold) {
- discard;
- }
-#endif // USE_OPAQUE_PREPASS
-#endif // MODE_RENDER_DEPTH
#endif // !ALPHA_SCISSOR_USED
+#endif // !MODE_RENDER_DEPTH
#endif // USE_SHADOW_TO_OPACITY
#ifdef MODE_RENDER_DEPTH
@@ -1920,13 +1928,6 @@ void main() {
#endif
frag_color.rgb = linear_to_srgb(frag_color.rgb);
-#ifdef USE_BCS
- frag_color.rgb = apply_bcs(frag_color.rgb, bcs);
-#endif
-
-#ifdef USE_COLOR_CORRECTION
- frag_color.rgb = apply_color_correction(frag_color.rgb, color_correction);
-#endif
#else // !BASE_PASS
frag_color = vec4(0.0, 0.0, 0.0, alpha);
#endif // !BASE_PASS
@@ -2138,14 +2139,6 @@ void main() {
#endif
additive_light_color = linear_to_srgb(additive_light_color);
-#ifdef USE_BCS
- additive_light_color = apply_bcs(additive_light_color, bcs);
-#endif
-
-#ifdef USE_COLOR_CORRECTION
- additive_light_color = apply_color_correction(additive_light_color, color_correction);
-#endif
-
frag_color.rgb += additive_light_color;
#endif // USE_ADDITIVE_LIGHTING
diff --git a/drivers/gles3/shaders/sky.glsl b/drivers/gles3/shaders/sky.glsl
index 6c33bf7123..26549901a6 100644
--- a/drivers/gles3/shaders/sky.glsl
+++ b/drivers/gles3/shaders/sky.glsl
@@ -209,14 +209,6 @@ void main() {
#endif
color = linear_to_srgb(color);
-#ifdef USE_BCS
- color = apply_bcs(color, bcs);
-#endif
-
-#ifdef USE_COLOR_CORRECTION
- color = apply_color_correction(color, color_correction);
-#endif
-
frag_color.rgb = color * luminance_multiplier;
frag_color.a = alpha;
diff --git a/drivers/gles3/shaders/tonemap.glsl b/drivers/gles3/shaders/tonemap.glsl
deleted file mode 100644
index 0b769e77f2..0000000000
--- a/drivers/gles3/shaders/tonemap.glsl
+++ /dev/null
@@ -1,333 +0,0 @@
-/* clang-format off */
-[vertex]
-
-#ifdef USE_GLES_OVER_GL
-#define lowp
-#define mediump
-#define highp
-#else
-precision highp float;
-precision highp int;
-#endif
-
-layout(location = 0) in vec2 vertex_attrib;
-/* clang-format on */
-layout(location = 4) in vec2 uv_in;
-
-out vec2 uv_interp;
-
-void main() {
- gl_Position = vec4(vertex_attrib, 0.0, 1.0);
-
- uv_interp = uv_in;
-}
-
-/* clang-format off */
-[fragment]
-
-#ifdef USE_GLES_OVER_GL
-#define lowp
-#define mediump
-#define highp
-#else
-#if defined(USE_HIGHP_PRECISION)
-precision highp float;
-precision highp int;
-#else
-precision mediump float;
-precision mediump int;
-#endif
-#endif
-
-in vec2 uv_interp;
-/* clang-format on */
-
-layout(location = 0) out vec4 frag_color;
-
-#ifdef USE_MULTIVIEW
-uniform highp sampler2DArray source; //texunit:0
-#else
-uniform highp sampler2D source; //texunit:0
-#endif
-
-#if defined(USE_GLOW_LEVEL1) || defined(USE_GLOW_LEVEL2) || defined(USE_GLOW_LEVEL3) || defined(USE_GLOW_LEVEL4) || defined(USE_GLOW_LEVEL5) || defined(USE_GLOW_LEVEL6) || defined(USE_GLOW_LEVEL7)
-#define USING_GLOW // only use glow when at least one glow level is selected
-
-#ifdef USE_MULTI_TEXTURE_GLOW
-uniform highp sampler2D source_glow1; //texunit:2
-uniform highp sampler2D source_glow2; //texunit:3
-uniform highp sampler2D source_glow3; //texunit:4
-uniform highp sampler2D source_glow4; //texunit:5
-uniform highp sampler2D source_glow5; //texunit:6
-uniform highp sampler2D source_glow6; //texunit:7
-#ifdef USE_GLOW_LEVEL7
-uniform highp sampler2D source_glow7; //texunit:8
-#endif
-#else
-uniform highp sampler2D source_glow; //texunit:2
-#endif
-uniform highp float glow_intensity;
-#endif
-
-#ifdef USE_BCS
-uniform vec3 bcs;
-#endif
-
-#ifdef USE_FXAA
-uniform vec2 pixel_size;
-#endif
-
-#ifdef USE_COLOR_CORRECTION
-uniform sampler2D color_correction; //texunit:1
-#endif
-
-#ifdef USE_GLOW_FILTER_BICUBIC
-// w0, w1, w2, and w3 are the four cubic B-spline basis functions
-float w0(float a) {
- return (1.0 / 6.0) * (a * (a * (-a + 3.0) - 3.0) + 1.0);
-}
-
-float w1(float a) {
- return (1.0 / 6.0) * (a * a * (3.0 * a - 6.0) + 4.0);
-}
-
-float w2(float a) {
- return (1.0 / 6.0) * (a * (a * (-3.0 * a + 3.0) + 3.0) + 1.0);
-}
-
-float w3(float a) {
- return (1.0 / 6.0) * (a * a * a);
-}
-
-// g0 and g1 are the two amplitude functions
-float g0(float a) {
- return w0(a) + w1(a);
-}
-
-float g1(float a) {
- return w2(a) + w3(a);
-}
-
-// h0 and h1 are the two offset functions
-float h0(float a) {
- return -1.0 + w1(a) / (w0(a) + w1(a));
-}
-
-float h1(float a) {
- return 1.0 + w3(a) / (w2(a) + w3(a));
-}
-
-uniform ivec2 glow_texture_size;
-
-vec4 texture_bicubic(sampler2D tex, vec2 uv, int p_lod) {
- float lod = float(p_lod);
- vec2 tex_size = vec2(glow_texture_size >> p_lod);
- vec2 texel_size = vec2(1.0) / tex_size;
-
- uv = uv * tex_size + vec2(0.5);
-
- vec2 iuv = floor(uv);
- vec2 fuv = fract(uv);
-
- float g0x = g0(fuv.x);
- float g1x = g1(fuv.x);
- float h0x = h0(fuv.x);
- float h1x = h1(fuv.x);
- float h0y = h0(fuv.y);
- float h1y = h1(fuv.y);
-
- vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5)) * texel_size;
- vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5)) * texel_size;
- vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5)) * texel_size;
- vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5)) * texel_size;
-
- return (g0(fuv.y) * (g0x * textureLod(tex, p0, lod) + g1x * textureLod(tex, p1, lod))) +
- (g1(fuv.y) * (g0x * textureLod(tex, p2, lod) + g1x * textureLod(tex, p3, lod)));
-}
-
-#define GLOW_TEXTURE_SAMPLE(m_tex, m_uv, m_lod) texture_bicubic(m_tex, m_uv, m_lod)
-#else //!USE_GLOW_FILTER_BICUBIC
-#define GLOW_TEXTURE_SAMPLE(m_tex, m_uv, m_lod) textureLod(m_tex, m_uv, float(m_lod))
-#endif //USE_GLOW_FILTER_BICUBIC
-
-vec3 apply_glow(vec3 color, vec3 glow) { // apply glow using the selected blending mode
-#ifdef USE_GLOW_REPLACE
- color = glow;
-#endif
-
-#ifdef USE_GLOW_SCREEN
- color = max((color + glow) - (color * glow), vec3(0.0));
-#endif
-
-#ifdef USE_GLOW_SOFTLIGHT
- glow = glow * vec3(0.5) + vec3(0.5);
-
- color.r = (glow.r <= 0.5) ? (color.r - (1.0 - 2.0 * glow.r) * color.r * (1.0 - color.r)) : (((glow.r > 0.5) && (color.r <= 0.25)) ? (color.r + (2.0 * glow.r - 1.0) * (4.0 * color.r * (4.0 * color.r + 1.0) * (color.r - 1.0) + 7.0 * color.r)) : (color.r + (2.0 * glow.r - 1.0) * (sqrt(color.r) - color.r)));
- color.g = (glow.g <= 0.5) ? (color.g - (1.0 - 2.0 * glow.g) * color.g * (1.0 - color.g)) : (((glow.g > 0.5) && (color.g <= 0.25)) ? (color.g + (2.0 * glow.g - 1.0) * (4.0 * color.g * (4.0 * color.g + 1.0) * (color.g - 1.0) + 7.0 * color.g)) : (color.g + (2.0 * glow.g - 1.0) * (sqrt(color.g) - color.g)));
- color.b = (glow.b <= 0.5) ? (color.b - (1.0 - 2.0 * glow.b) * color.b * (1.0 - color.b)) : (((glow.b > 0.5) && (color.b <= 0.25)) ? (color.b + (2.0 * glow.b - 1.0) * (4.0 * color.b * (4.0 * color.b + 1.0) * (color.b - 1.0) + 7.0 * color.b)) : (color.b + (2.0 * glow.b - 1.0) * (sqrt(color.b) - color.b)));
-#endif
-
-#if !defined(USE_GLOW_SCREEN) && !defined(USE_GLOW_SOFTLIGHT) && !defined(USE_GLOW_REPLACE) // no other selected -> additive
- color += glow;
-#endif
-
- return color;
-}
-
-vec3 apply_bcs(vec3 color, vec3 bcs) {
- color = mix(vec3(0.0), color, bcs.x);
- color = mix(vec3(0.5), color, bcs.y);
- color = mix(vec3(dot(vec3(1.0), color) * 0.33333), color, bcs.z);
-
- return color;
-}
-
-vec3 apply_color_correction(vec3 color, sampler2D correction_tex) {
- color.r = texture(correction_tex, vec2(color.r, 0.0)).r;
- color.g = texture(correction_tex, vec2(color.g, 0.0)).g;
- color.b = texture(correction_tex, vec2(color.b, 0.0)).b;
-
- return color;
-}
-
-vec3 apply_fxaa(vec3 color, vec2 uv_interp, vec2 pixel_size) {
- const float FXAA_REDUCE_MIN = (1.0 / 128.0);
- const float FXAA_REDUCE_MUL = (1.0 / 8.0);
- const float FXAA_SPAN_MAX = 8.0;
-
-#ifdef USE_MULTIVIEW
- vec3 rgbNW = textureLod(source, vec3(uv_interp + vec2(-1.0, -1.0) * pixel_size, ViewIndex), 0.0).xyz;
- vec3 rgbNE = textureLod(source, vec3(uv_interp + vec2(1.0, -1.0) * pixel_size, ViewIndex), 0.0).xyz;
- vec3 rgbSW = textureLod(source, vec3(uv_interp + vec2(-1.0, 1.0) * pixel_size, ViewIndex), 0.0).xyz;
- vec3 rgbSE = textureLod(source, vec3(uv_interp + vec2(1.0, 1.0) * pixel_size, ViewIndex), 0.0).xyz;
-#else
- vec3 rgbNW = textureLod(source, uv_interp + vec2(-1.0, -1.0) * pixel_size, 0.0).xyz;
- vec3 rgbNE = textureLod(source, uv_interp + vec2(1.0, -1.0) * pixel_size, 0.0).xyz;
- vec3 rgbSW = textureLod(source, uv_interp + vec2(-1.0, 1.0) * pixel_size, 0.0).xyz;
- vec3 rgbSE = textureLod(source, uv_interp + vec2(1.0, 1.0) * pixel_size, 0.0).xyz;
-#endif
- vec3 rgbM = color;
- vec3 luma = vec3(0.299, 0.587, 0.114);
- float lumaNW = dot(rgbNW, luma);
- float lumaNE = dot(rgbNE, luma);
- float lumaSW = dot(rgbSW, luma);
- float lumaSE = dot(rgbSE, luma);
- float lumaM = dot(rgbM, luma);
- float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
- float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
-
- vec2 dir;
- dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
- dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
-
- float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) *
- (0.25 * FXAA_REDUCE_MUL),
- FXAA_REDUCE_MIN);
-
- float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
- dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX),
- max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
- dir * rcpDirMin)) *
- pixel_size;
-
-#ifdef USE_MULTIVIEW
- vec3 rgbA = 0.5 * (textureLod(source, vec3(uv_interp + dir * (1.0 / 3.0 - 0.5), ViewIndex), 0.0).xyz + textureLod(source, vec3(uv_interp + dir * (2.0 / 3.0 - 0.5), ViewIndex), 0.0).xyz);
- vec3 rgbB = rgbA * 0.5 + 0.25 * (textureLod(source, vec3(uv_interp + dir * -0.5, ViewIndex), 0.0).xyz + textureLod(source, vec3(uv_interp + dir * 0.5, ViewIndex), 0.0).xyz);
-#else
- vec3 rgbA = 0.5 * (textureLod(source, uv_interp + dir * (1.0 / 3.0 - 0.5), 0.0).xyz + textureLod(source, uv_interp + dir * (2.0 / 3.0 - 0.5), 0.0).xyz);
- vec3 rgbB = rgbA * 0.5 + 0.25 * (textureLod(source, uv_interp + dir * -0.5, 0.0).xyz + textureLod(source, uv_interp + dir * 0.5, 0.0).xyz);
-#endif
-
- float lumaB = dot(rgbB, luma);
- if ((lumaB < lumaMin) || (lumaB > lumaMax)) {
- return rgbA;
- } else {
- return rgbB;
- }
-}
-
-void main() {
-#ifdef USE_MULTIVIEW
- vec4 color = textureLod(source, vec3(uv_interp, ViewIndex), 0.0);
-#else
- vec4 color = textureLod(source, uv_interp, 0.0);
-#endif
-
-#ifdef USE_FXAA
- color.rgb = apply_fxaa(color.rgb, uv_interp, pixel_size);
-#endif
-
- // Glow
-
-#ifdef USING_GLOW
- vec3 glow = vec3(0.0);
-#ifdef USE_MULTI_TEXTURE_GLOW
-#ifdef USE_GLOW_LEVEL1
- glow += GLOW_TEXTURE_SAMPLE(source_glow1, uv_interp, 0).rgb;
-#ifdef USE_GLOW_LEVEL2
- glow += GLOW_TEXTURE_SAMPLE(source_glow2, uv_interp, 0).rgb;
-#ifdef USE_GLOW_LEVEL3
- glow += GLOW_TEXTURE_SAMPLE(source_glow3, uv_interp, 0).rgb;
-#ifdef USE_GLOW_LEVEL4
- glow += GLOW_TEXTURE_SAMPLE(source_glow4, uv_interp, 0).rgb;
-#ifdef USE_GLOW_LEVEL5
- glow += GLOW_TEXTURE_SAMPLE(source_glow5, uv_interp, 0).rgb;
-#ifdef USE_GLOW_LEVEL6
- glow += GLOW_TEXTURE_SAMPLE(source_glow6, uv_interp, 0).rgb;
-#ifdef USE_GLOW_LEVEL7
- glow += GLOW_TEXTURE_SAMPLE(source_glow7, uv_interp, 0).rgb;
-#endif
-#endif
-#endif
-#endif
-#endif
-#endif
-#endif
-
-#else
-
-#ifdef USE_GLOW_LEVEL1
- glow += GLOW_TEXTURE_SAMPLE(source_glow, uv_interp, 1).rgb;
-#endif
-
-#ifdef USE_GLOW_LEVEL2
- glow += GLOW_TEXTURE_SAMPLE(source_glow, uv_interp, 2).rgb;
-#endif
-
-#ifdef USE_GLOW_LEVEL3
- glow += GLOW_TEXTURE_SAMPLE(source_glow, uv_interp, 3).rgb;
-#endif
-
-#ifdef USE_GLOW_LEVEL4
- glow += GLOW_TEXTURE_SAMPLE(source_glow, uv_interp, 4).rgb;
-#endif
-
-#ifdef USE_GLOW_LEVEL5
- glow += GLOW_TEXTURE_SAMPLE(source_glow, uv_interp, 5).rgb;
-#endif
-
-#ifdef USE_GLOW_LEVEL6
- glow += GLOW_TEXTURE_SAMPLE(source_glow, uv_interp, 6).rgb;
-#endif
-
-#ifdef USE_GLOW_LEVEL7
- glow += GLOW_TEXTURE_SAMPLE(source_glow, uv_interp, 7).rgb;
-#endif
-#endif //USE_MULTI_TEXTURE_GLOW
-
- glow *= glow_intensity;
- color.rgb = apply_glow(color.rgb, glow);
-#endif
-
- // Additional effects
-
-#ifdef USE_BCS
- color.rgb = apply_bcs(color.rgb, bcs);
-#endif
-
-#ifdef USE_COLOR_CORRECTION
- color.rgb = apply_color_correction(color.rgb, color_correction);
-#endif
-
- frag_color = color;
-}
diff --git a/drivers/gles3/shaders/tonemap_inc.glsl b/drivers/gles3/shaders/tonemap_inc.glsl
index f8f12760ec..fb915aeb38 100644
--- a/drivers/gles3/shaders/tonemap_inc.glsl
+++ b/drivers/gles3/shaders/tonemap_inc.glsl
@@ -1,43 +1,31 @@
-#ifdef USE_BCS
-uniform vec3 bcs;
-#endif
-
-#ifdef USE_COLOR_CORRECTION
-#ifdef USE_1D_LUT
-uniform sampler2D source_color_correction; //texunit:-1
-#else
-uniform sampler3D source_color_correction; //texunit:-1
-#endif
-#endif
-
layout(std140) uniform TonemapData { //ubo:0
float exposure;
float white;
int tonemapper;
int pad;
-};
-vec3 apply_bcs(vec3 color, vec3 bcs) {
- color = mix(vec3(0.0), color, bcs.x);
- color = mix(vec3(0.5), color, bcs.y);
- color = mix(vec3(dot(vec3(1.0), color) * 0.33333), color, bcs.z);
+ int pad2;
+ float brightness;
+ float contrast;
+ float saturation;
+};
- return color;
-}
-#ifdef USE_COLOR_CORRECTION
-#ifdef USE_1D_LUT
-vec3 apply_color_correction(vec3 color) {
- color.r = texture(source_color_correction, vec2(color.r, 0.0f)).r;
- color.g = texture(source_color_correction, vec2(color.g, 0.0f)).g;
- color.b = texture(source_color_correction, vec2(color.b, 0.0f)).b;
- return color;
+// This expects 0-1 range input.
+vec3 linear_to_srgb(vec3 color) {
+ //color = clamp(color, vec3(0.0), vec3(1.0));
+ //const vec3 a = vec3(0.055f);
+ //return mix((vec3(1.0f) + a) * pow(color.rgb, vec3(1.0f / 2.4f)) - a, 12.92f * color.rgb, lessThan(color.rgb, vec3(0.0031308f)));
+ // Approximation from http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
+ return max(vec3(1.055) * pow(color, vec3(0.416666667)) - vec3(0.055), vec3(0.0));
}
-#else
-vec3 apply_color_correction(vec3 color) {
- return textureLod(source_color_correction, color, 0.0).rgb;
+
+// This expects 0-1 range input, outside that range it behaves poorly.
+vec3 srgb_to_linear(vec3 color) {
+ // Approximation from http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
+ return color * (color * (color * 0.305306011 + 0.682171111) + 0.012522878);
}
-#endif
-#endif
+
+#ifdef APPLY_TONEMAPPING
vec3 tonemap_filmic(vec3 color, float p_white) {
// exposure bias: input scale (color *= bias, white *= bias) to make the brightness consistent with other tonemappers
@@ -92,21 +80,6 @@ vec3 tonemap_reinhard(vec3 color, float p_white) {
return (p_white * color + color) / (color * p_white + p_white);
}
-// This expects 0-1 range input.
-vec3 linear_to_srgb(vec3 color) {
- //color = clamp(color, vec3(0.0), vec3(1.0));
- //const vec3 a = vec3(0.055f);
- //return mix((vec3(1.0f) + a) * pow(color.rgb, vec3(1.0f / 2.4f)) - a, 12.92f * color.rgb, lessThan(color.rgb, vec3(0.0031308f)));
- // Approximation from http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
- return max(vec3(1.055) * pow(color, vec3(0.416666667)) - vec3(0.055), vec3(0.0));
-}
-
-// This expects 0-1 range input, outside that range it behaves poorly.
-vec3 srgb_to_linear(vec3 color) {
- // Approximation from http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html
- return color * (color * (color * 0.305306011 + 0.682171111) + 0.012522878);
-}
-
#define TONEMAPPER_LINEAR 0
#define TONEMAPPER_REINHARD 1
#define TONEMAPPER_FILMIC 2
@@ -125,3 +98,5 @@ vec3 apply_tonemapping(vec3 color, float p_white) { // inputs are LINEAR, always
return tonemap_aces(max(vec3(0.0f), color), p_white);
}
}
+
+#endif // APPLY_TONEMAPPING
diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp
index 23376b4381..62d22dac4d 100644
--- a/drivers/gles3/storage/material_storage.cpp
+++ b/drivers/gles3/storage/material_storage.cpp
@@ -1274,6 +1274,7 @@ MaterialStorage::MaterialStorage() {
actions.renames["CUSTOM2"] = "custom2_attrib";
actions.renames["CUSTOM3"] = "custom3_attrib";
actions.renames["OUTPUT_IS_SRGB"] = "SHADER_IS_SRGB";
+ actions.renames["LIGHT_VERTEX"] = "light_vertex";
actions.renames["NODE_POSITION_WORLD"] = "model_matrix[3].xyz";
actions.renames["CAMERA_POSITION_WORLD"] = "scene_data.inv_view_matrix[3].xyz";
@@ -1320,6 +1321,7 @@ MaterialStorage::MaterialStorage() {
actions.usage_defines["COLOR"] = "#define COLOR_USED\n";
actions.usage_defines["INSTANCE_CUSTOM"] = "#define ENABLE_INSTANCE_CUSTOM\n";
actions.usage_defines["POSITION"] = "#define OVERRIDE_POSITION\n";
+ actions.usage_defines["LIGHT_VERTEX"] = "#define LIGHT_VERTEX_USED\n";
actions.usage_defines["ALPHA_SCISSOR_THRESHOLD"] = "#define ALPHA_SCISSOR_USED\n";
actions.usage_defines["ALPHA_HASH_SCALE"] = "#define ALPHA_HASH_USED\n";
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp
index 1906d168fe..803555cb07 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp
@@ -1008,7 +1008,7 @@ VkResult RenderingDeviceDriverVulkan::_create_render_pass(VkDevice p_device, con
const uint32_t depth_attachment_index = vector_base_index + 3;
_convert_subpass_attachments(p_create_info->pSubpasses[i].pInputAttachments, p_create_info->pSubpasses[i].inputAttachmentCount, subpasses_attachments[input_attachments_index]);
_convert_subpass_attachments(p_create_info->pSubpasses[i].pColorAttachments, p_create_info->pSubpasses[i].colorAttachmentCount, subpasses_attachments[color_attachments_index]);
- _convert_subpass_attachments(p_create_info->pSubpasses[i].pResolveAttachments, p_create_info->pSubpasses[i].colorAttachmentCount, subpasses_attachments[resolve_attachments_index]);
+ _convert_subpass_attachments(p_create_info->pSubpasses[i].pResolveAttachments, (p_create_info->pSubpasses[i].pResolveAttachments != nullptr) ? p_create_info->pSubpasses[i].colorAttachmentCount : 0, subpasses_attachments[resolve_attachments_index]);
_convert_subpass_attachments(p_create_info->pSubpasses[i].pDepthStencilAttachment, (p_create_info->pSubpasses[i].pDepthStencilAttachment != nullptr) ? 1 : 0, subpasses_attachments[depth_attachment_index]);
// Ignores sType and pNext from the subpass.
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 779080786a..49896d66d8 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -1613,13 +1613,26 @@ Variant CodeTextEditor::get_edit_state() {
return state;
}
+Variant CodeTextEditor::get_previous_state() {
+ return previous_state;
+}
+
+void CodeTextEditor::store_previous_state() {
+ previous_state = get_navigation_state();
+}
+
void CodeTextEditor::set_edit_state(const Variant &p_state) {
Dictionary state = p_state;
/* update the row first as it sets the column to 0 */
text_editor->set_caret_line(state["row"]);
text_editor->set_caret_column(state["column"]);
- text_editor->set_v_scroll(state["scroll_position"]);
+ if (int(state["scroll_position"]) == -1) {
+ // Special case for previous state.
+ text_editor->center_viewport_to_caret();
+ } else {
+ text_editor->set_v_scroll(state["scroll_position"]);
+ }
text_editor->set_h_scroll(state["h_scroll_position"]);
if (state.get("selection", false)) {
@@ -1648,6 +1661,10 @@ void CodeTextEditor::set_edit_state(const Variant &p_state) {
text_editor->set_line_as_bookmarked(bookmarks[i], true);
}
}
+
+ if (previous_state.is_empty()) {
+ previous_state = p_state;
+ }
}
Variant CodeTextEditor::get_navigation_state() {
diff --git a/editor/code_editor.h b/editor/code_editor.h
index 64b13b9006..c36eedb580 100644
--- a/editor/code_editor.h
+++ b/editor/code_editor.h
@@ -174,6 +174,8 @@ class CodeTextEditor : public VBoxContainer {
int error_line;
int error_column;
+ Dictionary previous_state;
+
void _update_text_editor_theme();
void _update_font_ligatures();
void _complete_request();
@@ -254,6 +256,8 @@ public:
Variant get_edit_state();
void set_edit_state(const Variant &p_state);
Variant get_navigation_state();
+ Variant get_previous_state();
+ void store_previous_state();
void set_error_count(int p_error_count);
void set_warning_count(int p_warning_count);
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 5cc09b7104..63c2ebe3d9 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -287,6 +287,7 @@ void EditorHelp::_class_desc_select(const String &p_select) {
if (table->has(link)) {
// Found in the current page.
if (class_desc->is_ready()) {
+ emit_signal(SNAME("request_save_history"));
class_desc->scroll_to_paragraph((*table)[link]);
} else {
scroll_to = (*table)[link];
@@ -3077,6 +3078,7 @@ void EditorHelp::_bind_methods() {
ClassDB::bind_method("_help_callback", &EditorHelp::_help_callback);
ADD_SIGNAL(MethodInfo("go_to_help"));
+ ADD_SIGNAL(MethodInfo("request_save_history"));
}
void EditorHelp::init_gdext_pointers() {
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 5b24fb1559..134733fa11 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -456,6 +456,9 @@ void EditorNode::_update_from_settings() {
void EditorNode::_gdextensions_reloaded() {
// In case the developer is inspecting an object that will be changed by the reload.
InspectorDock::get_inspector_singleton()->update_tree();
+
+ // Regenerate documentation.
+ EditorHelp::generate_doc();
}
void EditorNode::_select_default_main_screen_plugin() {
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index ea364d8a0d..2964fb364b 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -3408,7 +3408,7 @@ void EditorPropertyResource::update_property() {
}
}
- resource_picker->set_edited_resource(res);
+ resource_picker->set_edited_resource_no_check(res);
}
void EditorPropertyResource::collapse_all_folding() {
diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp
index eee589489d..9e4bf2b7d9 100644
--- a/editor/editor_resource_picker.cpp
+++ b/editor/editor_resource_picker.cpp
@@ -896,7 +896,10 @@ void EditorResourcePicker::set_edited_resource(Ref<Resource> p_resource) {
ERR_FAIL_MSG(vformat("Failed to set a resource of the type '%s' because this EditorResourcePicker only accepts '%s' and its derivatives.", class_str, base_type));
}
}
+ set_edited_resource_no_check(p_resource);
+}
+void EditorResourcePicker::set_edited_resource_no_check(Ref<Resource> p_resource) {
edited_resource = p_resource;
_update_resource();
}
diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h
index 8146c02dff..333d9c9915 100644
--- a/editor/editor_resource_picker.h
+++ b/editor/editor_resource_picker.h
@@ -127,6 +127,7 @@ public:
Vector<String> get_allowed_types() const;
void set_edited_resource(Ref<Resource> p_resource);
+ void set_edited_resource_no_check(Ref<Resource> p_resource);
Ref<Resource> get_edited_resource();
void set_toggle_mode(bool p_enable);
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 33fdd7418e..aa44189782 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -510,6 +510,12 @@ HashSet<String> EditorExportPlatform::get_features(const Ref<EditorExportPreset>
result.insert("template_release");
}
+#ifdef REAL_T_IS_DOUBLE
+ result.insert("double");
+#else
+ result.insert("single");
+#endif // REAL_T_IS_DOUBLE
+
if (!p_preset->get_custom_features().is_empty()) {
Vector<String> tmp_custom_list = p_preset->get_custom_features().split(",");
diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp
index 038e357ce2..c995e590f1 100644
--- a/editor/export/project_export.cpp
+++ b/editor/export/project_export.cpp
@@ -419,6 +419,12 @@ void ProjectExportDialog::_update_feature_list() {
feature_set.insert(E);
}
+#ifdef REAL_T_IS_DOUBLE
+ feature_set.insert("double");
+#else
+ feature_set.insert("single");
+#endif // REAL_T_IS_DOUBLE
+
custom_feature_display->clear();
String text;
bool first = true;
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index b7deb6afa6..3f96d934a8 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -2394,6 +2394,12 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
}
} break;
+ case FILE_SHOW_IN_FILESYSTEM: {
+ if (!p_selected.is_empty()) {
+ navigate_to_path(p_selected[0]);
+ }
+ } break;
+
case FILE_DEPENDENCIES: {
// Checkout the file dependencies.
if (!p_selected.is_empty()) {
@@ -3286,8 +3292,33 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect
if (p_paths.size() == 1) {
const String &fpath = p_paths[0];
+ bool added_separator = false;
+
+ if (favorites_list.has(fpath)) {
+ TreeItem *favorites_item = tree->get_root()->get_first_child();
+ TreeItem *cursor_item = tree->get_selected();
+ bool is_item_in_favorites = false;
+ while (cursor_item != nullptr) {
+ if (cursor_item == favorites_item) {
+ is_item_in_favorites = true;
+ break;
+ }
+
+ cursor_item = cursor_item->get_parent();
+ }
+
+ if (is_item_in_favorites) {
+ p_popup->add_separator();
+ added_separator = true;
+ p_popup->add_icon_item(get_editor_theme_icon(SNAME("ShowInFileSystem")), TTR("Show in FileSystem"), FILE_SHOW_IN_FILESYSTEM);
+ }
+ }
+
#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
- p_popup->add_separator();
+ if (!added_separator) {
+ p_popup->add_separator();
+ added_separator = true;
+ }
// Opening the system file manager is not supported on the Android and web editors.
const bool is_directory = fpath.ends_with("/");
@@ -4153,6 +4184,7 @@ FileSystemDock::FileSystemDock() {
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &FileSystemDock::_project_settings_changed));
add_resource_tooltip_plugin(memnew(EditorTextureTooltipPlugin));
+ add_resource_tooltip_plugin(memnew(EditorAudioStreamTooltipPlugin));
}
FileSystemDock::~FileSystemDock() {
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index 058886c91a..7449657c06 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -116,6 +116,7 @@ private:
FILE_INSTANTIATE,
FILE_ADD_FAVORITE,
FILE_REMOVE_FAVORITE,
+ FILE_SHOW_IN_FILESYSTEM,
FILE_DEPENDENCIES,
FILE_OWNERS,
FILE_MOVE,
diff --git a/editor/gui/editor_dir_dialog.cpp b/editor/gui/editor_dir_dialog.cpp
index 9d5464210b..08612f419c 100644
--- a/editor/gui/editor_dir_dialog.cpp
+++ b/editor/gui/editor_dir_dialog.cpp
@@ -117,23 +117,13 @@ void EditorDirDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
FileSystemDock::get_singleton()->connect("folder_color_changed", callable_mp(this, &EditorDirDialog::reload).bind(""));
-
EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &EditorDirDialog::reload).bind(""));
reload();
-
- if (!tree->is_connected("item_collapsed", callable_mp(this, &EditorDirDialog::_item_collapsed))) {
- tree->connect("item_collapsed", callable_mp(this, &EditorDirDialog::_item_collapsed), CONNECT_DEFERRED);
- }
-
- if (!EditorFileSystem::get_singleton()->is_connected("filesystem_changed", callable_mp(this, &EditorDirDialog::reload))) {
- EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &EditorDirDialog::reload).bind(""));
- }
} break;
case NOTIFICATION_EXIT_TREE: {
- if (EditorFileSystem::get_singleton()->is_connected("filesystem_changed", callable_mp(this, &EditorDirDialog::reload))) {
- EditorFileSystem::get_singleton()->disconnect("filesystem_changed", callable_mp(this, &EditorDirDialog::reload));
- }
+ EditorFileSystem::get_singleton()->disconnect("filesystem_changed", callable_mp(this, &EditorDirDialog::reload));
+ FileSystemDock::get_singleton()->disconnect("folder_color_changed", callable_mp(this, &EditorDirDialog::reload));
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
@@ -229,6 +219,7 @@ EditorDirDialog::EditorDirDialog() {
vb->add_child(tree);
tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
tree->connect("item_activated", callable_mp(this, &EditorDirDialog::_item_activated));
+ tree->connect("item_collapsed", callable_mp(this, &EditorDirDialog::_item_collapsed), CONNECT_DEFERRED);
set_ok_button_text(TTR("Move"));
diff --git a/editor/gui/editor_scene_tabs.cpp b/editor/gui/editor_scene_tabs.cpp
index 5d1e68f008..94fac59b9b 100644
--- a/editor/gui/editor_scene_tabs.cpp
+++ b/editor/gui/editor_scene_tabs.cpp
@@ -178,7 +178,7 @@ void EditorSceneTabs::_update_context_menu() {
if (tab_id >= 0) {
scene_tabs_context_menu->add_separator();
- scene_tabs_context_menu->add_icon_item(get_editor_theme_icon(SNAME("ShowInFileSystem")), TTR("Show in FileSystem"), EditorNode::FILE_SHOW_IN_FILESYSTEM);
+ scene_tabs_context_menu->add_item(TTR("Show in FileSystem"), EditorNode::FILE_SHOW_IN_FILESYSTEM);
_disable_menu_option_if(EditorNode::FILE_SHOW_IN_FILESYSTEM, !ResourceLoader::exists(EditorNode::get_editor_data().get_scene_path(tab_id)));
scene_tabs_context_menu->add_item(TTR("Play This Scene"), EditorNode::FILE_RUN_SCENE);
_disable_menu_option_if(EditorNode::FILE_RUN_SCENE, no_root_node);
diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp
index 221061f9f7..361ae2a945 100644
--- a/editor/gui/scene_tree_editor.cpp
+++ b/editor/gui/scene_tree_editor.cpp
@@ -1013,7 +1013,6 @@ void SceneTreeEditor::set_selected(Node *p_node, bool p_emit_selected) {
if (!p_node) {
selected = nullptr;
}
- _update_tree();
selected = p_node;
}
diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp
index 6f4a376496..fa503cc4df 100644
--- a/editor/import_dock.cpp
+++ b/editor/import_dock.cpp
@@ -200,7 +200,8 @@ void ImportDock::_update_options(const String &p_path, const Ref<ConfigFile> &p_
params->update();
_update_preset_menu();
- if (params->importer.is_valid() && params->paths.size() == 1 && params->importer->has_advanced_options()) {
+ bool was_imported = p_config.is_valid() && p_config->get_value("remap", "importer") != "skip" && p_config->get_value("remap", "importer") != "keep";
+ if (was_imported && params->importer.is_valid() && params->paths.size() == 1 && params->importer->has_advanced_options()) {
advanced->show();
advanced_spacer->show();
} else {
@@ -508,6 +509,18 @@ static bool _find_owners(EditorFileSystemDirectory *efsd, const String &p_path)
return false;
}
+void ImportDock::_reimport_pressed() {
+ _reimport_attempt();
+
+ if (params->importer.is_valid() && params->paths.size() == 1 && params->importer->has_advanced_options()) {
+ advanced->show();
+ advanced_spacer->show();
+ } else {
+ advanced->hide();
+ advanced_spacer->hide();
+ }
+}
+
void ImportDock::_reimport_attempt() {
bool used_in_resources = false;
@@ -528,7 +541,7 @@ void ImportDock::_reimport_attempt() {
ERR_CONTINUE(err != OK);
String imported_with = config->get_value("remap", "importer");
- if (imported_with != importer_name) {
+ if (imported_with != importer_name && imported_with != "keep" && imported_with != "skip") {
Ref<Resource> resource = ResourceLoader::load(params->paths[i]);
if (resource.is_valid()) {
need_cleanup.push_back(params->paths[i]);
@@ -575,7 +588,10 @@ void ImportDock::_reimport_and_cleanup() {
for (const String &path : need_cleanup) {
Ref<Resource> old_res = old_resources[path];
- Ref<Resource> new_res = ResourceLoader::load(path);
+ Ref<Resource> new_res;
+ if (params->importer.is_valid()) {
+ new_res = ResourceLoader::load(path);
+ }
for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) {
Node *edited_scene_root = EditorNode::get_editor_data().get_edited_scene_root(i);
@@ -782,7 +798,7 @@ ImportDock::ImportDock() {
import = memnew(Button);
import->set_text(TTR("Reimport"));
import->set_disabled(true);
- import->connect("pressed", callable_mp(this, &ImportDock::_reimport_attempt));
+ import->connect("pressed", callable_mp(this, &ImportDock::_reimport_pressed));
if (!DisplayServer::get_singleton()->get_swap_cancel_ok()) {
advanced_spacer = hb->add_spacer();
advanced = memnew(Button);
diff --git a/editor/import_dock.h b/editor/import_dock.h
index 78cd6a556c..c0a1dee7ca 100644
--- a/editor/import_dock.h
+++ b/editor/import_dock.h
@@ -76,6 +76,7 @@ class ImportDock : public VBoxContainer {
void _property_edited(const StringName &p_prop);
void _property_toggled(const StringName &p_prop, bool p_checked);
void _set_dirty(bool p_dirty);
+ void _reimport_pressed();
void _reimport_attempt();
void _reimport_and_cleanup();
void _reimport();
diff --git a/editor/plugin_config_dialog.cpp b/editor/plugin_config_dialog.cpp
index 6f54a1c57a..184a9233a8 100644
--- a/editor/plugin_config_dialog.cpp
+++ b/editor/plugin_config_dialog.cpp
@@ -72,6 +72,7 @@ void PluginConfigDialog::_on_confirmed() {
String script_path = path.path_join(script_name);
Ref<ConfigFile> cf = memnew(ConfigFile);
+ cf->load(path.path_join("plugin.cfg"));
cf->set_value("plugin", "name", name_edit->get_text());
cf->set_value("plugin", "description", desc_edit->get_text());
cf->set_value("plugin", "author", author_edit->get_text());
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 8a9118a03e..5ac5dd6ee6 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -1344,22 +1344,33 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) {
// Drag the pivot (in pivot mode / with V key)
if (drag_type == DRAG_NONE) {
+ bool move_temp_pivot = ((b.is_valid() && b->is_shift_pressed()) || (k.is_valid() && k->is_shift_pressed()));
+
if ((b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::LEFT && tool == TOOL_EDIT_PIVOT) ||
- (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == Key::V && tool == TOOL_SELECT && k->get_modifiers_mask().is_empty())) {
+ (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == Key::V && tool == TOOL_SELECT && (k->get_modifiers_mask().is_empty() || move_temp_pivot))) {
List<CanvasItem *> selection = _get_edited_canvas_items();
// Filters the selection with nodes that allow setting the pivot
drag_selection = List<CanvasItem *>();
for (CanvasItem *ci : selection) {
- if (ci->_edit_use_pivot()) {
+ if (ci->_edit_use_pivot() || move_temp_pivot) {
drag_selection.push_back(ci);
}
}
// Start dragging if we still have nodes
if (drag_selection.size() > 0) {
+ Vector2 event_pos = (b.is_valid()) ? b->get_position() : viewport->get_local_mouse_position();
+
+ if (move_temp_pivot) {
+ drag_type = DRAG_TEMP_PIVOT;
+ temp_pivot = transform.affine_inverse().xform(event_pos);
+ viewport->queue_redraw();
+ return true;
+ }
+
_save_canvas_item_state(drag_selection);
- drag_from = transform.affine_inverse().xform((b.is_valid()) ? b->get_position() : viewport->get_local_mouse_position());
+ drag_from = transform.affine_inverse().xform(event_pos);
Vector2 new_pos;
if (drag_selection.size() == 1) {
new_pos = snap_point(drag_from, SNAP_NODE_SIDES | SNAP_NODE_CENTER | SNAP_NODE_ANCHORS | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, drag_selection[0]);
@@ -1416,6 +1427,20 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) {
return true;
}
}
+
+ if (drag_type == DRAG_TEMP_PIVOT) {
+ if (m.is_valid()) {
+ temp_pivot = transform.affine_inverse().xform(m->get_position());
+ viewport->queue_redraw();
+ return true;
+ }
+
+ if ((b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT && tool == TOOL_EDIT_PIVOT) ||
+ (k.is_valid() && !k->is_pressed() && k->get_keycode() == Key::V)) {
+ drag_type = DRAG_NONE;
+ return true;
+ }
+ }
return false;
}
@@ -1441,7 +1466,9 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) {
drag_type = DRAG_ROTATE;
drag_from = transform.affine_inverse().xform(b->get_position());
CanvasItem *ci = drag_selection[0];
- if (ci->_edit_use_pivot()) {
+ if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {
+ drag_rotation_center = temp_pivot;
+ } else if (ci->_edit_use_pivot()) {
drag_rotation_center = ci->get_global_transform_with_canvas().xform(ci->_edit_get_pivot());
} else {
drag_rotation_center = ci->get_global_transform_with_canvas().get_origin();
@@ -1461,7 +1488,16 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) {
drag_to = transform.affine_inverse().xform(m->get_position());
//Rotate the opposite way if the canvas item's compounded scale has an uneven number of negative elements
bool opposite = (ci->get_global_transform().get_scale().sign().dot(ci->get_transform().get_scale().sign()) == 0);
- ci->_edit_set_rotation(snap_angle(ci->_edit_get_rotation() + (opposite ? -1 : 1) * (drag_from - drag_rotation_center).angle_to(drag_to - drag_rotation_center), ci->_edit_get_rotation()));
+ real_t prev_rotation = ci->_edit_get_rotation();
+ real_t new_rotation = snap_angle(ci->_edit_get_rotation() + (opposite ? -1 : 1) * (drag_from - drag_rotation_center).angle_to(drag_to - drag_rotation_center), prev_rotation);
+
+ ci->_edit_set_rotation(new_rotation);
+ if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {
+ Transform2D xform = ci->get_global_transform_with_canvas() * ci->get_transform().affine_inverse();
+ Vector2 radius = xform.xform(ci->_edit_get_position()) - temp_pivot;
+ radius = radius.rotated(new_rotation - prev_rotation);
+ ci->_edit_set_position(xform.affine_inverse().xform(temp_pivot + radius));
+ }
viewport->queue_redraw();
}
return true;
@@ -3161,7 +3197,7 @@ void CanvasItemEditor::_draw_ruler_tool() {
} else {
if (grid_snap_active) {
Ref<Texture2D> position_icon = get_editor_theme_icon(SNAME("EditorPosition"));
- viewport->draw_texture(get_editor_theme_icon(SNAME("EditorPosition")), (ruler_tool_origin - view_offset) * zoom - position_icon->get_size() / 2);
+ viewport->draw_texture(position_icon, (ruler_tool_origin - view_offset) * zoom - position_icon->get_size() / 2);
}
}
}
@@ -3583,6 +3619,10 @@ void CanvasItemEditor::_draw_selection() {
get_theme_color(SNAME("accent_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.6),
Math::round(2 * EDSCALE));
}
+
+ if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {
+ viewport->draw_texture(pivot_icon, (temp_pivot - view_offset) * zoom - (pivot_icon->get_size() / 2).floor(), get_theme_color(SNAME("accent_color"), SNAME("Editor")));
+ }
}
void CanvasItemEditor::_draw_straight_line(Point2 p_from, Point2 p_to, Color p_color) {
@@ -3931,8 +3971,6 @@ void CanvasItemEditor::_notification(int p_what) {
} break;
case NOTIFICATION_PROCESS: {
- int nb_having_pivot = 0;
-
// Update the viewport if the canvas_item changes
List<CanvasItem *> selection = _get_edited_canvas_items(true);
for (CanvasItem *ci : selection) {
@@ -3972,14 +4010,10 @@ void CanvasItemEditor::_notification(int p_what) {
viewport->queue_redraw();
}
}
-
- if (ci->_edit_use_pivot()) {
- nb_having_pivot++;
- }
}
- // Activate / Deactivate the pivot tool
- pivot_button->set_disabled(nb_having_pivot == 0);
+ // Activate / Deactivate the pivot tool.
+ pivot_button->set_disabled(selection.is_empty());
// Update the viewport if bones changes
for (KeyValue<BoneKey, BoneList> &E : bone_list) {
@@ -4048,6 +4082,11 @@ void CanvasItemEditor::_selection_changed() {
_reset_drag();
}
selected_from_canvas = false;
+
+ if (temp_pivot != Vector2(INFINITY, INFINITY)) {
+ temp_pivot = Vector2(INFINITY, INFINITY);
+ viewport->queue_redraw();
+ }
}
void CanvasItemEditor::edit(CanvasItem *p_canvas_item) {
@@ -4202,6 +4241,18 @@ void CanvasItemEditor::_button_tool_select(int p_index) {
tool = (Tool)p_index;
+ if (p_index == TOOL_EDIT_PIVOT && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
+ // Special action that places temporary rotation pivot in the middle of the selection.
+ List<CanvasItem *> selection = _get_edited_canvas_items();
+ if (!selection.is_empty()) {
+ Vector2 center;
+ for (const CanvasItem *ci : selection) {
+ center += ci->_edit_get_position();
+ }
+ temp_pivot = center / selection.size();
+ }
+ }
+
viewport->queue_redraw();
_update_cursor();
}
@@ -5279,7 +5330,7 @@ CanvasItemEditor::CanvasItemEditor() {
main_menu_hbox->add_child(pivot_button);
pivot_button->set_toggle_mode(true);
pivot_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_EDIT_PIVOT));
- pivot_button->set_tooltip_text(TTR("Click to change object's rotation pivot."));
+ pivot_button->set_tooltip_text(TTR("Click to change object's rotation pivot.") + "\n" + TTR("Shift: Set temporary rotation pivot.") + "\n" + TTR("Click this button while holding Shift to put the rotation pivot in the center of the selected nodes."));
pan_button = memnew(Button);
pan_button->set_theme_type_variation("FlatButton");
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index c4b995b048..f52ad3dc4f 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -174,6 +174,7 @@ private:
DRAG_SCALE_BOTH,
DRAG_ROTATE,
DRAG_PIVOT,
+ DRAG_TEMP_PIVOT,
DRAG_V_GUIDE,
DRAG_H_GUIDE,
DRAG_DOUBLE_GUIDE,
@@ -251,6 +252,7 @@ private:
bool key_scale = false;
bool pan_pressed = false;
+ Vector2 temp_pivot = Vector2(INFINITY, INFINITY);
bool ruler_tool_active = false;
Point2 ruler_tool_origin;
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index 70213b280c..9fb4d86fa2 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -678,6 +678,8 @@ Ref<Texture2D> EditorAudioStreamPreviewPlugin::generate(const Ref<Resource> &p_f
}
}
+ p_metadata["length"] = stream->get_length();
+
//post_process_preview(img);
Ref<Image> image = Image::create_from_data(w, h, false, Image::FORMAT_RGB8, img);
diff --git a/editor/plugins/editor_resource_tooltip_plugins.cpp b/editor/plugins/editor_resource_tooltip_plugins.cpp
index fab8ee9f59..dfeb59214c 100644
--- a/editor/plugins/editor_resource_tooltip_plugins.cpp
+++ b/editor/plugins/editor_resource_tooltip_plugins.cpp
@@ -103,6 +103,7 @@ bool EditorTextureTooltipPlugin::handles(const String &p_resource_type) const {
Control *EditorTextureTooltipPlugin::make_tooltip_for_path(const String &p_resource_path, const Dictionary &p_metadata, Control *p_base) const {
HBoxContainer *hb = memnew(HBoxContainer);
VBoxContainer *vb = Object::cast_to<VBoxContainer>(p_base);
+ DEV_ASSERT(vb);
vb->set_alignment(BoxContainer::ALIGNMENT_CENTER);
Vector2 dimensions = p_metadata.get("dimensions", Vector2());
@@ -117,3 +118,29 @@ Control *EditorTextureTooltipPlugin::make_tooltip_for_path(const String &p_resou
hb->add_child(vb);
return hb;
}
+
+// EditorAudioStreamTooltipPlugin
+
+bool EditorAudioStreamTooltipPlugin::handles(const String &p_resource_type) const {
+ return ClassDB::is_parent_class(p_resource_type, "AudioStream");
+}
+
+Control *EditorAudioStreamTooltipPlugin::make_tooltip_for_path(const String &p_resource_path, const Dictionary &p_metadata, Control *p_base) const {
+ VBoxContainer *vb = Object::cast_to<VBoxContainer>(p_base);
+ DEV_ASSERT(vb);
+
+ double length = p_metadata.get("length", 0.0);
+ if (length >= 60.0) {
+ vb->add_child(memnew(Label(vformat(TTR("Length: %0dm %0ds"), int(length / 60.0), int(fmod(length, 60))))));
+ } else if (length >= 1.0) {
+ vb->add_child(memnew(Label(vformat(TTR("Length: %0.1fs"), length))));
+ } else {
+ vb->add_child(memnew(Label(vformat(TTR("Length: %0.3fs"), length))));
+ }
+
+ TextureRect *tr = memnew(TextureRect);
+ vb->add_child(tr);
+ request_thumbnail(p_resource_path, tr);
+
+ return vb;
+}
diff --git a/editor/plugins/editor_resource_tooltip_plugins.h b/editor/plugins/editor_resource_tooltip_plugins.h
index e3a27de0bb..43be8fd8e8 100644
--- a/editor/plugins/editor_resource_tooltip_plugins.h
+++ b/editor/plugins/editor_resource_tooltip_plugins.h
@@ -33,9 +33,8 @@
#include "core/object/gdvirtual.gen.inc"
#include "core/object/ref_counted.h"
-#include <scene/gui/control.h>
+#include "scene/gui/control.h"
-class Control;
class Texture2D;
class TextureRect;
class VBoxContainer;
@@ -67,4 +66,12 @@ public:
virtual Control *make_tooltip_for_path(const String &p_resource_path, const Dictionary &p_metadata, Control *p_base) const override;
};
+class EditorAudioStreamTooltipPlugin : public EditorResourceTooltipPlugin {
+ GDCLASS(EditorAudioStreamTooltipPlugin, EditorResourceTooltipPlugin);
+
+public:
+ virtual bool handles(const String &p_resource_type) const override;
+ virtual Control *make_tooltip_for_path(const String &p_resource_path, const Dictionary &p_metadata, Control *p_base) const override;
+};
+
#endif // EDITOR_RESOURCE_TOOLTIP_PLUGINS_H
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
index d62eddeeea..0c922ea070 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
@@ -59,200 +59,159 @@ void MeshInstance3DEditor::edit(MeshInstance3D *p_mesh) {
node = p_mesh;
}
-void MeshInstance3DEditor::_menu_option(int p_option) {
- Ref<Mesh> mesh = node->get_mesh();
- if (mesh.is_null()) {
- err_dialog->set_text(TTR("Mesh is empty!"));
- err_dialog->popup_centered();
- return;
- }
-
+Vector<Ref<Shape3D>> MeshInstance3DEditor::create_shape_from_mesh(Ref<Mesh> p_mesh, int p_option, bool p_verbose) {
+ Vector<Ref<Shape3D>> shapes;
switch (p_option) {
- case MENU_OPTION_CREATE_STATIC_TRIMESH_BODY: {
- EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
-
- List<Node *> selection = editor_selection->get_selected_node_list();
-
- if (selection.is_empty()) {
- Ref<ConcavePolygonShape3D> shape = mesh->create_trimesh_shape();
- if (shape.is_null()) {
- err_dialog->set_text(TTR("Couldn't create a Trimesh collision shape."));
- err_dialog->popup_centered();
- return;
- }
-
- CollisionShape3D *cshape = memnew(CollisionShape3D);
- cshape->set_shape(shape);
- StaticBody3D *body = memnew(StaticBody3D);
- body->add_child(cshape, true);
-
- Node *owner = get_tree()->get_edited_scene_root();
-
- ur->create_action(TTR("Create Static Trimesh Body"));
- ur->add_do_method(node, "add_child", body, true);
- ur->add_do_method(body, "set_owner", owner);
- ur->add_do_method(cshape, "set_owner", owner);
- ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, body);
- ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, cshape);
- ur->add_do_reference(body);
- ur->add_undo_method(node, "remove_child", body);
- ur->commit_action();
- return;
- }
+ case SHAPE_TYPE_TRIMESH: {
+ shapes.push_back(p_mesh->create_trimesh_shape());
- ur->create_action(TTR("Create Static Trimesh Body"));
-
- for (Node *E : selection) {
- MeshInstance3D *instance = Object::cast_to<MeshInstance3D>(E);
- if (!instance) {
- continue;
- }
-
- Ref<Mesh> m = instance->get_mesh();
- if (m.is_null()) {
- continue;
- }
-
- Ref<ConcavePolygonShape3D> shape = m->create_trimesh_shape();
- if (shape.is_null()) {
- continue;
- }
-
- CollisionShape3D *cshape = memnew(CollisionShape3D);
- cshape->set_shape(shape);
- StaticBody3D *body = memnew(StaticBody3D);
- body->add_child(cshape, true);
-
- Node *owner = get_tree()->get_edited_scene_root();
-
- ur->add_do_method(instance, "add_child", body, true);
- ur->add_do_method(body, "set_owner", owner);
- ur->add_do_method(cshape, "set_owner", owner);
- ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, body);
- ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, cshape);
- ur->add_do_reference(body);
- ur->add_undo_method(instance, "remove_child", body);
+ if (p_verbose && shapes.is_empty()) {
+ err_dialog->set_text(TTR("Couldn't create a Trimesh collision shape."));
+ err_dialog->popup_centered();
}
-
- ur->commit_action();
-
} break;
- case MENU_OPTION_CREATE_TRIMESH_COLLISION_SHAPE: {
- if (node == get_tree()->get_edited_scene_root()) {
- err_dialog->set_text(TTR("This doesn't work on scene root!"));
- err_dialog->popup_centered();
- return;
- }
+ case SHAPE_TYPE_SINGLE_CONVEX: {
+ shapes.push_back(p_mesh->create_convex_shape(true, false));
- Ref<ConcavePolygonShape3D> shape = mesh->create_trimesh_shape();
- if (shape.is_null()) {
- return;
+ if (p_verbose && shapes.is_empty()) {
+ err_dialog->set_text(TTR("Couldn't create a single collision shape."));
+ err_dialog->popup_centered();
}
-
- CollisionShape3D *cshape = memnew(CollisionShape3D);
- cshape->set_shape(shape);
- cshape->set_transform(node->get_transform());
-
- Node *owner = get_tree()->get_edited_scene_root();
-
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
-
- ur->create_action(TTR("Create Trimesh Static Shape"));
-
- ur->add_do_method(node->get_parent(), "add_child", cshape, true);
- ur->add_do_method(node->get_parent(), "move_child", cshape, node->get_index() + 1);
- ur->add_do_method(cshape, "set_owner", owner);
- ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, cshape);
- ur->add_do_reference(cshape);
- ur->add_undo_method(node->get_parent(), "remove_child", cshape);
- ur->commit_action();
} break;
- case MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE:
- case MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE: {
- if (node == get_tree()->get_edited_scene_root()) {
- err_dialog->set_text(TTR("Can't create a single convex collision shape for the scene root."));
+ case SHAPE_TYPE_SIMPLIFIED_CONVEX: {
+ shapes.push_back(p_mesh->create_convex_shape(true, true));
+
+ if (p_verbose && shapes.is_empty()) {
+ err_dialog->set_text(TTR("Couldn't create a simplified collision shape."));
err_dialog->popup_centered();
- return;
}
+ } break;
- bool simplify = (p_option == MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE);
+ case SHAPE_TYPE_MULTIPLE_CONVEX: {
+ Ref<MeshConvexDecompositionSettings> settings;
+ settings.instantiate();
+ settings->set_max_convex_hulls(32);
+ settings->set_max_concavity(0.001);
- Ref<ConvexPolygonShape3D> shape = mesh->create_convex_shape(true, simplify);
+ shapes = p_mesh->convex_decompose(settings);
- if (shape.is_null()) {
- err_dialog->set_text(TTR("Couldn't create a single convex collision shape."));
+ if (p_verbose && shapes.is_empty()) {
+ err_dialog->set_text(TTR("Couldn't create any collision shapes."));
err_dialog->popup_centered();
- return;
}
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ } break;
- if (simplify) {
- ur->create_action(TTR("Create Simplified Convex Shape"));
- } else {
- ur->create_action(TTR("Create Single Convex Shape"));
- }
+ default:
+ break;
+ }
+ return shapes;
+}
- CollisionShape3D *cshape = memnew(CollisionShape3D);
- cshape->set_shape(shape);
- cshape->set_transform(node->get_transform());
+void MeshInstance3DEditor::_create_collision_shape() {
+ int placement_option = shape_placement->get_selected();
+ int shape_type_option = shape_type->get_selected();
- Node *owner = get_tree()->get_edited_scene_root();
+ EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
- ur->add_do_method(node->get_parent(), "add_child", cshape, true);
- ur->add_do_method(node->get_parent(), "move_child", cshape, node->get_index() + 1);
- ur->add_do_method(cshape, "set_owner", owner);
- ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, cshape);
- ur->add_do_reference(cshape);
- ur->add_undo_method(node->get_parent(), "remove_child", cshape);
+ switch (shape_type_option) {
+ case SHAPE_TYPE_TRIMESH: {
+ ur->create_action(TTR(placement_option == SHAPE_PLACEMENT_SIBLING ? "Create Trimesh Collision Shape Sibling" : "Create Trimesh Static Body"));
+ } break;
+ case SHAPE_TYPE_SINGLE_CONVEX: {
+ ur->create_action(TTR(placement_option == SHAPE_PLACEMENT_SIBLING ? "Create Single Convex Collision Shape Sibling" : "Create Single Convex Static Body"));
+ } break;
+ case SHAPE_TYPE_SIMPLIFIED_CONVEX: {
+ ur->create_action(TTR(placement_option == SHAPE_PLACEMENT_SIBLING ? "Create Simplified Convex Collision Shape Sibling" : "Create Simplified Convex Static Body"));
+ } break;
+ case SHAPE_TYPE_MULTIPLE_CONVEX: {
+ ur->create_action(TTR(placement_option == SHAPE_PLACEMENT_SIBLING ? "Create Multiple Convex Collision Shape Siblings" : "Create Multiple Convex Static Body"));
+ } break;
+ default:
+ break;
+ }
- ur->commit_action();
+ List<Node *> selection = editor_selection->get_selected_node_list();
- } break;
+ bool verbose = false;
+ if (selection.is_empty()) {
+ selection.push_back(node);
+ verbose = true;
+ }
- case MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES: {
- if (node == get_tree()->get_edited_scene_root()) {
- err_dialog->set_text(TTR("Can't create multiple convex collision shapes for the scene root."));
+ for (Node *E : selection) {
+ if (placement_option == SHAPE_PLACEMENT_SIBLING && E == get_tree()->get_edited_scene_root()) {
+ if (verbose) {
+ err_dialog->set_text(TTR("Can't create a collision shape as sibling for the scene root."));
err_dialog->popup_centered();
- return;
}
+ continue;
+ }
- Ref<MeshConvexDecompositionSettings> settings = Ref<MeshConvexDecompositionSettings>();
- settings.instantiate();
- settings->set_max_convex_hulls(32);
- settings->set_max_concavity(0.001);
+ MeshInstance3D *instance = Object::cast_to<MeshInstance3D>(E);
+ if (!instance) {
+ continue;
+ }
- Vector<Ref<Shape3D>> shapes = mesh->convex_decompose(settings);
+ Ref<Mesh> m = instance->get_mesh();
+ if (m.is_null()) {
+ continue;
+ }
- if (!shapes.size()) {
- err_dialog->set_text(TTR("Couldn't create any collision shapes."));
- err_dialog->popup_centered();
- return;
- }
- EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ Vector<Ref<Shape3D>> shapes = create_shape_from_mesh(m, shape_type_option, verbose);
+ if (shapes.is_empty()) {
+ return;
+ }
+
+ Node *owner = get_tree()->get_edited_scene_root();
+ if (placement_option == SHAPE_PLACEMENT_STATIC_BODY_CHILD) {
+ StaticBody3D *body = memnew(StaticBody3D);
- ur->create_action(TTR("Create Multiple Convex Shapes"));
+ ur->add_do_method(instance, "add_child", body, true);
+ ur->add_do_method(body, "set_owner", owner);
+ ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, body);
- for (int i = 0; i < shapes.size(); i++) {
+ for (Ref<Shape3D> shape : shapes) {
CollisionShape3D *cshape = memnew(CollisionShape3D);
+ cshape->set_shape(shape);
+ body->add_child(cshape, true);
+ ur->add_do_method(cshape, "set_owner", owner);
+ ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, cshape);
+ }
+ ur->add_do_reference(body);
+ ur->add_undo_method(instance, "remove_child", body);
+ } else {
+ for (Ref<Shape3D> shape : shapes) {
+ CollisionShape3D *cshape = memnew(CollisionShape3D);
+ cshape->set_shape(shape);
cshape->set_name("CollisionShape3D");
-
- cshape->set_shape(shapes[i]);
cshape->set_transform(node->get_transform());
-
- Node *owner = get_tree()->get_edited_scene_root();
-
- ur->add_do_method(node->get_parent(), "add_child", cshape);
- ur->add_do_method(node->get_parent(), "move_child", cshape, node->get_index() + 1);
+ ur->add_do_method(E->get_parent(), "add_child", cshape);
+ ur->add_do_method(E->get_parent(), "move_child", cshape, E->get_index() + 1);
ur->add_do_method(cshape, "set_owner", owner);
ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, cshape);
ur->add_do_reference(cshape);
ur->add_undo_method(node->get_parent(), "remove_child", cshape);
}
- ur->commit_action();
+ }
+ }
+
+ ur->commit_action();
+}
+
+void MeshInstance3DEditor::_menu_option(int p_option) {
+ Ref<Mesh> mesh = node->get_mesh();
+ if (mesh.is_null()) {
+ err_dialog->set_text(TTR("Mesh is empty!"));
+ err_dialog->popup_centered();
+ return;
+ }
+ switch (p_option) {
+ case MENU_OPTION_CREATE_COLLISION_SHAPE: {
+ shape_dialog->popup_centered();
} break;
case MENU_OPTION_CREATE_NAVMESH: {
@@ -571,18 +530,7 @@ MeshInstance3DEditor::MeshInstance3DEditor() {
options->set_switch_on_hover(true);
Node3DEditor::get_singleton()->add_control_to_menu_panel(options);
- options->get_popup()->add_item(TTR("Create Trimesh Static Body"), MENU_OPTION_CREATE_STATIC_TRIMESH_BODY);
- options->get_popup()->set_item_tooltip(-1, TTR("Creates a StaticBody3D and assigns a polygon-based collision shape to it automatically.\nThis is the most accurate (but slowest) option for collision detection."));
- options->get_popup()->add_separator();
- options->get_popup()->add_item(TTR("Create Trimesh Collision Sibling"), MENU_OPTION_CREATE_TRIMESH_COLLISION_SHAPE);
- options->get_popup()->set_item_tooltip(-1, TTR("Creates a polygon-based collision shape.\nThis is the most accurate (but slowest) option for collision detection."));
- options->get_popup()->add_item(TTR("Create Single Convex Collision Sibling"), MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE);
- options->get_popup()->set_item_tooltip(-1, TTR("Creates a single convex collision shape.\nThis is the fastest (but least accurate) option for collision detection."));
- options->get_popup()->add_item(TTR("Create Simplified Convex Collision Sibling"), MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE);
- options->get_popup()->set_item_tooltip(-1, TTR("Creates a simplified convex collision shape.\nThis is similar to single collision shape, but can result in a simpler geometry in some cases, at the cost of accuracy."));
- options->get_popup()->add_item(TTR("Create Multiple Convex Collision Siblings"), MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES);
- options->get_popup()->set_item_tooltip(-1, TTR("Creates a polygon-based collision shape.\nThis is a performance middle-ground between a single convex collision and a polygon-based collision."));
- options->get_popup()->add_separator();
+ options->get_popup()->add_item(TTR("Create Collision Shape..."), MENU_OPTION_CREATE_COLLISION_SHAPE);
options->get_popup()->add_item(TTR("Create Navigation Mesh"), MENU_OPTION_CREATE_NAVMESH);
options->get_popup()->add_separator();
options->get_popup()->add_item(TTR("Create Outline Mesh..."), MENU_OPTION_CREATE_OUTLINE_MESH);
@@ -613,6 +561,44 @@ MeshInstance3DEditor::MeshInstance3DEditor() {
add_child(outline_dialog);
outline_dialog->connect("confirmed", callable_mp(this, &MeshInstance3DEditor::_create_outline_mesh));
+ shape_dialog = memnew(ConfirmationDialog);
+ shape_dialog->set_title(TTR("Create Collision Shape"));
+ shape_dialog->set_ok_button_text(TTR("Create"));
+
+ VBoxContainer *shape_dialog_vbc = memnew(VBoxContainer);
+ shape_dialog->add_child(shape_dialog_vbc);
+
+ Label *l = memnew(Label);
+ l->set_text(TTR("Collision Shape placement"));
+ shape_dialog_vbc->add_child(l);
+
+ shape_placement = memnew(OptionButton);
+ shape_placement->set_h_size_flags(SIZE_EXPAND_FILL);
+ shape_placement->add_item(TTR("Sibling"), SHAPE_PLACEMENT_SIBLING);
+ shape_placement->set_item_tooltip(-1, TTR("Creates collision shapes as Sibling."));
+ shape_placement->add_item(TTR("Static Body Child"), SHAPE_PLACEMENT_STATIC_BODY_CHILD);
+ shape_placement->set_item_tooltip(-1, TTR("Creates a StaticBody3D as child and assigns collision shapes to it."));
+ shape_dialog_vbc->add_child(shape_placement);
+
+ l = memnew(Label);
+ l->set_text(TTR("Collision Shape Type"));
+ shape_dialog_vbc->add_child(l);
+
+ shape_type = memnew(OptionButton);
+ shape_type->set_h_size_flags(SIZE_EXPAND_FILL);
+ shape_type->add_item(TTR("Trimesh"), SHAPE_TYPE_TRIMESH);
+ shape_type->set_item_tooltip(-1, TTR("Creates a polygon-based collision shape.\nThis is the most accurate (but slowest) option for collision detection."));
+ shape_type->add_item(TTR("Single Convex"), SHAPE_TYPE_SINGLE_CONVEX);
+ shape_type->set_item_tooltip(-1, TTR("Creates a single convex collision shape.\nThis is the fastest (but least accurate) option for collision detection."));
+ shape_type->add_item(TTR("Simplified Convex"), SHAPE_TYPE_SIMPLIFIED_CONVEX);
+ shape_type->set_item_tooltip(-1, TTR("Creates a simplified convex collision shape.\nThis is similar to single collision shape, but can result in a simpler geometry in some cases, at the cost of accuracy."));
+ shape_type->add_item(TTR("Multiple Convex"), SHAPE_TYPE_MULTIPLE_CONVEX);
+ shape_type->set_item_tooltip(-1, TTR("Creates a polygon-based collision shape.\nThis is a performance middle-ground between a single convex collision and a polygon-based collision."));
+ shape_dialog_vbc->add_child(shape_type);
+
+ add_child(shape_dialog);
+ shape_dialog->connect("confirmed", callable_mp(this, &MeshInstance3DEditor::_create_collision_shape));
+
err_dialog = memnew(AcceptDialog);
add_child(err_dialog);
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.h b/editor/plugins/mesh_instance_3d_editor_plugin.h
index eb984e240e..ce7d23239c 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.h
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.h
@@ -33,6 +33,7 @@
#include "editor/editor_plugin.h"
#include "scene/3d/mesh_instance_3d.h"
+#include "scene/gui/option_button.h"
class AcceptDialog;
class ConfirmationDialog;
@@ -43,11 +44,7 @@ class MeshInstance3DEditor : public Control {
GDCLASS(MeshInstance3DEditor, Control);
enum Menu {
- MENU_OPTION_CREATE_STATIC_TRIMESH_BODY,
- MENU_OPTION_CREATE_TRIMESH_COLLISION_SHAPE,
- MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE,
- MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE,
- MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES,
+ MENU_OPTION_CREATE_COLLISION_SHAPE,
MENU_OPTION_CREATE_NAVMESH,
MENU_OPTION_CREATE_OUTLINE_MESH,
MENU_OPTION_CREATE_DEBUG_TANGENTS,
@@ -56,6 +53,18 @@ class MeshInstance3DEditor : public Control {
MENU_OPTION_DEBUG_UV2,
};
+ enum ShapePlacement {
+ SHAPE_PLACEMENT_SIBLING,
+ SHAPE_PLACEMENT_STATIC_BODY_CHILD,
+ };
+
+ enum ShapeType {
+ SHAPE_TYPE_TRIMESH,
+ SHAPE_TYPE_SINGLE_CONVEX,
+ SHAPE_TYPE_SIMPLIFIED_CONVEX,
+ SHAPE_TYPE_MULTIPLE_CONVEX,
+ };
+
MeshInstance3D *node = nullptr;
MenuButton *options = nullptr;
@@ -63,12 +72,18 @@ class MeshInstance3DEditor : public Control {
ConfirmationDialog *outline_dialog = nullptr;
SpinBox *outline_size = nullptr;
+ ConfirmationDialog *shape_dialog = nullptr;
+ OptionButton *shape_type = nullptr;
+ OptionButton *shape_placement = nullptr;
+
AcceptDialog *err_dialog = nullptr;
AcceptDialog *debug_uv_dialog = nullptr;
Control *debug_uv = nullptr;
Vector<Vector2> uv_lines;
+ void _create_collision_shape();
+ Vector<Ref<Shape3D>> create_shape_from_mesh(Ref<Mesh> p_mesh, int p_option, bool p_verbose);
void _menu_option(int p_option);
void _create_outline_mesh();
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index 1cffdb6454..4e317114a3 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -169,21 +169,17 @@ void Path3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, con
const Basis posture = c->get_point_baked_posture(idx);
const Vector3 tangent = -posture.get_column(2);
const Vector3 up = posture.get_column(1);
- const Plane p_tilt = Plane(tangent, position);
+ const Plane tilt_plane_global = gt.xform(Plane(tangent, position));
Vector3 intersection;
- if (p_tilt.intersects_ray(ray_from, ray_dir, &intersection)) {
- Vector3 direction = intersection - position;
- direction.normalize(); // FIXME: redundant?
+ if (tilt_plane_global.intersects_ray(ray_from, ray_dir, &intersection)) {
+ Vector3 direction = gi.xform(intersection) - position;
real_t tilt_angle = up.signed_angle_to(direction, tangent);
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
- real_t snap = Node3DEditor::get_singleton()->get_rotate_snap();
-
- tilt_angle = Math::rad_to_deg(tilt_angle) + snap * 0.5; // Else it won't reach +180.
- tilt_angle -= Math::fmod(tilt_angle, snap);
- tilt_angle = Math::deg_to_rad(tilt_angle);
+ real_t snap_degrees = Node3DEditor::get_singleton()->get_rotate_snap();
+ tilt_angle = Math::deg_to_rad(Math::snapped(Math::rad_to_deg(tilt_angle), snap_degrees));
}
c->set_point_tilt(idx, tilt_angle);
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index cc9e887448..c48af90622 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -273,6 +273,7 @@ void ScriptEditorBase::_bind_methods() {
ADD_SIGNAL(MethodInfo("request_help", PropertyInfo(Variant::STRING, "topic")));
ADD_SIGNAL(MethodInfo("request_open_script_at_line", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::INT, "line")));
ADD_SIGNAL(MethodInfo("request_save_history"));
+ ADD_SIGNAL(MethodInfo("request_save_previous_state", PropertyInfo(Variant::INT, "line")));
ADD_SIGNAL(MethodInfo("go_to_help", PropertyInfo(Variant::STRING, "what")));
ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text")));
ADD_SIGNAL(MethodInfo("replace_in_files_requested", PropertyInfo(Variant::STRING, "text")));
@@ -639,6 +640,32 @@ void ScriptEditor::_save_history() {
_update_history_arrows();
}
+void ScriptEditor::_save_previous_state(Dictionary p_state) {
+ if (lock_history) {
+ // Done as a result of a deferred call triggered by set_edit_state().
+ lock_history = false;
+ return;
+ }
+
+ if (history_pos >= 0 && history_pos < history.size() && history[history_pos].control == tab_container->get_current_tab_control()) {
+ Node *n = tab_container->get_current_tab_control();
+
+ if (Object::cast_to<ScriptTextEditor>(n)) {
+ history.write[history_pos].state = p_state;
+ }
+ }
+
+ history.resize(history_pos + 1);
+ ScriptHistory sh;
+ sh.control = tab_container->get_current_tab_control();
+ sh.state = Variant();
+
+ history.push_back(sh);
+ history_pos++;
+
+ _update_history_arrows();
+}
+
void ScriptEditor::_go_to_tab(int p_idx) {
ScriptEditorBase *current = _get_current_editor();
if (current) {
@@ -668,8 +695,10 @@ void ScriptEditor::_go_to_tab(int p_idx) {
sh.control = c;
sh.state = Variant();
- history.push_back(sh);
- history_pos++;
+ if (!lock_history && (history.is_empty() || history[history.size() - 1].control != sh.control)) {
+ history.push_back(sh);
+ history_pos++;
+ }
tab_container->set_current_tab(p_idx);
@@ -2185,8 +2214,11 @@ void ScriptEditor::_update_script_names() {
sd.index = i;
sedata.set(i, sd);
}
+
+ lock_history = true;
_go_to_tab(new_prev_tab);
_go_to_tab(new_cur_tab);
+ lock_history = false;
_sort_list_on_update = false;
}
@@ -2474,6 +2506,10 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col,
if (script_editor_cache->has_section(p_resource->get_path())) {
se->set_edit_state(script_editor_cache->get_value(p_resource->get_path(), "state"));
+ ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(se);
+ if (ste) {
+ ste->store_previous_state();
+ }
}
_sort_list_on_update = true;
@@ -2485,6 +2521,7 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col,
se->connect("request_open_script_at_line", callable_mp(this, &ScriptEditor::_goto_script_line));
se->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto));
se->connect("request_save_history", callable_mp(this, &ScriptEditor::_save_history));
+ se->connect("request_save_previous_state", callable_mp(this, &ScriptEditor::_save_previous_state));
se->connect("search_in_files_requested", callable_mp(this, &ScriptEditor::_on_find_in_files_requested));
se->connect("replace_in_files_requested", callable_mp(this, &ScriptEditor::_on_replace_in_files_requested));
se->connect("go_to_method", callable_mp(this, &ScriptEditor::script_goto_method));
@@ -3421,6 +3458,7 @@ void ScriptEditor::_help_class_open(const String &p_class) {
_go_to_tab(tab_container->get_tab_count() - 1);
eh->go_to_class(p_class);
eh->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto));
+ eh->connect("request_save_history", callable_mp(this, &ScriptEditor::_save_history));
_add_recent_script(p_class);
_sort_list_on_update = true;
_update_script_names();
@@ -3549,6 +3587,7 @@ void ScriptEditor::_update_history_pos(int p_new_pos) {
ScriptEditorBase *seb = Object::cast_to<ScriptEditorBase>(n);
if (seb) {
+ lock_history = true;
seb->set_edit_state(history[history_pos].state);
seb->ensure_focus();
@@ -3558,9 +3597,10 @@ void ScriptEditor::_update_history_pos(int p_new_pos) {
}
}
- if (Object::cast_to<EditorHelp>(n)) {
- Object::cast_to<EditorHelp>(n)->set_scroll(history[history_pos].state);
- Object::cast_to<EditorHelp>(n)->set_focused();
+ EditorHelp *eh = Object::cast_to<EditorHelp>(n);
+ if (eh) {
+ eh->set_scroll(history[history_pos].state);
+ eh->set_focused();
}
n->set_meta("__editor_pass", ++edit_pass);
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index f87cb0958c..de3ab3fd5a 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -472,12 +472,14 @@ class ScriptEditor : public PanelContainer {
void _history_back();
bool waiting_update_names;
+ bool lock_history = false;
void _help_class_open(const String &p_class);
void _help_class_goto(const String &p_desc);
bool _help_tab_goto(const String &p_name, const String &p_desc);
void _update_history_arrows();
void _save_history();
+ void _save_previous_state(Dictionary p_state);
void _go_to_tab(int p_idx);
void _update_history_pos(int p_new_pos);
void _update_script_colors();
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 0a6eacf11d..a642f35d6f 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -412,6 +412,14 @@ Variant ScriptTextEditor::get_navigation_state() {
return code_editor->get_navigation_state();
}
+Variant ScriptTextEditor::get_previous_state() {
+ return code_editor->get_previous_state();
+}
+
+void ScriptTextEditor::store_previous_state() {
+ return code_editor->store_previous_state();
+}
+
void ScriptTextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) {
code_editor->convert_case(p_case);
}
@@ -904,6 +912,18 @@ void ScriptTextEditor::_breakpoint_toggled(int p_row) {
EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), p_row + 1, code_editor->get_text_editor()->is_line_breakpointed(p_row));
}
+void ScriptTextEditor::_on_caret_moved() {
+ int current_line = code_editor->get_text_editor()->get_caret_line();
+ if (ABS(current_line - previous_line) >= 10) {
+ Dictionary nav_state = get_navigation_state();
+ nav_state["row"] = previous_line;
+ nav_state["scroll_position"] = -1;
+ emit_signal(SNAME("request_save_previous_state"), nav_state);
+ store_previous_state();
+ }
+ previous_line = current_line;
+}
+
void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_column) {
Node *base = get_tree()->get_edited_scene_root();
if (base) {
@@ -1344,12 +1364,12 @@ void ScriptTextEditor::_edit_option(int p_op) {
code_editor->get_text_editor()->duplicate_lines();
} break;
case EDIT_TOGGLE_FOLD_LINE: {
- int previous_line = -1;
+ int prev_line = -1;
for (int caret_idx : tx->get_caret_index_edit_order()) {
int line_idx = tx->get_caret_line(caret_idx);
- if (line_idx != previous_line) {
+ if (line_idx != prev_line) {
tx->toggle_foldable_line(line_idx);
- previous_line = line_idx;
+ prev_line = line_idx;
}
}
tx->queue_redraw();
@@ -2352,6 +2372,7 @@ ScriptTextEditor::ScriptTextEditor() {
code_editor->get_text_editor()->set_draw_breakpoints_gutter(true);
code_editor->get_text_editor()->set_draw_executing_lines_gutter(true);
code_editor->get_text_editor()->connect("breakpoint_toggled", callable_mp(this, &ScriptTextEditor::_breakpoint_toggled));
+ code_editor->get_text_editor()->connect("caret_changed", callable_mp(this, &ScriptTextEditor::_on_caret_moved));
connection_gutter = 1;
code_editor->get_text_editor()->add_gutter(connection_gutter);
diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h
index 2ea73d4c73..de89fe458c 100644
--- a/editor/plugins/script_text_editor.h
+++ b/editor/plugins/script_text_editor.h
@@ -99,6 +99,7 @@ class ScriptTextEditor : public ScriptEditorBase {
Color marked_line_color = Color(1, 1, 1);
Color folded_code_region_color = Color(1, 1, 1);
+ int previous_line = 0;
PopupPanel *color_panel = nullptr;
ColorPicker *color_picker = nullptr;
@@ -164,6 +165,8 @@ protected:
void _breakpoint_item_pressed(int p_idx);
void _breakpoint_toggled(int p_row);
+ void _on_caret_moved();
+
void _validate_script(); // No longer virtual.
void _update_warnings();
void _update_errors();
@@ -260,6 +263,9 @@ public:
virtual void validate() override;
+ Variant get_previous_state();
+ void store_previous_state();
+
ScriptTextEditor();
~ScriptTextEditor();
};
diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp
index fe02e3096c..8c41858d62 100644
--- a/editor/plugins/tiles/tile_set_editor.cpp
+++ b/editor/plugins/tiles/tile_set_editor.cpp
@@ -881,7 +881,9 @@ TileSetEditor::TileSetEditor() {
sources_add_button->set_flat(false);
sources_add_button->set_theme_type_variation("FlatButton");
sources_add_button->get_popup()->add_item(TTR("Atlas"));
+ sources_add_button->get_popup()->set_item_tooltip(-1, TTR("A palette of tiles made from a texture."));
sources_add_button->get_popup()->add_item(TTR("Scenes Collection"));
+ sources_add_button->get_popup()->set_item_tooltip(-1, TTR("A collection of scenes that can be instantiated and placed as tiles."));
sources_add_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_source_add_id_pressed));
sources_bottom_actions->add_child(sources_add_button);
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index 70cef0e345..4fe91d1cc5 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -802,18 +802,20 @@ void ProjectManager::_apply_project_tags() {
}
}
- ConfigFile cfg;
const String project_godot = project_list->get_selected_projects()[0].path.path_join("project.godot");
- Error err = cfg.load(project_godot);
- if (err != OK) {
- tag_edit_error->set_text(vformat(TTR("Couldn't load project at '%s' (error %d). It may be missing or corrupted."), project_godot, err));
+ ProjectSettings *cfg = memnew(ProjectSettings(project_godot));
+ if (!cfg->is_project_loaded()) {
+ memdelete(cfg);
+ tag_edit_error->set_text(vformat(TTR("Couldn't load project at '%s'. It may be missing or corrupted."), project_godot));
tag_edit_error->show();
callable_mp((Window *)tag_manage_dialog, &Window::show).call_deferred(); // Make sure the dialog does not disappear.
return;
} else {
tags.sort();
- cfg.set_value("application", "config/tags", tags);
- err = cfg.save(project_godot);
+ cfg->set("application/config/tags", tags);
+ Error err = cfg->save_custom(project_godot);
+ memdelete(cfg);
+
if (err != OK) {
tag_edit_error->set_text(vformat(TTR("Couldn't save project at '%s' (error %d)."), project_godot, err));
tag_edit_error->show();
diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp
index 70e8484a78..c4aeac434b 100644
--- a/editor/project_settings_editor.cpp
+++ b/editor/project_settings_editor.cpp
@@ -287,6 +287,8 @@ void ProjectSettingsEditor::_add_feature_overrides() {
presets.insert("s3tc");
presets.insert("etc2");
presets.insert("editor");
+ presets.insert("editor_hint");
+ presets.insert("editor_runtime");
presets.insert("template_debug");
presets.insert("template_release");
presets.insert("debug");
diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp
index cd043e165e..c12f95753f 100644
--- a/editor/script_create_dialog.cpp
+++ b/editor/script_create_dialog.cpp
@@ -883,6 +883,7 @@ ScriptCreateDialog::ScriptCreateDialog() {
parent_name->connect("text_changed", callable_mp(this, &ScriptCreateDialog::_parent_name_changed));
parent_name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
hb->add_child(parent_name);
+ register_text_enter(parent_name);
parent_search_button = memnew(Button);
parent_search_button->connect("pressed", callable_mp(this, &ScriptCreateDialog::_browse_class_in_tree));
hb->add_child(parent_search_button);
diff --git a/editor/shader_create_dialog.cpp b/editor/shader_create_dialog.cpp
index 8d77b14ab0..43fd559393 100644
--- a/editor/shader_create_dialog.cpp
+++ b/editor/shader_create_dialog.cpp
@@ -243,7 +243,12 @@ void fog() {
emit_signal(SNAME("shader_include_created"), shader_inc);
} else {
- if (!is_built_in) {
+ if (is_built_in) {
+ Node *edited_scene = get_tree()->get_edited_scene_root();
+ if (likely(edited_scene)) {
+ shader->set_path(edited_scene->get_scene_file_path() + "::");
+ }
+ } else {
String lpath = ProjectSettings::get_singleton()->localize_path(file_path->get_text());
shader->set_path(lpath);
diff --git a/editor/shader_globals_editor.cpp b/editor/shader_globals_editor.cpp
index 86a78d813e..216ccd71ab 100644
--- a/editor/shader_globals_editor.cpp
+++ b/editor/shader_globals_editor.cpp
@@ -219,7 +219,7 @@ protected:
case RS::GLOBAL_VAR_TYPE_SAMPLER2DARRAY: {
pinfo.type = Variant::OBJECT;
pinfo.hint = PROPERTY_HINT_RESOURCE_TYPE;
- pinfo.hint_string = "Texture2DArray";
+ pinfo.hint_string = "Texture2DArray,CompressedTexture2DArray";
} break;
case RS::GLOBAL_VAR_TYPE_SAMPLER3D: {
pinfo.type = Variant::OBJECT;
@@ -229,7 +229,7 @@ protected:
case RS::GLOBAL_VAR_TYPE_SAMPLERCUBE: {
pinfo.type = Variant::OBJECT;
pinfo.hint = PROPERTY_HINT_RESOURCE_TYPE;
- pinfo.hint_string = "Cubemap";
+ pinfo.hint_string = "Cubemap,CompressedCubemap";
} break;
default: {
} break;
diff --git a/main/main.cpp b/main/main.cpp
index 78a539fff7..eee634086e 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1746,6 +1746,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
}
}
+ OS::get_singleton()->_in_editor = editor;
if (globals->setup(project_path, main_pack, upwards, editor) == OK) {
#ifdef TOOLS_ENABLED
found_project = true;
@@ -2341,10 +2342,13 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
Engine::get_singleton()->set_max_physics_steps_per_frame(GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "physics/common/max_physics_steps_per_frame", PROPERTY_HINT_RANGE, "1,100,1"), 8));
Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5));
Engine::get_singleton()->set_max_fps(GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/max_fps", PROPERTY_HINT_RANGE, "0,1000,1"), 0));
- Engine::get_singleton()->set_audio_output_latency(GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "audio/driver/output_latency", PROPERTY_HINT_RANGE, "1,100,1"), 15));
+
+ GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "audio/driver/output_latency", PROPERTY_HINT_RANGE, "1,100,1"), 15);
// Use a safer default output_latency for web to avoid audio cracking on low-end devices, especially mobile.
GLOBAL_DEF_RST("audio/driver/output_latency.web", 50);
+ Engine::get_singleton()->set_audio_output_latency(GLOBAL_GET("audio/driver/output_latency"));
+
GLOBAL_DEF("debug/settings/stdout/print_fps", false);
GLOBAL_DEF("debug/settings/stdout/print_gpu_profile", false);
GLOBAL_DEF("debug/settings/stdout/verbose_stdout", false);
diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp
index e92609f42f..5f94a80566 100644
--- a/modules/fbx/fbx_document.cpp
+++ b/modules/fbx/fbx_document.cpp
@@ -86,6 +86,17 @@ static Quaternion _as_quaternion(const ufbx_quat &p_quat) {
return Quaternion(real_t(p_quat.x), real_t(p_quat.y), real_t(p_quat.z), real_t(p_quat.w));
}
+static Transform3D _as_transform(const ufbx_transform &p_xform) {
+ Transform3D result;
+ result.origin = FBXDocument::_as_vec3(p_xform.translation);
+ result.basis.set_quaternion_scale(_as_quaternion(p_xform.rotation), FBXDocument::_as_vec3(p_xform.scale));
+ return result;
+}
+
+static real_t _relative_error(const Vector3 &p_a, const Vector3 &p_b) {
+ return p_a.distance_to(p_b) / MAX(p_a.length(), p_b.length());
+}
+
static Color _material_color(const ufbx_material_map &p_map) {
if (p_map.value_components == 1) {
float r = float(p_map.value_real);
@@ -196,6 +207,16 @@ static uint32_t _decode_vertex_index(const Vector3 &p_vertex) {
return uint32_t(p_vertex.x) | uint32_t(p_vertex.y) << 16;
}
+static ufbx_skin_deformer *_find_skin_deformer(ufbx_skin_cluster *p_cluster) {
+ for (const ufbx_connection &conn : p_cluster->element.connections_src) {
+ ufbx_skin_deformer *deformer = ufbx_as_skin_deformer(conn.dst);
+ if (deformer) {
+ return deformer;
+ }
+ }
+ return nullptr;
+}
+
struct ThreadPoolFBX {
struct Group {
ufbx_thread_pool_context ctx = {};
@@ -333,23 +354,67 @@ Error FBXDocument::_parse_nodes(Ref<FBXState> p_state) {
}
{
- node->transform.origin = _as_vec3(fbx_node->local_transform.translation);
- node->transform.basis.set_quaternion_scale(_as_quaternion(fbx_node->local_transform.rotation), _as_vec3(fbx_node->local_transform.scale));
-
- if (fbx_node->bind_pose) {
- ufbx_bone_pose *pose = ufbx_get_bone_pose(fbx_node->bind_pose, fbx_node);
- ufbx_transform rest_transform = ufbx_matrix_to_transform(&pose->bone_to_parent);
-
- Vector3 rest_position = _as_vec3(rest_transform.translation);
- Quaternion rest_rotation = _as_quaternion(rest_transform.rotation);
- Vector3 rest_scale = _as_vec3(rest_transform.scale);
- Transform3D godot_rest_xform;
- godot_rest_xform.basis.set_quaternion_scale(rest_rotation, rest_scale);
- godot_rest_xform.origin = rest_position;
- node->set_additional_data("GODOT_rest_transform", godot_rest_xform);
- } else {
- node->set_additional_data("GODOT_rest_transform", node->transform);
+ node->transform = _as_transform(fbx_node->local_transform);
+
+ bool found_rest_xform = false;
+ bool bad_rest_xform = false;
+ Transform3D candidate_rest_xform;
+
+ if (fbx_node->parent) {
+ // Attempt to resolve a rest pose for bones: This uses internal FBX connections to find
+ // all skin clusters connected to the bone.
+ for (const ufbx_connection &child_conn : fbx_node->element.connections_src) {
+ ufbx_skin_cluster *child_cluster = ufbx_as_skin_cluster(child_conn.dst);
+ if (!child_cluster)
+ continue;
+ ufbx_skin_deformer *child_deformer = _find_skin_deformer(child_cluster);
+ if (!child_deformer)
+ continue;
+
+ // Found a skin cluster: Now iterate through all the skin clusters of the parent and
+ // try to find one that used by the same deformer.
+ for (const ufbx_connection &parent_conn : fbx_node->parent->element.connections_src) {
+ ufbx_skin_cluster *parent_cluster = ufbx_as_skin_cluster(parent_conn.dst);
+ if (!parent_cluster)
+ continue;
+ ufbx_skin_deformer *parent_deformer = _find_skin_deformer(parent_cluster);
+ if (parent_deformer != child_deformer)
+ continue;
+
+ // Success: Found two skin clusters from the same deformer, now we can resolve the
+ // local bind pose from the difference between the two world-space bind poses.
+ ufbx_matrix child_to_world = child_cluster->bind_to_world;
+ ufbx_matrix world_to_parent = ufbx_matrix_invert(&parent_cluster->bind_to_world);
+ ufbx_matrix child_to_parent = ufbx_matrix_mul(&world_to_parent, &child_to_world);
+ Transform3D xform = _as_transform(ufbx_matrix_to_transform(&child_to_parent));
+
+ if (!found_rest_xform) {
+ // Found the first bind pose for the node, assume that this one is good
+ found_rest_xform = true;
+ candidate_rest_xform = xform;
+ } else if (!bad_rest_xform) {
+ // Found another: Let's hope it's similar to the previous one, if not warn and
+ // use the initial pose, which is used by default if rest pose is not found.
+ real_t error = 0.0f;
+ error += _relative_error(candidate_rest_xform.origin, xform.origin);
+ for (int i = 0; i < 3; i++) {
+ error += _relative_error(candidate_rest_xform.basis.rows[i], xform.basis.rows[i]);
+ }
+ const real_t max_error = 0.01f;
+ if (error >= max_error) {
+ WARN_PRINT(vformat("FBX: Node '%s' has multiple bind poses, using initial pose as rest pose.", node->get_name()));
+ bad_rest_xform = true;
+ }
+ }
+ }
+ }
+ }
+
+ Transform3D godot_rest_xform = node->transform;
+ if (found_rest_xform && !bad_rest_xform) {
+ godot_rest_xform = candidate_rest_xform;
}
+ node->set_additional_data("GODOT_rest_transform", godot_rest_xform);
}
for (const ufbx_node *child : fbx_node->children) {
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 36b157521d..bfe090edb0 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -45,8 +45,8 @@ uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool
}
uint32_t GDScriptByteCodeGenerator::add_local(const StringName &p_name, const GDScriptDataType &p_type) {
- int stack_pos = locals.size() + RESERVED_STACK;
- locals.push_back(StackSlot(p_type.builtin_type));
+ int stack_pos = locals.size() + GDScriptFunction::FIXED_ADDRESSES_MAX;
+ locals.push_back(StackSlot(p_type.builtin_type, p_type.can_contain_object()));
add_stack_identifier(p_name, stack_pos);
return stack_pos;
}
@@ -122,7 +122,7 @@ uint32_t GDScriptByteCodeGenerator::add_temporary(const GDScriptDataType &p_type
List<int> &pool = temporaries_pool[temp_type];
if (pool.is_empty()) {
- StackSlot new_temp(temp_type);
+ StackSlot new_temp(temp_type, p_type.can_contain_object());
int idx = temporaries.size();
pool.push_back(idx);
temporaries.push_back(new_temp);
@@ -136,15 +136,14 @@ uint32_t GDScriptByteCodeGenerator::add_temporary(const GDScriptDataType &p_type
void GDScriptByteCodeGenerator::pop_temporary() {
ERR_FAIL_COND(used_temporaries.is_empty());
int slot_idx = used_temporaries.back()->get();
- const StackSlot &slot = temporaries[slot_idx];
- if (slot.type == Variant::NIL) {
+ if (temporaries[slot_idx].can_contain_object) {
// Avoid keeping in the stack long-lived references to objects,
- // which may prevent RefCounted objects from being freed.
+ // which may prevent `RefCounted` objects from being freed.
// However, the cleanup will be performed an the end of the
// statement, to allow object references to survive chaining.
- temporaries_pending_clear.push_back(slot_idx);
+ temporaries_pending_clear.insert(slot_idx);
}
- temporaries_pool[slot.type].push_back(slot_idx);
+ temporaries_pool[temporaries[slot_idx].type].push_back(slot_idx);
used_temporaries.pop_back();
}
@@ -187,7 +186,7 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
append_opcode(GDScriptFunction::OPCODE_END);
for (int i = 0; i < temporaries.size(); i++) {
- int stack_index = i + max_locals + RESERVED_STACK;
+ int stack_index = i + max_locals + GDScriptFunction::FIXED_ADDRESSES_MAX;
for (int j = 0; j < temporaries[i].bytecode_indices.size(); j++) {
opcodes.write[temporaries[i].bytecode_indices[j]] = stack_index | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS);
}
@@ -398,7 +397,7 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
if (debug_stack) {
function->stack_debug = stack_debug;
}
- function->_stack_size = RESERVED_STACK + max_locals + temporaries.size();
+ function->_stack_size = GDScriptFunction::FIXED_ADDRESSES_MAX + max_locals + temporaries.size();
function->_instruction_args_size = instr_args_max;
#ifdef DEBUG_ENABLED
@@ -945,6 +944,11 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr
}
}
+void GDScriptByteCodeGenerator::write_assign_null(const Address &p_target) {
+ append_opcode(GDScriptFunction::OPCODE_ASSIGN_NULL);
+ append(p_target);
+}
+
void GDScriptByteCodeGenerator::write_assign_true(const Address &p_target) {
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TRUE);
append(p_target);
@@ -1579,9 +1583,8 @@ void GDScriptByteCodeGenerator::write_for(const Address &p_variable, bool p_use_
if (p_use_conversion) {
write_assign_with_conversion(p_variable, temp);
- const GDScriptDataType &type = p_variable.type;
- if (type.kind != GDScriptDataType::BUILTIN || type.builtin_type == Variant::ARRAY || type.builtin_type == Variant::DICTIONARY) {
- write_assign_false(temp); // Can contain RefCounted, so clear it.
+ if (p_variable.type.can_contain_object()) {
+ clear_address(temp); // Can contain `RefCounted`, so clear it.
}
}
}
@@ -1746,23 +1749,56 @@ void GDScriptByteCodeGenerator::end_block() {
pop_stack_identifiers();
}
-void GDScriptByteCodeGenerator::clean_temporaries() {
- List<int>::Element *E = temporaries_pending_clear.front();
- while (E) {
- // The temporary may have been re-used as something else than an object
- // since it was added to the list. In that case, there's no need to clear it.
- int slot_idx = E->get();
- const StackSlot &slot = temporaries[slot_idx];
- if (slot.type == Variant::NIL) {
- write_assign_false(Address(Address::TEMPORARY, slot_idx));
+void GDScriptByteCodeGenerator::clear_temporaries() {
+ for (int slot_idx : temporaries_pending_clear) {
+ // The temporary may have been re-used as something else since it was added to the list.
+ // In that case, there's **no** need to clear it.
+ if (temporaries[slot_idx].can_contain_object) {
+ clear_address(Address(Address::TEMPORARY, slot_idx)); // Can contain `RefCounted`, so clear it.
+ }
+ }
+ temporaries_pending_clear.clear();
+}
+
+void GDScriptByteCodeGenerator::clear_address(const Address &p_address) {
+ // Do not check `is_local_dirty()` here! Always clear the address since the codegen doesn't track the compiler.
+ // Also, this method is used to initialize local variables of built-in types, since they cannot be `null`.
+
+ if (p_address.type.has_type && p_address.type.kind == GDScriptDataType::BUILTIN) {
+ switch (p_address.type.builtin_type) {
+ case Variant::BOOL:
+ write_assign_false(p_address);
+ break;
+ case Variant::ARRAY:
+ if (p_address.type.has_container_element_type(0)) {
+ write_construct_typed_array(p_address, p_address.type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
+ } else {
+ write_construct(p_address, p_address.type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
+ }
+ break;
+ case Variant::NIL:
+ case Variant::OBJECT:
+ write_assign_null(p_address);
+ break;
+ default:
+ write_construct(p_address, p_address.type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
+ break;
}
+ } else {
+ write_assign_null(p_address);
+ }
- List<int>::Element *next = E->next();
- E->erase();
- E = next;
+ if (p_address.mode == Address::LOCAL_VARIABLE) {
+ dirty_locals.erase(p_address.address);
}
}
+// Returns `true` if the local has been re-used and not cleaned up with `clear_address()`.
+bool GDScriptByteCodeGenerator::is_local_dirty(const Address &p_address) const {
+ ERR_FAIL_COND_V(p_address.mode != Address::LOCAL_VARIABLE, false);
+ return dirty_locals.has(p_address.address);
+}
+
GDScriptByteCodeGenerator::~GDScriptByteCodeGenerator() {
if (!ended && function != nullptr) {
memdelete(function);
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index f902cb10cc..5a736b2554 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -38,15 +38,14 @@
class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
struct StackSlot {
Variant::Type type = Variant::NIL;
+ bool can_contain_object = true;
Vector<int> bytecode_indices;
StackSlot() = default;
- StackSlot(Variant::Type p_type) :
- type(p_type) {}
+ StackSlot(Variant::Type p_type, bool p_can_contain_object) :
+ type(p_type), can_contain_object(p_can_contain_object) {}
};
- const static int RESERVED_STACK = 3; // For self, class, and nil.
-
struct CallTarget {
Address target;
bool is_new_temporary = false;
@@ -85,9 +84,11 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
RBMap<StringName, int> local_constants;
Vector<StackSlot> locals;
+ HashSet<int> dirty_locals;
+
Vector<StackSlot> temporaries;
List<int> used_temporaries;
- List<int> temporaries_pending_clear;
+ HashSet<int> temporaries_pending_clear;
RBMap<Variant::Type, List<int>> temporaries_pool;
List<GDScriptFunction::StackDebug> stack_debug;
@@ -193,6 +194,9 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
ERR_PRINT("Leaving block with non-zero temporary variables: " + itos(used_temporaries.size()));
}
#endif
+ for (int i = current_locals; i < locals.size(); i++) {
+ dirty_locals.insert(i + GDScriptFunction::FIXED_ADDRESSES_MAX);
+ }
locals.resize(current_locals);
if (debug_stack) {
for (const KeyValue<StringName, int> &E : block_identifiers) {
@@ -455,7 +459,9 @@ public:
virtual uint32_t add_or_get_name(const StringName &p_name) override;
virtual uint32_t add_temporary(const GDScriptDataType &p_type) override;
virtual void pop_temporary() override;
- virtual void clean_temporaries() override;
+ virtual void clear_temporaries() override;
+ virtual void clear_address(const Address &p_address) override;
+ virtual bool is_local_dirty(const Address &p_address) const override;
virtual void start_parameters() override;
virtual void end_parameters() override;
@@ -496,6 +502,7 @@ public:
virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) override;
virtual void write_assign(const Address &p_target, const Address &p_source) override;
virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) override;
+ virtual void write_assign_null(const Address &p_target) override;
virtual void write_assign_true(const Address &p_target) override;
virtual void write_assign_false(const Address &p_target) override;
virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src, bool p_use_conversion) override;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index 7ad8f841aa..4c33ed499a 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -73,7 +73,9 @@ public:
virtual uint32_t add_or_get_name(const StringName &p_name) = 0;
virtual uint32_t add_temporary(const GDScriptDataType &p_type) = 0;
virtual void pop_temporary() = 0;
- virtual void clean_temporaries() = 0;
+ virtual void clear_temporaries() = 0;
+ virtual void clear_address(const Address &p_address) = 0;
+ virtual bool is_local_dirty(const Address &p_address) const = 0;
virtual void start_parameters() = 0;
virtual void end_parameters() = 0;
@@ -114,6 +116,7 @@ public:
virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) = 0;
virtual void write_assign(const Address &p_target, const Address &p_source) = 0;
virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) = 0;
+ virtual void write_assign_null(const Address &p_target) = 0;
virtual void write_assign_true(const Address &p_target) = 0;
virtual void write_assign_false(const Address &p_target) = 0;
virtual void write_assign_default_parameter(const Address &dst, const Address &src, bool p_use_conversion) = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index c526d9c0a4..734e37bc09 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -1826,7 +1826,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
ERR_FAIL_V_MSG(p_previous_test, "Reaching the end of pattern compilation without matching a pattern.");
}
-List<GDScriptCodeGenerator::Address> GDScriptCompiler::_add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block) {
+List<GDScriptCodeGenerator::Address> GDScriptCompiler::_add_block_locals(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block) {
List<GDScriptCodeGenerator::Address> addresses;
for (int i = 0; i < p_block->locals.size(); i++) {
if (p_block->locals[i].type == GDScriptParser::SuiteNode::Local::PARAMETER || p_block->locals[i].type == GDScriptParser::SuiteNode::Local::FOR_VARIABLE) {
@@ -1838,27 +1838,25 @@ List<GDScriptCodeGenerator::Address> GDScriptCompiler::_add_locals_in_block(Code
return addresses;
}
-// Avoid keeping in the stack long-lived references to objects, which may prevent RefCounted objects from being freed.
-void GDScriptCompiler::_clear_addresses(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_addresses) {
- for (const List<GDScriptCodeGenerator::Address>::Element *E = p_addresses.front(); E; E = E->next()) {
- GDScriptDataType type = E->get().type;
- // If not an object and cannot contain an object, no need to clear.
- if (type.kind != GDScriptDataType::BUILTIN || type.builtin_type == Variant::ARRAY || type.builtin_type == Variant::DICTIONARY) {
- codegen.generator->write_assign_false(E->get());
+// Avoid keeping in the stack long-lived references to objects, which may prevent `RefCounted` objects from being freed.
+void GDScriptCompiler::_clear_block_locals(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_locals) {
+ for (const GDScriptCodeGenerator::Address &local : p_locals) {
+ if (local.type.can_contain_object()) {
+ codegen.generator->clear_address(local);
}
}
}
-Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals, bool p_reset_locals) {
+Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals, bool p_clear_locals) {
Error err = OK;
GDScriptCodeGenerator *gen = codegen.generator;
List<GDScriptCodeGenerator::Address> block_locals;
- gen->clean_temporaries();
+ gen->clear_temporaries();
codegen.start_block();
if (p_add_locals) {
- block_locals = _add_locals_in_block(codegen, p_block);
+ block_locals = _add_block_locals(codegen, p_block);
}
for (int i = 0; i < p_block->statements.size(); i++) {
@@ -1873,7 +1871,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
case GDScriptParser::Node::MATCH: {
const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s);
- codegen.start_block();
+ codegen.start_block(); // Add an extra block, since the binding pattern and @special variables belong to the branch scope.
// Evaluate the match expression.
GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script));
@@ -1914,7 +1912,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
codegen.start_block(); // Create an extra block around for binds.
// Add locals in block before patterns, so temporaries don't use the stack address for binds.
- List<GDScriptCodeGenerator::Address> branch_locals = _add_locals_in_block(codegen, branch->block);
+ List<GDScriptCodeGenerator::Address> branch_locals = _add_block_locals(codegen, branch->block);
#ifdef DEBUG_ENABLED
// Add a newline before each branch, since the debugger needs those.
@@ -1961,7 +1959,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
return err;
}
- _clear_addresses(codegen, branch_locals);
+ _clear_block_locals(codegen, branch_locals);
codegen.end_block(); // Get out of extra block.
}
@@ -2003,7 +2001,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
case GDScriptParser::Node::FOR: {
const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s);
- codegen.start_block();
+ codegen.start_block(); // Add an extra block, since the iterator and @special variables belong to the loop scope.
+
GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype(), codegen.script));
gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype(), codegen.script));
@@ -2021,14 +2020,21 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
gen->write_for(iterator, for_n->use_conversion_assign);
- err = _parse_block(codegen, for_n->loop);
+ // Loop variables must be cleared even when `break`/`continue` is used.
+ List<GDScriptCodeGenerator::Address> loop_locals = _add_block_locals(codegen, for_n->loop);
+
+ //_clear_block_locals(codegen, loop_locals); // Inside loop, before block - for `continue`. // TODO
+
+ err = _parse_block(codegen, for_n->loop, false); // Don't add locals again.
if (err) {
return err;
}
gen->write_endfor();
- codegen.end_block();
+ _clear_block_locals(codegen, loop_locals); // Outside loop, after block - for `break` and normal exit.
+
+ codegen.end_block(); // Get out of extra block.
} break;
case GDScriptParser::Node::WHILE: {
const GDScriptParser::WhileNode *while_n = static_cast<const GDScriptParser::WhileNode *>(s);
@@ -2046,12 +2052,19 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
codegen.generator->pop_temporary();
}
- err = _parse_block(codegen, while_n->loop);
+ // Loop variables must be cleared even when `break`/`continue` is used.
+ List<GDScriptCodeGenerator::Address> loop_locals = _add_block_locals(codegen, while_n->loop);
+
+ //_clear_block_locals(codegen, loop_locals); // Inside loop, before block - for `continue`. // TODO
+
+ err = _parse_block(codegen, while_n->loop, false); // Don't add locals again.
if (err) {
return err;
}
gen->write_endwhile();
+
+ _clear_block_locals(codegen, loop_locals); // Outside loop, after block - for `break` and normal exit.
} break;
case GDScriptParser::Node::BREAK: {
gen->write_break();
@@ -2134,21 +2147,16 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
codegen.generator->pop_temporary();
}
initialized = true;
- } else if (local_type.has_type) {
- // Initialize with default for type.
- if (local_type.has_container_element_type(0)) {
- codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
- initialized = true;
- } else if (local_type.kind == GDScriptDataType::BUILTIN) {
- codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
- initialized = true;
- }
- // The `else` branch is for objects, in such case we leave it as `null`.
+ } else if ((local_type.has_type && local_type.kind == GDScriptDataType::BUILTIN) || codegen.generator->is_local_dirty(local)) {
+ // Initialize with default for the type. Built-in types must always be cleared (they cannot be `null`).
+ // Objects and untyped variables are assigned to `null` only if the stack address has been re-used and not cleared.
+ codegen.generator->clear_address(local);
+ initialized = true;
}
- // Assigns a null for the unassigned variables in loops.
+ // Don't check `is_local_dirty()` since the variable must be assigned to `null` **on each iteration**.
if (!initialized && p_block->is_in_loop) {
- codegen.generator->write_construct(local, Variant::NIL, Vector<GDScriptCodeGenerator::Address>());
+ codegen.generator->clear_address(local);
}
} break;
case GDScriptParser::Node::CONSTANT: {
@@ -2180,11 +2188,11 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
} break;
}
- gen->clean_temporaries();
+ gen->clear_temporaries();
}
- if (p_add_locals && p_reset_locals) {
- _clear_addresses(codegen, block_locals);
+ if (p_add_locals && p_clear_locals) {
+ _clear_block_locals(codegen, block_locals);
}
codegen.end_block();
diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h
index 637d61ca3b..45f0f9e19b 100644
--- a/modules/gdscript/gdscript_compiler.h
+++ b/modules/gdscript/gdscript_compiler.h
@@ -153,9 +153,9 @@ class GDScriptCompiler {
GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false);
GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested);
- List<GDScriptCodeGenerator::Address> _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
- void _clear_addresses(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_addresses);
- Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true, bool p_reset_locals = true);
+ List<GDScriptCodeGenerator::Address> _add_block_locals(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block);
+ void _clear_block_locals(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_locals);
+ Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true, bool p_clear_locals = true);
GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false);
GDScriptFunction *_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class);
Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index 26f7cb7537..c7873dcd52 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -360,6 +360,13 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 3;
} break;
+ case OPCODE_ASSIGN_NULL: {
+ text += "assign ";
+ text += DADDR(1);
+ text += " = null";
+
+ incr += 2;
+ } break;
case OPCODE_ASSIGN_TRUE: {
text += "assign ";
text += DADDR(1);
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 002fc159fa..184d256bcd 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -78,17 +78,17 @@ public:
if (valid && builtin_type == Variant::ARRAY && has_container_element_type(0)) {
Array array = p_variant;
if (array.is_typed()) {
- GDScriptDataType array_container_type = get_container_element_type(0);
+ const GDScriptDataType &elem_type = container_element_types[0];
Variant::Type array_builtin_type = (Variant::Type)array.get_typed_builtin();
StringName array_native_type = array.get_typed_class_name();
Ref<Script> array_script_type_ref = array.get_typed_script();
if (array_script_type_ref.is_valid()) {
- valid = (array_container_type.kind == SCRIPT || array_container_type.kind == GDSCRIPT) && array_container_type.script_type == array_script_type_ref.ptr();
+ valid = (elem_type.kind == SCRIPT || elem_type.kind == GDSCRIPT) && elem_type.script_type == array_script_type_ref.ptr();
} else if (array_native_type != StringName()) {
- valid = array_container_type.kind == NATIVE && array_container_type.native_type == array_native_type;
+ valid = elem_type.kind == NATIVE && elem_type.native_type == array_native_type;
} else {
- valid = array_container_type.kind == BUILTIN && array_container_type.builtin_type == array_builtin_type;
+ valid = elem_type.kind == BUILTIN && elem_type.builtin_type == array_builtin_type;
}
} else {
valid = false;
@@ -147,6 +147,25 @@ public:
return false;
}
+ bool can_contain_object() const {
+ if (has_type && kind == BUILTIN) {
+ switch (builtin_type) {
+ case Variant::ARRAY:
+ if (has_container_element_type(0)) {
+ return container_element_types[0].can_contain_object();
+ }
+ return true;
+ case Variant::DICTIONARY:
+ case Variant::NIL:
+ case Variant::OBJECT:
+ return true;
+ default:
+ return false;
+ }
+ }
+ return true;
+ }
+
void set_container_element_type(int p_index, const GDScriptDataType &p_element_type) {
ERR_FAIL_COND(p_index < 0);
while (p_index >= container_element_types.size()) {
@@ -218,6 +237,7 @@ public:
OPCODE_SET_STATIC_VARIABLE, // Only for GDScript.
OPCODE_GET_STATIC_VARIABLE, // Only for GDScript.
OPCODE_ASSIGN,
+ OPCODE_ASSIGN_NULL,
OPCODE_ASSIGN_TRUE,
OPCODE_ASSIGN_FALSE,
OPCODE_ASSIGN_TYPED_BUILTIN,
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index 1a8c22cc11..842975698b 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -234,6 +234,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_SET_STATIC_VARIABLE, \
&&OPCODE_GET_STATIC_VARIABLE, \
&&OPCODE_ASSIGN, \
+ &&OPCODE_ASSIGN_NULL, \
&&OPCODE_ASSIGN_TRUE, \
&&OPCODE_ASSIGN_FALSE, \
&&OPCODE_ASSIGN_TYPED_BUILTIN, \
@@ -1256,6 +1257,16 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
+ OPCODE(OPCODE_ASSIGN_NULL) {
+ CHECK_SPACE(2);
+ GET_VARIANT_PTR(dst, 0);
+
+ *dst = Variant();
+
+ ip += 2;
+ }
+ DISPATCH_OPCODE;
+
OPCODE(OPCODE_ASSIGN_TRUE) {
CHECK_SPACE(2);
GET_VARIANT_PTR(dst, 0);
@@ -2649,6 +2660,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GET_VARIANT_PTR(counter, 0);
GET_VARIANT_PTR(container, 1);
+ *counter = Variant();
+
bool valid;
if (!container->iter_init(*counter, valid)) {
#ifdef DEBUG_ENABLED
@@ -2976,20 +2989,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#else
Object *obj = *VariantInternal::get_object(container);
#endif
+
+ *counter = Variant();
+
Array ref;
ref.push_back(*counter);
Variant vref;
VariantInternal::initialize(&vref, Variant::ARRAY);
*VariantInternal::get_array(&vref) = ref;
- Variant **args = instruction_args; // Overriding an instruction argument, but we don't need access to that anymore.
- args[0] = &vref;
+ const Variant *args[] = { &vref };
Callable::CallError ce;
- Variant has_next = obj->callp(CoreStringNames::get_singleton()->_iter_init, (const Variant **)args, 1, ce);
+ Variant has_next = obj->callp(CoreStringNames::get_singleton()->_iter_init, args, 1, ce);
#ifdef DEBUG_ENABLED
- if (ce.error != Callable::CallError::CALL_OK) {
+ if (ref.size() != 1 || ce.error != Callable::CallError::CALL_OK) {
err_text = vformat(R"(There was an error calling "_iter_next" on iterator object of type %s.)", *container);
OPCODE_BREAK;
}
@@ -2999,8 +3014,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
ip = jumpto;
} else {
+ *counter = ref[0];
+
GET_VARIANT_PTR(iterator, 2);
- *iterator = obj->callp(CoreStringNames::get_singleton()->_iter_get, (const Variant **)args, 1, ce);
+ *iterator = obj->callp(CoreStringNames::get_singleton()->_iter_get, (const Variant **)&counter, 1, ce);
#ifdef DEBUG_ENABLED
if (ce.error != Callable::CallError::CALL_OK) {
err_text = vformat(R"(There was an error calling "_iter_get" on iterator object of type %s.)", *container);
@@ -3307,20 +3324,20 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#else
Object *obj = *VariantInternal::get_object(container);
#endif
+
Array ref;
ref.push_back(*counter);
Variant vref;
VariantInternal::initialize(&vref, Variant::ARRAY);
*VariantInternal::get_array(&vref) = ref;
- Variant **args = instruction_args; // Overriding an instruction argument, but we don't need access to that anymore.
- args[0] = &vref;
+ const Variant *args[] = { &vref };
Callable::CallError ce;
- Variant has_next = obj->callp(CoreStringNames::get_singleton()->_iter_next, (const Variant **)args, 1, ce);
+ Variant has_next = obj->callp(CoreStringNames::get_singleton()->_iter_next, args, 1, ce);
#ifdef DEBUG_ENABLED
- if (ce.error != Callable::CallError::CALL_OK) {
+ if (ref.size() != 1 || ce.error != Callable::CallError::CALL_OK) {
err_text = vformat(R"(There was an error calling "_iter_next" on iterator object of type %s.)", *container);
OPCODE_BREAK;
}
@@ -3330,8 +3347,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size);
ip = jumpto;
} else {
+ *counter = ref[0];
+
GET_VARIANT_PTR(iterator, 2);
- *iterator = obj->callp(CoreStringNames::get_singleton()->_iter_get, (const Variant **)args, 1, ce);
+ *iterator = obj->callp(CoreStringNames::get_singleton()->_iter_get, (const Variant **)&counter, 1, ce);
#ifdef DEBUG_ENABLED
if (ce.error != Callable::CallError::CALL_OK) {
err_text = vformat(R"(There was an error calling "_iter_get" on iterator object of type %s.)", *container);
diff --git a/modules/gdscript/tests/scripts/runtime/features/object_iterators.gd b/modules/gdscript/tests/scripts/runtime/features/object_iterators.gd
new file mode 100644
index 0000000000..6fe28c6f78
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/object_iterators.gd
@@ -0,0 +1,49 @@
+class MyIterator:
+ var count: int
+
+ func _init(p_count: int) -> void:
+ count = p_count
+
+ func _iter_init(arg: Array) -> bool:
+ prints("_iter_init", arg)
+ arg[0] = 0
+ return arg[0] < count
+
+ func _iter_next(arg: Array) -> bool:
+ prints("_iter_next", arg)
+ arg[0] += 1
+ return arg[0] < count
+
+ func _iter_get(arg: Variant) -> Variant:
+ prints("_iter_get", arg)
+ return arg
+
+func test():
+ var container := PackedDataContainer.new()
+ var _err := container.pack([{
+ id = 123,
+ node_path = ^"/some/path",
+ data = PackedByteArray(),
+ }])
+
+ for ref: PackedDataContainerRef in container:
+ for key: String in ref:
+ print(key)
+
+ print("===")
+
+ for ref: Variant in container:
+ for key: String in ref:
+ print(key)
+
+ print("===")
+
+ var hard_custom := MyIterator.new(3)
+ for x in hard_custom:
+ print(x)
+
+ print("===")
+
+ var weak_custom: Variant = MyIterator.new(3)
+ for x in weak_custom:
+ print(x)
diff --git a/modules/gdscript/tests/scripts/runtime/features/object_iterators.out b/modules/gdscript/tests/scripts/runtime/features/object_iterators.out
new file mode 100644
index 0000000000..942a2c9dd8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/object_iterators.out
@@ -0,0 +1,30 @@
+GDTEST_OK
+id
+node_path
+data
+===
+id
+node_path
+data
+===
+_iter_init [<null>]
+_iter_get 0
+0
+_iter_next [0]
+_iter_get 1
+1
+_iter_next [1]
+_iter_get 2
+2
+_iter_next [2]
+===
+_iter_init [<null>]
+_iter_get 0
+0
+_iter_next [0]
+_iter_get 1
+1
+_iter_next [1]
+_iter_get 2
+2
+_iter_next [2]
diff --git a/modules/gdscript/tests/scripts/runtime/features/reset_uninit_local_vars.gd b/modules/gdscript/tests/scripts/runtime/features/reset_uninit_local_vars.gd
new file mode 100644
index 0000000000..ef2d9a1d60
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/reset_uninit_local_vars.gd
@@ -0,0 +1,21 @@
+# GH-89958
+
+func test():
+ if true:
+ @warning_ignore("unused_variable")
+ var a = 1
+ @warning_ignore("unused_variable")
+ var b := 1
+ @warning_ignore("unused_variable")
+ var c := 1
+
+ if true:
+ var a
+ @warning_ignore("unassigned_variable")
+ print(a)
+ var b
+ @warning_ignore("unassigned_variable")
+ print(b)
+ var c: Object
+ @warning_ignore("unassigned_variable")
+ print(c)
diff --git a/modules/gdscript/tests/scripts/runtime/features/reset_uninit_local_vars.out b/modules/gdscript/tests/scripts/runtime/features/reset_uninit_local_vars.out
new file mode 100644
index 0000000000..279ea2d3b1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/reset_uninit_local_vars.out
@@ -0,0 +1,4 @@
+GDTEST_OK
+<null>
+<null>
+<null>
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index abc89e4e97..42c3ecc7cb 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -298,6 +298,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
}
return nullptr;
}
+ ERR_FAIL_COND_V(!p_options.has("animation/fps"), nullptr);
#ifndef DISABLE_DEPRECATED
bool trimming = p_options.has("animation/trimming") ? (bool)p_options["animation/trimming"] : false;
diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
index 352b439332..7e52cde059 100644
--- a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
+++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp
@@ -127,6 +127,11 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state
trigger_body->set_body_type("trigger");
p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), trigger_body);
}
+ // If this node defines explicit member shape nodes, save this information.
+ if (node_trigger.has("nodes")) {
+ Array node_trigger_nodes = node_trigger["nodes"];
+ p_gltf_node->set_additional_data(StringName("GLTFPhysicsCompoundTriggerNodes"), node_trigger_nodes);
+ }
}
if (physics_body_ext.has("motion") || physics_body_ext.has("type")) {
p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_dictionary(physics_body_ext));
@@ -241,6 +246,19 @@ Node3D *_add_physics_node_to_given_node(Node3D *p_current_node, Node3D *p_child,
return p_current_node;
}
+Array _get_ancestor_compound_trigger_nodes(Ref<GLTFState> p_state, TypedArray<GLTFNode> p_state_nodes, CollisionObject3D *p_ancestor_col_obj) {
+ GLTFNodeIndex ancestor_index = p_state->get_node_index(p_ancestor_col_obj);
+ ERR_FAIL_INDEX_V(ancestor_index, p_state_nodes.size(), Array());
+ Ref<GLTFNode> ancestor_gltf_node = p_state_nodes[ancestor_index];
+ Variant compound_trigger_nodes = ancestor_gltf_node->get_additional_data(StringName("GLTFPhysicsCompoundTriggerNodes"));
+ if (compound_trigger_nodes.is_array()) {
+ return compound_trigger_nodes;
+ }
+ Array ret;
+ ancestor_gltf_node->set_additional_data(StringName("GLTFPhysicsCompoundTriggerNodes"), ret);
+ return ret;
+}
+
Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
Ref<GLTFPhysicsBody> gltf_physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody"));
#ifndef DISABLE_DEPRECATED
@@ -269,12 +287,27 @@ Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state
#endif // DISABLE_DEPRECATED
Node3D *ret = nullptr;
CollisionObject3D *ancestor_col_obj = nullptr;
+ Ref<GLTFPhysicsShape> gltf_physics_collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
+ Ref<GLTFPhysicsShape> gltf_physics_trigger_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape"));
if (gltf_physics_body.is_valid()) {
ancestor_col_obj = gltf_physics_body->to_node();
ret = ancestor_col_obj;
} else {
ancestor_col_obj = _get_ancestor_collision_object(p_scene_parent);
- if (!Object::cast_to<PhysicsBody3D>(ancestor_col_obj)) {
+ if (Object::cast_to<Area3D>(ancestor_col_obj) && gltf_physics_trigger_shape.is_valid()) {
+ // At this point, we found an ancestor Area3D node. But do we want to use it for this trigger shape?
+ TypedArray<GLTFNode> state_nodes = p_state->get_nodes();
+ GLTFNodeIndex self_index = state_nodes.find(p_gltf_node);
+ Array compound_trigger_nodes = _get_ancestor_compound_trigger_nodes(p_state, state_nodes, ancestor_col_obj);
+ // Check if the ancestor specifies compound trigger nodes, and if this node is in there.
+ // Remember that JSON does not have integers, only "number", aka double-precision floats.
+ if (compound_trigger_nodes.size() > 0 && !compound_trigger_nodes.has(double(self_index))) {
+ // If the compound trigger we found is not the intended user of
+ // this shape node, then we need to create a new Area3D node.
+ ancestor_col_obj = memnew(Area3D);
+ ret = ancestor_col_obj;
+ }
+ } else if (!Object::cast_to<PhysicsBody3D>(ancestor_col_obj)) {
if (p_gltf_node->get_additional_data(StringName("GLTFPhysicsCompoundCollider"))) {
// If the GLTF file wants this node to group solid shapes together,
// and there is no parent body, we need to create a static body.
@@ -288,8 +321,6 @@ Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state
// set above. If there is no ancestor body, we will either generate an
// Area3D or StaticBody3D implicitly, so prefer an Area3D as the base
// node for best compatibility with signal connections to this node.
- Ref<GLTFPhysicsShape> gltf_physics_collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
- Ref<GLTFPhysicsShape> gltf_physics_trigger_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape"));
bool is_ancestor_col_obj_solid = Object::cast_to<PhysicsBody3D>(ancestor_col_obj);
if (is_ancestor_col_obj_solid && gltf_physics_collider_shape.is_valid()) {
Node3D *child = _generate_shape_node_and_body_if_needed(p_state, p_gltf_node, gltf_physics_collider_shape, ancestor_col_obj, false);
@@ -362,8 +393,14 @@ void GLTFDocumentExtensionPhysics::convert_scene_node(Ref<GLTFState> p_state, Re
gltf_shape->set_mesh_index(_get_or_insert_mesh_in_state(p_state, importer_mesh));
}
}
- if (cast_to<Area3D>(_get_ancestor_collision_object(p_scene_node->get_parent()))) {
+ CollisionObject3D *ancestor_col_obj = _get_ancestor_collision_object(p_scene_node->get_parent());
+ if (cast_to<Area3D>(ancestor_col_obj)) {
p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShape"), gltf_shape);
+ // Write explicit member shape nodes to the ancestor compound trigger node.
+ TypedArray<GLTFNode> state_nodes = p_state->get_nodes();
+ GLTFNodeIndex self_index = state_nodes.size(); // The current p_gltf_node will be inserted next.
+ Array compound_trigger_nodes = _get_ancestor_compound_trigger_nodes(p_state, p_state->get_nodes(), ancestor_col_obj);
+ compound_trigger_nodes.push_back(double(self_index));
} else {
p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShape"), gltf_shape);
}
@@ -422,6 +459,11 @@ Error GLTFDocumentExtensionPhysics::export_node(Ref<GLTFState> p_state, Ref<GLTF
Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody"));
if (physics_body.is_valid()) {
physics_body_ext = physics_body->to_dictionary();
+ Variant compound_trigger_nodes = p_gltf_node->get_additional_data(StringName("GLTFPhysicsCompoundTriggerNodes"));
+ if (compound_trigger_nodes.is_array()) {
+ Dictionary trigger_property = physics_body_ext.get_or_add("trigger", {});
+ trigger_property["nodes"] = compound_trigger_nodes;
+ }
}
Ref<GLTFPhysicsShape> collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
if (collider_shape.is_valid()) {
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
index f8cc3d1d8c..12fa3bed7e 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
@@ -195,7 +195,7 @@ void OpenXRHandTrackingExtension::on_process() {
Ref<XRHandTracker> godot_tracker;
godot_tracker.instantiate();
- godot_tracker->set_hand(i == 0 ? XRHandTracker::HAND_LEFT : XRHandTracker::HAND_RIGHT);
+ godot_tracker->set_tracker_hand(i == 0 ? XRPositionalTracker::TRACKER_HAND_LEFT : XRPositionalTracker::TRACKER_HAND_RIGHT);
godot_tracker->set_tracker_name(i == 0 ? "/user/hand_tracker/left" : "/user/hand_tracker/right");
XRServer::get_singleton()->add_tracker(godot_tracker);
hand_trackers[i].godot_tracker = godot_tracker;
diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp
index 535d464d6f..45c1d8ec06 100644
--- a/modules/webxr/webxr_interface_js.cpp
+++ b/modules/webxr/webxr_interface_js.cpp
@@ -713,7 +713,7 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) {
if (unlikely(hand_tracker.is_null())) {
hand_tracker.instantiate();
- hand_tracker->set_hand(p_input_source_id == 0 ? XRHandTracker::HAND_LEFT : XRHandTracker::HAND_RIGHT);
+ hand_tracker->set_tracker_hand(p_input_source_id == 0 ? XRPositionalTracker::TRACKER_HAND_LEFT : XRPositionalTracker::TRACKER_HAND_RIGHT);
hand_tracker->set_tracker_name(p_input_source_id == 0 ? "/user/hand_tracker/left" : "/user/hand_tracker/right");
// These flags always apply, since WebXR doesn't give us enough insight to be more fine grained.
diff --git a/scene/2d/audio_listener_2d.cpp b/scene/2d/audio_listener_2d.cpp
index b4484694a5..cff0654ecc 100644
--- a/scene/2d/audio_listener_2d.cpp
+++ b/scene/2d/audio_listener_2d.cpp
@@ -45,7 +45,7 @@ bool AudioListener2D::_set(const StringName &p_name, const Variant &p_value) {
bool AudioListener2D::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "current") {
- if (is_inside_tree() && get_tree()->is_node_being_edited(this)) {
+ if (is_part_of_edited_scene()) {
r_ret = current;
} else {
r_ret = is_current();
@@ -63,13 +63,13 @@ void AudioListener2D::_get_property_list(List<PropertyInfo> *p_list) const {
void AudioListener2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
- if (!get_tree()->is_node_being_edited(this) && current) {
+ if (!is_part_of_edited_scene() && current) {
make_current();
}
} break;
case NOTIFICATION_EXIT_TREE: {
- if (!get_tree()->is_node_being_edited(this)) {
+ if (!is_part_of_edited_scene()) {
if (is_current()) {
clear_current();
current = true; // Keep it true.
@@ -98,7 +98,7 @@ void AudioListener2D::clear_current() {
}
bool AudioListener2D::is_current() const {
- if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) {
+ if (is_inside_tree() && !is_part_of_edited_scene()) {
return get_viewport()->get_audio_listener_2d() == this;
} else {
return current;
diff --git a/scene/3d/audio_listener_3d.cpp b/scene/3d/audio_listener_3d.cpp
index 437d840d5f..4a18447b3b 100644
--- a/scene/3d/audio_listener_3d.cpp
+++ b/scene/3d/audio_listener_3d.cpp
@@ -55,7 +55,7 @@ bool AudioListener3D::_set(const StringName &p_name, const Variant &p_value) {
bool AudioListener3D::_get(const StringName &p_name, Variant &r_ret) const {
if (p_name == "current") {
- if (is_inside_tree() && get_tree()->is_node_being_edited(this)) {
+ if (is_part_of_edited_scene()) {
r_ret = current;
} else {
r_ret = is_current();
@@ -81,7 +81,7 @@ void AudioListener3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_WORLD: {
bool first_listener = get_viewport()->_audio_listener_3d_add(this);
- if (!get_tree()->is_node_being_edited(this) && (current || first_listener)) {
+ if (!is_part_of_edited_scene() && (current || first_listener)) {
make_current();
}
} break;
@@ -91,7 +91,7 @@ void AudioListener3D::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_WORLD: {
- if (!get_tree()->is_node_being_edited(this)) {
+ if (!is_part_of_edited_scene()) {
if (is_current()) {
clear_current();
current = true; //keep it true
@@ -133,7 +133,7 @@ void AudioListener3D::clear_current() {
}
bool AudioListener3D::is_current() const {
- if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) {
+ if (is_inside_tree() && !is_part_of_edited_scene()) {
return get_viewport()->get_audio_listener_3d() == this;
} else {
return current;
diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp
index e8bd498e1f..8515aacba7 100644
--- a/scene/3d/camera_3d.cpp
+++ b/scene/3d/camera_3d.cpp
@@ -90,7 +90,7 @@ void Camera3D::_update_camera() {
RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform());
- if (get_tree()->is_node_being_edited(this) || !is_current()) {
+ if (is_part_of_edited_scene() || !is_current()) {
return;
}
@@ -126,7 +126,7 @@ void Camera3D::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_WORLD: {
- if (!get_tree()->is_node_being_edited(this)) {
+ if (!is_part_of_edited_scene()) {
if (is_current()) {
clear_current();
current = true; //keep it true
@@ -286,7 +286,7 @@ void Camera3D::set_current(bool p_enabled) {
}
bool Camera3D::is_current() const {
- if (is_inside_tree() && !get_tree()->is_node_being_edited(this)) {
+ if (is_inside_tree() && !is_part_of_edited_scene()) {
return get_viewport()->get_camera_3d() == this;
} else {
return current;
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 3b788b2fd0..98a5134283 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -197,7 +197,7 @@ void Node3D::_notification(int p_what) {
}
#ifdef TOOLS_ENABLED
- if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) {
+ if (is_part_of_edited_scene()) {
get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SNAME("_request_gizmo_for_id"), get_instance_id());
}
#endif
@@ -582,7 +582,7 @@ void Node3D::set_subgizmo_selection(Ref<Node3DGizmo> p_gizmo, int p_id, Transfor
return;
}
- if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) {
+ if (is_part_of_edited_scene()) {
get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_set_subgizmo_selection, this, p_gizmo, p_id, p_transform);
}
#endif
@@ -599,7 +599,7 @@ void Node3D::clear_subgizmo_selection() {
return;
}
- if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) {
+ if (is_part_of_edited_scene()) {
get_tree()->call_group_flags(SceneTree::GROUP_CALL_DEFERRED, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_clear_subgizmo_selection, this);
}
#endif
diff --git a/scene/3d/physics/character_body_3d.cpp b/scene/3d/physics/character_body_3d.cpp
index b13c279234..dda3ea9cca 100644
--- a/scene/3d/physics/character_body_3d.cpp
+++ b/scene/3d/physics/character_body_3d.cpp
@@ -232,7 +232,7 @@ void CharacterBody3D::_move_and_slide_grounded(double p_delta, bool p_was_on_flo
} else {
// Travel is too high to be safely canceled, we take it into account.
result.travel = result.travel.slide(up_direction);
- motion = motion.normalized() * result.travel.length();
+ motion = result.remainder;
}
set_global_transform(gt);
// Determines if you are on the ground, and limits the possibility of climbing on the walls because of the approximations.
diff --git a/scene/3d/xr_body_modifier_3d.cpp b/scene/3d/xr_body_modifier_3d.cpp
index 8aec3e856e..cf73882a7b 100644
--- a/scene/3d/xr_body_modifier_3d.cpp
+++ b/scene/3d/xr_body_modifier_3d.cpp
@@ -312,7 +312,7 @@ void XRBodyModifier3D::_process_modification() {
}
}
-void XRBodyModifier3D::_tracker_changed(const StringName &p_tracker_name, const Ref<XRBodyTracker> &p_tracker) {
+void XRBodyModifier3D::_tracker_changed(const StringName &p_tracker_name, XRServer::TrackerType p_tracker_type) {
if (tracker_name == p_tracker_name) {
_get_joint_data();
}
@@ -327,18 +327,18 @@ void XRBodyModifier3D::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE: {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server) {
- xr_server->connect("body_tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
- xr_server->connect("body_tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
- xr_server->connect("body_tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed).bind(Ref<XRBodyTracker>()));
+ xr_server->connect("tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
+ xr_server->connect("tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
+ xr_server->connect("tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
}
_get_joint_data();
} break;
case NOTIFICATION_EXIT_TREE: {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server) {
- xr_server->disconnect("body_tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
- xr_server->disconnect("body_tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
- xr_server->disconnect("body_tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed).bind(Ref<XRBodyTracker>()));
+ xr_server->disconnect("tracker_added", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
+ xr_server->disconnect("tracker_updated", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
+ xr_server->disconnect("tracker_removed", callable_mp(this, &XRBodyModifier3D::_tracker_changed));
}
for (int i = 0; i < XRBodyTracker::JOINT_MAX; i++) {
joints[i].bone = -1;
diff --git a/scene/3d/xr_body_modifier_3d.h b/scene/3d/xr_body_modifier_3d.h
index 9ff0cd7207..78d70146ee 100644
--- a/scene/3d/xr_body_modifier_3d.h
+++ b/scene/3d/xr_body_modifier_3d.h
@@ -86,7 +86,7 @@ private:
JointData joints[XRBodyTracker::JOINT_MAX];
void _get_joint_data();
- void _tracker_changed(const StringName &p_tracker_name, const Ref<XRBodyTracker> &p_tracker);
+ void _tracker_changed(const StringName &p_tracker_name, XRServer::TrackerType p_tracker_type);
};
VARIANT_BITFIELD_CAST(XRBodyModifier3D::BodyUpdate)
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index c3a586a1ee..97a2917dc1 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -53,7 +53,7 @@ void FileDialog::_focus_file_text() {
int lp = file->get_text().rfind(".");
if (lp != -1) {
file->select(0, lp);
- if (file->is_inside_tree() && !get_tree()->is_node_being_edited(file)) {
+ if (file->is_inside_tree() && !is_part_of_edited_scene()) {
file->grab_focus();
}
}
diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp
index 1b960a9b62..d035515b51 100644
--- a/scene/gui/graph_node.cpp
+++ b/scene/gui/graph_node.cpp
@@ -336,7 +336,8 @@ void GraphNode::_notification(int p_what) {
int width = get_size().width - sb_panel->get_minimum_size().x;
- if (get_child_count() > 0) {
+ // Take the HboxContainer child into account.
+ if (get_child_count(false) > 0) {
int slot_index = 0;
for (const KeyValue<int, Slot> &E : slot_table) {
if (E.key < 0 || E.key >= slot_y_cache.size()) {
@@ -647,6 +648,7 @@ void GraphNode::_port_pos_update() {
left_port_cache.clear();
right_port_cache.clear();
int vertical_ofs = titlebar_hbox->get_size().height + sb_titlebar->get_minimum_size().height + sb_panel->get_margin(SIDE_TOP);
+ int slot_index = 0;
for (int i = 0; i < get_child_count(false); i++) {
Control *child = Object::cast_to<Control>(get_child(i, false));
@@ -656,27 +658,28 @@ void GraphNode::_port_pos_update() {
Size2i size = child->get_rect().size;
- if (slot_table.has(i)) {
- if (slot_table[i].enable_left) {
+ if (slot_table.has(slot_index)) {
+ if (slot_table[slot_index].enable_left) {
PortCache port_cache;
port_cache.pos = Point2i(edgeofs, vertical_ofs + size.height / 2);
- port_cache.type = slot_table[i].type_left;
- port_cache.color = slot_table[i].color_left;
- port_cache.slot_index = child->get_index(false);
+ port_cache.type = slot_table[slot_index].type_left;
+ port_cache.color = slot_table[slot_index].color_left;
+ port_cache.slot_index = slot_index;
left_port_cache.push_back(port_cache);
}
- if (slot_table[i].enable_right) {
+ if (slot_table[slot_index].enable_right) {
PortCache port_cache;
port_cache.pos = Point2i(get_size().width - edgeofs, vertical_ofs + size.height / 2);
- port_cache.type = slot_table[i].type_right;
- port_cache.color = slot_table[i].color_right;
- port_cache.slot_index = child->get_index(false);
+ port_cache.type = slot_table[slot_index].type_right;
+ port_cache.color = slot_table[slot_index].color_right;
+ port_cache.slot_index = slot_index;
right_port_cache.push_back(port_cache);
}
}
vertical_ofs += separation;
vertical_ofs += size.height;
+ slot_index++;
}
port_pos_dirty = false;
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 1a94d92855..ddfe202c13 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -771,7 +771,7 @@ void LineEdit::_notification(int p_what) {
switch (p_what) {
#ifdef TOOLS_ENABLED
case NOTIFICATION_ENTER_TREE: {
- if (Engine::get_singleton()->is_editor_hint() && !get_tree()->is_node_being_edited(this)) {
+ if (Engine::get_singleton()->is_editor_hint() && !is_part_of_edited_scene()) {
set_caret_blink_enabled(EDITOR_GET("text_editor/appearance/caret/caret_blink"));
set_caret_blink_interval(EDITOR_GET("text_editor/appearance/caret/caret_blink_interval"));
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 260956a775..9b991972be 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -276,6 +276,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const {
}
}
+ minsize.height = Math::ceil(minsize.height); // Ensures enough height at fractional content scales to prevent the v_scroll_bar from showing.
return minsize;
}
@@ -2832,9 +2833,9 @@ void PopupMenu::popup(const Rect2i &p_bounds) {
if (!is_embedded()) {
float win_scale = get_parent_visible_window()->get_content_scale_factor();
set_content_scale_factor(win_scale);
- Size2 minsize = get_contents_minimum_size();
- minsize.height += 0.5 * win_scale; // Ensures enough height at fractional content scales to prevent the v_scroll_bar from showing.
- set_min_size(minsize * win_scale);
+ Size2 minsize = get_contents_minimum_size() * win_scale;
+ minsize.height = Math::ceil(minsize.height); // Ensures enough height at fractional content scales to prevent the v_scroll_bar from showing.
+ set_min_size(minsize); // `height` is truncated here by the cast to Size2i for Window.min_size.
set_size(Vector2(0, 0)); // Shrinkwraps to min size.
}
Popup::popup(p_bounds);
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index 05f44891f6..e2feb59a8c 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -794,6 +794,22 @@ Ref<Texture2D> TabContainer::get_tab_icon(int p_tab) const {
return tab_bar->get_tab_icon(p_tab);
}
+void TabContainer::set_tab_icon_max_width(int p_tab, int p_width) {
+ if (tab_bar->get_tab_icon_max_width(p_tab) == p_width) {
+ return;
+ }
+
+ tab_bar->set_tab_icon_max_width(p_tab, p_width);
+
+ _update_margins();
+ _repaint();
+ queue_redraw();
+}
+
+int TabContainer::get_tab_icon_max_width(int p_tab) const {
+ return tab_bar->get_tab_icon_max_width(p_tab);
+}
+
void TabContainer::set_tab_disabled(int p_tab, bool p_disabled) {
if (tab_bar->is_tab_disabled(p_tab) == p_disabled) {
return;
@@ -990,6 +1006,8 @@ void TabContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_tab_tooltip", "tab_idx"), &TabContainer::get_tab_tooltip);
ClassDB::bind_method(D_METHOD("set_tab_icon", "tab_idx", "icon"), &TabContainer::set_tab_icon);
ClassDB::bind_method(D_METHOD("get_tab_icon", "tab_idx"), &TabContainer::get_tab_icon);
+ ClassDB::bind_method(D_METHOD("set_tab_icon_max_width", "tab_idx", "width"), &TabContainer::set_tab_icon_max_width);
+ ClassDB::bind_method(D_METHOD("get_tab_icon_max_width", "tab_idx"), &TabContainer::get_tab_icon_max_width);
ClassDB::bind_method(D_METHOD("set_tab_disabled", "tab_idx", "disabled"), &TabContainer::set_tab_disabled);
ClassDB::bind_method(D_METHOD("is_tab_disabled", "tab_idx"), &TabContainer::is_tab_disabled);
ClassDB::bind_method(D_METHOD("set_tab_hidden", "tab_idx", "hidden"), &TabContainer::set_tab_hidden);
diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h
index c11d9824e7..e00bc780d4 100644
--- a/scene/gui/tab_container.h
+++ b/scene/gui/tab_container.h
@@ -161,6 +161,9 @@ public:
void set_tab_icon(int p_tab, const Ref<Texture2D> &p_icon);
Ref<Texture2D> get_tab_icon(int p_tab) const;
+ void set_tab_icon_max_width(int p_tab, int p_width);
+ int get_tab_icon_max_width(int p_tab) const;
+
void set_tab_disabled(int p_tab, bool p_disabled);
bool is_tab_disabled(int p_tab) const;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 0bb77a92f2..39fba72e09 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -752,16 +752,15 @@ void TextEdit::_notification(int p_what) {
// Draw the minimap.
// Add visual feedback when dragging or hovering the visible area rectangle.
- float viewport_alpha;
+ Color viewport_color = theme_cache.caret_color;
if (dragging_minimap) {
- viewport_alpha = 0.25;
+ viewport_color.a = 0.25;
} else if (hovering_minimap) {
- viewport_alpha = 0.175;
+ viewport_color.a = 0.175;
} else {
- viewport_alpha = 0.1;
+ viewport_color.a = 0.1;
}
- const Color viewport_color = (theme_cache.background_color.get_v() < 0.5) ? Color(1, 1, 1, viewport_alpha) : Color(0, 0, 0, viewport_alpha);
if (rtl) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, viewport_offset_y, minimap_width, viewport_height), viewport_color);
} else {
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 982473deee..b17d345f1f 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -379,8 +379,8 @@ void TreeItem::set_text_overrun_behavior(int p_column, TextServer::OverrunBehavi
cells.write[p_column].text_buf->set_text_overrun_behavior(p_behavior);
cells.write[p_column].dirty = true;
- _changed_notify(p_column);
cells.write[p_column].cached_minimum_size_dirty = true;
+ _changed_notify(p_column);
}
TextServer::OverrunBehavior TreeItem::get_text_overrun_behavior(int p_column) const {
@@ -1512,7 +1512,9 @@ Size2 TreeItem::get_minimum_size(int p_column) {
const TreeItem::Cell &cell = cells[p_column];
if (cell.cached_minimum_size_dirty) {
- Size2 size;
+ Size2 size = Size2(
+ parent_tree->theme_cache.inner_item_margin_left + parent_tree->theme_cache.inner_item_margin_right,
+ parent_tree->theme_cache.inner_item_margin_top + parent_tree->theme_cache.inner_item_margin_bottom);
// Text.
if (!cell.text.is_empty()) {
@@ -1520,7 +1522,9 @@ Size2 TreeItem::get_minimum_size(int p_column) {
parent_tree->update_item_cell(this, p_column);
}
Size2 text_size = cell.text_buf->get_size();
- size.width += text_size.width;
+ if (get_text_overrun_behavior(p_column) == TextServer::OVERRUN_NO_TRIMMING) {
+ size.width += text_size.width;
+ }
size.height = MAX(size.height, text_size.height);
}
@@ -1539,13 +1543,10 @@ Size2 TreeItem::get_minimum_size(int p_column) {
Ref<Texture2D> texture = cell.buttons[i].texture;
if (texture.is_valid()) {
Size2 button_size = texture->get_size() + parent_tree->theme_cache.button_pressed->get_minimum_size();
- size.width += button_size.width;
+ size.width += button_size.width + parent_tree->theme_cache.button_margin;
size.height = MAX(size.height, button_size.height);
}
}
- if (cell.buttons.size() >= 2) {
- size.width += (cell.buttons.size() - 1) * parent_tree->theme_cache.button_margin;
- }
cells.write[p_column].cached_minimum_size = size;
cells.write[p_column].cached_minimum_size_dirty = false;
@@ -4394,17 +4395,23 @@ void Tree::_update_all() {
}
Size2 Tree::get_minimum_size() const {
- if (h_scroll_enabled && v_scroll_enabled) {
- return Size2();
- } else {
- Vector2 min_size = get_internal_min_size();
- Ref<StyleBox> bg = theme_cache.panel_style;
- if (bg.is_valid()) {
- min_size.x += bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT);
- min_size.y += bg->get_margin(SIDE_TOP) + bg->get_margin(SIDE_BOTTOM);
- }
- return Vector2(h_scroll_enabled ? 0 : min_size.x, v_scroll_enabled ? 0 : min_size.y);
+ Vector2 min_size = Vector2(0, _get_title_button_height());
+
+ if (theme_cache.panel_style.is_valid()) {
+ min_size += theme_cache.panel_style->get_minimum_size();
+ }
+
+ Vector2 content_min_size = get_internal_min_size();
+ if (h_scroll_enabled) {
+ content_min_size.x = 0;
+ min_size.y += h_scroll->get_combined_minimum_size().height;
+ }
+ if (v_scroll_enabled) {
+ min_size.x += v_scroll->get_combined_minimum_size().width;
+ content_min_size.y = 0;
}
+
+ return min_size + content_min_size;
}
TreeItem *Tree::create_item(TreeItem *p_parent, int p_index) {
@@ -4582,6 +4589,7 @@ void Tree::set_hide_root(bool p_enabled) {
hide_root = p_enabled;
queue_redraw();
+ update_minimum_size();
}
bool Tree::is_root_hidden() const {
@@ -4730,31 +4738,33 @@ int Tree::get_column_minimum_width(int p_column) const {
min_width = MAX(theme_cache.font->get_string_size(columns[p_column].xl_title, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + padding, min_width);
}
- if (!columns[p_column].clip_content) {
- int depth = 0;
- TreeItem *next;
- for (TreeItem *item = get_root(); item; item = next) {
- next = item->get_next_visible();
- // Compute the depth in tree.
- if (next && p_column == 0) {
- if (next->get_parent() == item) {
- depth += 1;
- } else {
- TreeItem *common_parent = item->get_parent();
- while (common_parent != next->get_parent() && common_parent) {
- common_parent = common_parent->get_parent();
- depth -= 1;
+ if (root && !columns[p_column].clip_content) {
+ int depth = 1;
+
+ TreeItem *last = nullptr;
+ TreeItem *first = hide_root ? root->get_next_visible() : root;
+ for (TreeItem *item = first; item; last = item, item = item->get_next_visible()) {
+ // Get column indentation.
+ int indent;
+ if (p_column == 0) {
+ if (last) {
+ if (item->parent == last) {
+ depth += 1;
+ } else if (item->parent != last->parent) {
+ depth = hide_root ? 0 : 1;
+ for (TreeItem *iter = item->parent; iter; iter = iter->parent) {
+ depth += 1;
+ }
}
}
+ indent = theme_cache.item_margin * depth;
+ } else {
+ indent = theme_cache.h_separation;
}
// Get the item minimum size.
Size2 item_size = item->get_minimum_size(p_column);
- if (p_column == 0) {
- item_size.width += theme_cache.item_margin * depth;
- } else {
- item_size.width += theme_cache.h_separation;
- }
+ item_size.width += indent;
// Check if the item is wider.
min_width = MAX(min_width, item_size.width);
@@ -4968,6 +4978,7 @@ void Tree::set_column_titles_visible(bool p_show) {
show_column_titles = p_show;
queue_redraw();
+ update_minimum_size();
}
bool Tree::are_column_titles_visible() const {
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 5c5049759f..305b80ffe5 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -3520,6 +3520,7 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_node_and_resource", "path"), &Node::_get_node_and_resource);
ClassDB::bind_method(D_METHOD("is_inside_tree"), &Node::is_inside_tree);
+ ClassDB::bind_method(D_METHOD("is_part_of_edited_scene"), &Node::is_part_of_edited_scene);
ClassDB::bind_method(D_METHOD("is_ancestor_of", "node"), &Node::is_ancestor_of);
ClassDB::bind_method(D_METHOD("is_greater_than", "node"), &Node::is_greater_than);
ClassDB::bind_method(D_METHOD("get_path"), &Node::get_path);
diff --git a/scene/main/node.h b/scene/main/node.h
index f49eeec9cd..fe212ae0f7 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -525,6 +525,8 @@ public:
bool is_property_pinned(const StringName &p_property) const;
virtual StringName get_property_store_alias(const StringName &p_property) const;
bool is_part_of_edited_scene() const;
+#else
+ bool is_part_of_edited_scene() const { return false; }
#endif
void get_storable_properties(HashSet<StringName> &r_storable_properties) const;
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index c465a3385f..870bed7409 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -729,13 +729,6 @@ void SceneTree::set_quit_on_go_back(bool p_enable) {
quit_on_go_back = p_enable;
}
-#ifdef TOOLS_ENABLED
-
-bool SceneTree::is_node_being_edited(const Node *p_node) const {
- return Engine::get_singleton()->is_editor_hint() && edited_scene_root && (edited_scene_root->is_ancestor_of(p_node) || edited_scene_root == p_node);
-}
-#endif
-
#ifdef DEBUG_ENABLED
void SceneTree::set_debug_collisions_hint(bool p_enabled) {
debug_collisions_hint = p_enabled;
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index c9f3a4de1f..6f0a61ec51 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -330,12 +330,6 @@ public:
_FORCE_INLINE_ double get_physics_process_time() const { return physics_process_time; }
_FORCE_INLINE_ double get_process_time() const { return process_time; }
-#ifdef TOOLS_ENABLED
- bool is_node_being_edited(const Node *p_node) const;
-#else
- bool is_node_being_edited(const Node *p_node) const { return false; }
-#endif
-
void set_pause(bool p_enabled);
bool is_paused() const;
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 2d30ea345d..73ce166123 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -4954,7 +4954,7 @@ Viewport::Viewport() {
unhandled_key_input_group = "_vp_unhandled_key_input" + id;
// Window tooltip.
- gui.tooltip_delay = GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "gui/timers/tooltip_delay_sec", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater"), 0.5);
+ gui.tooltip_delay = GLOBAL_GET("gui/timers/tooltip_delay_sec");
#ifndef _3D_DISABLED
set_scaling_3d_mode((Viewport::Scaling3DMode)(int)GLOBAL_GET("rendering/scaling_3d/mode"));
diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp
index 9f2fad410c..b83be8b6ef 100644
--- a/scene/resources/surface_tool.cpp
+++ b/scene/resources/surface_tool.cpp
@@ -803,6 +803,8 @@ const uint32_t SurfaceTool::custom_mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_
const uint32_t SurfaceTool::custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT };
void SurfaceTool::create_vertex_array_from_arrays(const Array &p_arrays, LocalVector<SurfaceTool::Vertex> &ret, uint64_t *r_format) {
+ ERR_FAIL_INDEX(RS::ARRAY_WEIGHTS, p_arrays.size());
+
ret.clear();
Vector<Vector3> varr = p_arrays[RS::ARRAY_VERTEX];
diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
index 209fabeddf..8d865ba440 100644
--- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
@@ -624,6 +624,7 @@ void SceneShaderForwardClustered::init(const String p_defines) {
actions.renames["CUSTOM2"] = "custom2_attrib";
actions.renames["CUSTOM3"] = "custom3_attrib";
actions.renames["OUTPUT_IS_SRGB"] = "SHADER_IS_SRGB";
+ actions.renames["LIGHT_VERTEX"] = "light_vertex";
actions.renames["NODE_POSITION_WORLD"] = "read_model_matrix[3].xyz";
actions.renames["CAMERA_POSITION_WORLD"] = "scene_data.inv_view_matrix[3].xyz";
@@ -670,6 +671,7 @@ void SceneShaderForwardClustered::init(const String p_defines) {
actions.usage_defines["COLOR"] = "#define COLOR_USED\n";
actions.usage_defines["INSTANCE_CUSTOM"] = "#define ENABLE_INSTANCE_CUSTOM\n";
actions.usage_defines["POSITION"] = "#define OVERRIDE_POSITION\n";
+ actions.usage_defines["LIGHT_VERTEX"] = "#define LIGHT_VERTEX_USED\n";
actions.usage_defines["ALPHA_SCISSOR_THRESHOLD"] = "#define ALPHA_SCISSOR_USED\n";
actions.usage_defines["ALPHA_HASH_SCALE"] = "#define ALPHA_HASH_USED\n";
diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
index a2f112669c..0810f567cb 100644
--- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
@@ -528,6 +528,7 @@ void SceneShaderForwardMobile::init(const String p_defines) {
actions.renames["CUSTOM2"] = "custom2_attrib";
actions.renames["CUSTOM3"] = "custom3_attrib";
actions.renames["OUTPUT_IS_SRGB"] = "SHADER_IS_SRGB";
+ actions.renames["LIGHT_VERTEX"] = "light_vertex";
actions.renames["NODE_POSITION_WORLD"] = "read_model_matrix[3].xyz";
actions.renames["CAMERA_POSITION_WORLD"] = "scene_data.inv_view_matrix[3].xyz";
@@ -574,6 +575,7 @@ void SceneShaderForwardMobile::init(const String p_defines) {
actions.usage_defines["COLOR"] = "#define COLOR_USED\n";
actions.usage_defines["INSTANCE_CUSTOM"] = "#define ENABLE_INSTANCE_CUSTOM\n";
actions.usage_defines["POSITION"] = "#define OVERRIDE_POSITION\n";
+ actions.usage_defines["LIGHT_VERTEX"] = "#define LIGHT_VERTEX\n";
actions.usage_defines["ALPHA_SCISSOR_THRESHOLD"] = "#define ALPHA_SCISSOR_USED\n";
actions.usage_defines["ALPHA_HASH_SCALE"] = "#define ALPHA_HASH_USED\n";
diff --git a/servers/rendering/renderer_rd/shaders/canvas.glsl b/servers/rendering/renderer_rd/shaders/canvas.glsl
index 235c772e2d..dbff09c301 100644
--- a/servers/rendering/renderer_rd/shaders/canvas.glsl
+++ b/servers/rendering/renderer_rd/shaders/canvas.glsl
@@ -665,6 +665,12 @@ void main() {
vec2 tex_uv = (vec4(vertex, 0.0, 1.0) * mat4(light_array.data[light_base].texture_matrix[0], light_array.data[light_base].texture_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
vec2 tex_uv_atlas = tex_uv * light_array.data[light_base].atlas_rect.zw + light_array.data[light_base].atlas_rect.xy;
+
+ if (any(lessThan(tex_uv, vec2(0.0, 0.0))) || any(greaterThanEqual(tex_uv, vec2(1.0, 1.0)))) {
+ //if outside the light texture, light color is zero
+ continue;
+ }
+
vec4 light_color = textureLod(sampler2D(atlas_texture, texture_sampler), tex_uv_atlas, 0.0);
vec4 light_base_color = light_array.data[light_base].color;
@@ -689,10 +695,6 @@ void main() {
light_color.rgb *= base_color.rgb;
}
#endif
- if (any(lessThan(tex_uv, vec2(0.0, 0.0))) || any(greaterThanEqual(tex_uv, vec2(1.0, 1.0)))) {
- //if outside the light texture, light color is zero
- light_color.a = 0.0;
- }
if (bool(light_array.data[light_base].flags & LIGHT_FLAGS_HAS_SHADOW)) {
vec2 shadow_pos = (vec4(shadow_vertex, 0.0, 1.0) * mat4(light_array.data[light_base].shadow_matrix[0], light_array.data[light_base].shadow_matrix[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0))).xy; //multiply inverse given its transposed. Optimizer removes useless operations.
diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
index 359d7799e5..cb07579c4b 100644
--- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
@@ -1022,6 +1022,11 @@ void fragment_shader(in SceneData scene_data) {
inv_view_matrix[1][3] = 0.0;
inv_view_matrix[2][3] = 0.0;
#endif
+
+#ifdef LIGHT_VERTEX_USED
+ vec3 light_vertex = vertex;
+#endif //LIGHT_VERTEX_USED
+
mat4 read_view_matrix = scene_data.view_matrix;
vec2 read_viewport_size = scene_data.viewport_size;
{
@@ -1032,6 +1037,15 @@ void fragment_shader(in SceneData scene_data) {
transmittance_color.a *= sss_strength;
#endif
+#ifdef LIGHT_VERTEX_USED
+ vertex = light_vertex;
+#ifdef USE_MULTIVIEW
+ view = -normalize(vertex - eye_offset);
+#else
+ view = -normalize(vertex);
+#endif //USE_MULTIVIEW
+#endif //LIGHT_VERTEX_USED
+
#ifndef USE_SHADOW_TO_OPACITY
#ifdef ALPHA_SCISSOR_USED
@@ -2043,7 +2057,7 @@ void fragment_shader(in SceneData scene_data) {
shadow = 1.0;
#endif
- float size_A = sc_use_light_soft_shadows ? directional_lights.data[i].size : 0.0;
+ float size_A = sc_use_directional_soft_shadows ? directional_lights.data[i].size : 0.0;
light_compute(normal, directional_lights.data[i].direction, normalize(view), size_A,
#ifndef DEBUG_DRAW_PSSM_SPLITS
@@ -2221,24 +2235,16 @@ void fragment_shader(in SceneData scene_data) {
}
#ifdef USE_SHADOW_TO_OPACITY
+#ifndef MODE_RENDER_DEPTH
alpha = min(alpha, clamp(length(ambient_light), 0.0, 1.0));
#if defined(ALPHA_SCISSOR_USED)
if (alpha < alpha_scissor) {
discard;
}
-#else
-#ifdef MODE_RENDER_DEPTH
-#ifdef USE_OPAQUE_PREPASS
-
- if (alpha < scene_data.opaque_prepass_threshold) {
- discard;
- }
-
-#endif // USE_OPAQUE_PREPASS
-#endif // MODE_RENDER_DEPTH
#endif // ALPHA_SCISSOR_USED
+#endif // !MODE_RENDER_DEPTH
#endif // USE_SHADOW_TO_OPACITY
#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
index c26313092b..b98ea5a27f 100644
--- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
@@ -830,6 +830,10 @@ void main() {
inv_view_matrix[2][3] = 0.0;
#endif
+#ifdef LIGHT_VERTEX_USED
+ vec3 light_vertex = vertex;
+#endif //LIGHT_VERTEX_USED
+
mat4 read_view_matrix = scene_data.view_matrix;
vec2 read_viewport_size = scene_data.viewport_size;
@@ -837,6 +841,15 @@ void main() {
#CODE : FRAGMENT
}
+#ifdef LIGHT_VERTEX_USED
+ vertex = light_vertex;
+#ifdef USE_MULTIVIEW
+ view = -normalize(vertex - eye_offset);
+#else
+ view = -normalize(vertex);
+#endif //USE_MULTIVIEW
+#endif //LIGHT_VERTEX_USED
+
#ifdef LIGHT_TRANSMITTANCE_USED
#ifdef SSS_MODE_SKIN
transmittance_color.a = sss_strength;
@@ -1743,24 +1756,16 @@ void main() {
} //spot lights
#ifdef USE_SHADOW_TO_OPACITY
+#ifndef MODE_RENDER_DEPTH
alpha = min(alpha, clamp(length(ambient_light), 0.0, 1.0));
#if defined(ALPHA_SCISSOR_USED)
if (alpha < alpha_scissor) {
discard;
}
-#else
-#ifdef MODE_RENDER_DEPTH
-#ifdef USE_OPAQUE_PREPASS
-
- if (alpha < scene_data.opaque_prepass_threshold) {
- discard;
- }
-
-#endif // USE_OPAQUE_PREPASS
-#endif // MODE_RENDER_DEPTH
#endif // !ALPHA_SCISSOR_USED
+#endif // !MODE_RENDER_DEPTH
#endif // USE_SHADOW_TO_OPACITY
#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
index 47e6fe5873..40ca74ae07 100644
--- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl
@@ -375,7 +375,7 @@ float sample_directional_soft_shadow(texture2D shadow, vec3 pssm_coord, vec2 tex
for (uint i = 0; i < sc_directional_penumbra_shadow_samples; i++) {
vec2 suv = pssm_coord.xy + (disk_rotation * scene_data_block.data.directional_penumbra_shadow_kernel[i].xy) * tex_scale;
float d = textureLod(sampler2D(shadow, SAMPLER_LINEAR_CLAMP), suv, 0.0).r;
- if (d < pssm_coord.z) {
+ if (d > pssm_coord.z) {
blocker_average += d;
blocker_count += 1.0;
}
@@ -384,7 +384,7 @@ float sample_directional_soft_shadow(texture2D shadow, vec3 pssm_coord, vec2 tex
if (blocker_count > 0.0) {
//blockers found, do soft shadow
blocker_average /= blocker_count;
- float penumbra = (pssm_coord.z - blocker_average) / blocker_average;
+ float penumbra = (-pssm_coord.z + blocker_average) / (1.0 - blocker_average);
tex_scale *= penumbra;
float s = 0.0;
@@ -488,7 +488,7 @@ float light_process_omni_shadow(uint idx, vec3 vertex, vec3 normal) {
if (blocker_count > 0.0) {
//blockers found, do soft shadow
blocker_average /= blocker_count;
- float penumbra = (z_norm + blocker_average) / blocker_average;
+ float penumbra = (-z_norm + blocker_average) / (1.0 - blocker_average);
tangent *= penumbra;
bitangent *= penumbra;
@@ -736,7 +736,7 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal) {
vec2 suv = shadow_uv + (disk_rotation * scene_data_block.data.penumbra_shadow_kernel[i].xy) * uv_size;
suv = clamp(suv, spot_lights.data[idx].atlas_rect.xy, clamp_max);
float d = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), suv, 0.0).r;
- if (d < splane.z) {
+ if (d > splane.z) {
blocker_average += d;
blocker_count += 1.0;
}
@@ -745,7 +745,7 @@ float light_process_spot_shadow(uint idx, vec3 vertex, vec3 normal) {
if (blocker_count > 0.0) {
//blockers found, do soft shadow
blocker_average /= blocker_count;
- float penumbra = (z_norm - blocker_average) / blocker_average;
+ float penumbra = (-z_norm + blocker_average) / (1.0 - blocker_average);
uv_size *= penumbra;
shadow = 0.0;
diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp
index aff874f452..af51083dc3 100644
--- a/servers/rendering/shader_types.cpp
+++ b/servers/rendering/shader_types.cpp
@@ -111,6 +111,7 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_SPATIAL].functions["vertex"].built_ins["EYE_OFFSET"] = constt(ShaderLanguage::TYPE_VEC3);
shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["VERTEX"] = constt(ShaderLanguage::TYPE_VEC3);
+ shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["LIGHT_VERTEX"] = ShaderLanguage::TYPE_VEC3;
shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["FRAGCOORD"] = constt(ShaderLanguage::TYPE_VEC4);
shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["FRONT_FACING"] = constt(ShaderLanguage::TYPE_BOOL);
shader_modes[RS::SHADER_SPATIAL].functions["fragment"].built_ins["NORMAL"] = ShaderLanguage::TYPE_VEC3;
diff --git a/servers/rendering/storage/environment_storage.cpp b/servers/rendering/storage/environment_storage.cpp
index 7b75a12a19..1bbb5da6bb 100644
--- a/servers/rendering/storage/environment_storage.cpp
+++ b/servers/rendering/storage/environment_storage.cpp
@@ -773,11 +773,7 @@ RS::EnvironmentSDFGIYScale RendererEnvironmentStorage::environment_get_sdfgi_y_s
void RendererEnvironmentStorage::environment_set_adjustment(RID p_env, bool p_enable, float p_brightness, float p_contrast, float p_saturation, bool p_use_1d_color_correction, RID p_color_correction) {
Environment *env = environment_owner.get_or_null(p_env);
ERR_FAIL_NULL(env);
-#ifdef DEBUG_ENABLED
- if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" && p_enable) {
- WARN_PRINT_ONCE_ED("Adjustments are not supported when using the GL Compatibility backend yet. Support will be added in a future release.");
- }
-#endif
+
env->adjustments_enabled = p_enable;
env->adjustments_brightness = p_brightness;
env->adjustments_contrast = p_contrast;
diff --git a/servers/xr/xr_hand_tracker.cpp b/servers/xr/xr_hand_tracker.cpp
index cb0fbfb35f..abfe2e9867 100644
--- a/servers/xr/xr_hand_tracker.cpp
+++ b/servers/xr/xr_hand_tracker.cpp
@@ -33,9 +33,6 @@
#include "xr_body_tracker.h"
void XRHandTracker::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_hand", "hand"), &XRHandTracker::set_hand);
- ClassDB::bind_method(D_METHOD("get_hand"), &XRHandTracker::get_hand);
-
ClassDB::bind_method(D_METHOD("set_has_tracking_data", "has_data"), &XRHandTracker::set_has_tracking_data);
ClassDB::bind_method(D_METHOD("get_has_tracking_data"), &XRHandTracker::get_has_tracking_data);
@@ -60,10 +57,6 @@ void XRHandTracker::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "has_tracking_data", PROPERTY_HINT_NONE), "set_has_tracking_data", "get_has_tracking_data");
ADD_PROPERTY(PropertyInfo(Variant::INT, "hand_tracking_source", PROPERTY_HINT_ENUM, "Unknown,Unobstructed,Controller"), "set_hand_tracking_source", "get_hand_tracking_source");
- BIND_ENUM_CONSTANT(HAND_LEFT);
- BIND_ENUM_CONSTANT(HAND_RIGHT);
- BIND_ENUM_CONSTANT(HAND_MAX);
-
BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_UNKNOWN);
BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_UNOBSTRUCTED);
BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_CONTROLLER);
@@ -110,48 +103,8 @@ void XRHandTracker::set_tracker_type(XRServer::TrackerType p_type) {
}
void XRHandTracker::set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand) {
- ERR_FAIL_INDEX(p_hand, TRACKER_HAND_MAX);
-
- switch (p_hand) {
- case TRACKER_HAND_LEFT:
- tracker_hand = TRACKER_HAND_LEFT;
- hand = HAND_LEFT;
- break;
-
- case TRACKER_HAND_RIGHT:
- tracker_hand = TRACKER_HAND_RIGHT;
- hand = HAND_RIGHT;
- break;
-
- case TRACKER_HAND_UNKNOWN:
- default:
- ERR_FAIL_MSG("XRHandTracker must specify hand");
- break;
- }
-}
-
-void XRHandTracker::set_hand(XRHandTracker::Hand p_hand) {
- ERR_FAIL_INDEX(p_hand, HAND_MAX);
-
- switch (p_hand) {
- case HAND_LEFT:
- tracker_hand = TRACKER_HAND_LEFT;
- hand = HAND_LEFT;
- break;
-
- case HAND_RIGHT:
- tracker_hand = TRACKER_HAND_RIGHT;
- hand = HAND_RIGHT;
- break;
-
- default:
- ERR_FAIL_MSG("XRHandTracker must specify hand");
- break;
- }
-}
-
-XRHandTracker::Hand XRHandTracker::get_hand() const {
- return hand;
+ ERR_FAIL_COND_MSG(p_hand != TRACKER_HAND_LEFT && p_hand != TRACKER_HAND_RIGHT, "XRHandTracker must specify hand.");
+ tracker_hand = p_hand;
}
void XRHandTracker::set_has_tracking_data(bool p_has_tracking_data) {
@@ -222,4 +175,5 @@ Vector3 XRHandTracker::get_hand_joint_angular_velocity(XRHandTracker::HandJoint
XRHandTracker::XRHandTracker() {
type = XRServer::TRACKER_HAND;
+ tracker_hand = TRACKER_HAND_LEFT;
}
diff --git a/servers/xr/xr_hand_tracker.h b/servers/xr/xr_hand_tracker.h
index 8ef3c229c3..c7c18a31f8 100644
--- a/servers/xr/xr_hand_tracker.h
+++ b/servers/xr/xr_hand_tracker.h
@@ -38,12 +38,6 @@ class XRHandTracker : public XRPositionalTracker {
_THREAD_SAFE_CLASS_
public:
- enum Hand {
- HAND_LEFT,
- HAND_RIGHT,
- HAND_MAX,
- };
-
enum HandTrackingSource {
HAND_TRACKING_SOURCE_UNKNOWN,
HAND_TRACKING_SOURCE_UNOBSTRUCTED,
@@ -93,9 +87,6 @@ public:
void set_tracker_type(XRServer::TrackerType p_type) override;
void set_tracker_hand(const XRPositionalTracker::TrackerHand p_hand) override;
- void set_hand(Hand p_hand);
- Hand get_hand() const;
-
void set_has_tracking_data(bool p_has_tracking_data);
bool get_has_tracking_data() const;
@@ -123,7 +114,6 @@ protected:
static void _bind_methods();
private:
- Hand hand = HAND_LEFT;
bool has_tracking_data = false;
HandTrackingSource hand_tracking_source = HAND_TRACKING_SOURCE_UNKNOWN;
@@ -134,7 +124,6 @@ private:
Vector3 hand_joint_angular_velocities[HAND_JOINT_MAX];
};
-VARIANT_ENUM_CAST(XRHandTracker::Hand)
VARIANT_ENUM_CAST(XRHandTracker::HandTrackingSource)
VARIANT_ENUM_CAST(XRHandTracker::HandJoint)
VARIANT_BITFIELD_CAST(XRHandTracker::HandJointFlags)
diff --git a/tests/scene/test_graph_node.h b/tests/scene/test_graph_node.h
new file mode 100644
index 0000000000..72b8b682c9
--- /dev/null
+++ b/tests/scene/test_graph_node.h
@@ -0,0 +1,59 @@
+/**************************************************************************/
+/* test_graph_node.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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 TEST_GRAPH_NODE_H
+#define TEST_GRAPH_NODE_H
+
+#include "scene/gui/graph_node.h"
+#include "scene/main/window.h"
+
+#include "tests/test_macros.h"
+
+namespace TestGraphNode {
+
+TEST_CASE("[GraphNode][SceneTree]") {
+ SUBCASE("[GraphNode] Graph Node only child on delete should not cause error.") {
+ // Setup.
+ GraphNode *test_node = memnew(GraphNode);
+ test_child->set_name("Graph Node");
+ Control *test_child = memnew(Control);
+ test_child->set_name("child");
+ test_node->add_child(test_child);
+
+ // Test.
+ CHECK_NOTHROW_MESSAGE(test_node->remove_child(test_child));
+
+ memdelete(test_node);
+ }
+}
+
+} // namespace TestGraphNode
+
+#endif // TEST_GRAPH_NODE_H
diff --git a/tests/scene/test_navigation_region_3d.h b/tests/scene/test_navigation_region_3d.h
index 0b20b3a1b2..f3d7f27361 100644
--- a/tests/scene/test_navigation_region_3d.h
+++ b/tests/scene/test_navigation_region_3d.h
@@ -65,7 +65,9 @@ TEST_SUITE("[Navigation]") {
CHECK_EQ(navigation_mesh->get_vertices().size(), 0);
SUBCASE("Synchronous bake should have immediate effects") {
+ ERR_PRINT_OFF; // Suppress warning about baking from visual meshes as source geometry.
navigation_region->bake_navigation_mesh(false);
+ ERR_PRINT_ON;
CHECK_FALSE(navigation_region->is_baking());
CHECK_NE(navigation_mesh->get_polygon_count(), 0);
CHECK_NE(navigation_mesh->get_vertices().size(), 0);