summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SConstruct4
-rw-r--r--core/error/error_macros.h2
-rw-r--r--core/io/image.cpp32
-rw-r--r--core/io/image.h1
-rw-r--r--core/io/resource_loader.cpp22
-rw-r--r--core/io/resource_loader.h2
-rw-r--r--core/object/object.cpp6
-rw-r--r--core/object/script_language_extension.cpp1
-rw-r--r--core/string/string_name.cpp9
-rw-r--r--core/string/string_name.h8
-rw-r--r--core/string/ustring.cpp217
-rw-r--r--doc/classes/BoneAttachment3D.xml6
-rw-r--r--doc/classes/Image.xml8
-rw-r--r--doc/classes/Input.xml1
-rw-r--r--doc/classes/MultiMesh.xml29
-rw-r--r--doc/classes/NavigationMesh.xml2
-rw-r--r--doc/classes/NavigationMeshSourceGeometryData2D.xml6
-rw-r--r--doc/classes/NavigationMeshSourceGeometryData3D.xml6
-rw-r--r--doc/classes/NavigationPolygon.xml12
-rw-r--r--doc/classes/ProjectSettings.xml19
-rw-r--r--doc/classes/RenderingServer.xml52
-rw-r--r--doc/classes/ScriptLanguageExtension.xml7
-rw-r--r--doc/classes/ShapeCast2D.xml18
-rw-r--r--doc/classes/ShapeCast3D.xml18
-rw-r--r--doc/classes/Viewport.xml1
-rw-r--r--doc/classes/ViewportTexture.xml5
-rw-r--r--doc/classes/VisualShaderNodeIntParameter.xml8
-rw-r--r--doc/classes/XRHandTracker.xml5
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp33
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.h2
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp26
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.h3
-rw-r--r--drivers/gles3/shaders/scene.glsl105
-rw-r--r--drivers/gles3/shaders/sky.glsl37
-rw-r--r--drivers/gles3/storage/light_storage.cpp3
-rw-r--r--drivers/gles3/storage/light_storage.h1
-rw-r--r--drivers/gles3/storage/material_storage.cpp10
-rw-r--r--drivers/gles3/storage/mesh_storage.cpp55
-rw-r--r--drivers/gles3/storage/mesh_storage.h56
-rw-r--r--drivers/gles3/storage/texture_storage.cpp8
-rw-r--r--drivers/gles3/storage/texture_storage.h1
-rw-r--r--editor/animation_bezier_editor.cpp2
-rw-r--r--editor/code_editor.cpp3
-rw-r--r--editor/editor_file_system.cpp3
-rw-r--r--editor/editor_folding.cpp2
-rw-r--r--editor/editor_node.cpp12
-rw-r--r--editor/editor_resource_picker.cpp4
-rw-r--r--editor/editor_settings.cpp10
-rw-r--r--editor/editor_settings.h3
-rw-r--r--editor/filesystem_dock.cpp19
-rw-r--r--editor/filesystem_dock.h1
-rw-r--r--editor/gui/scene_tree_editor.cpp19
-rw-r--r--editor/icons/AudioStreamPlayer.svg2
-rw-r--r--editor/icons/AudioStreamPlayer2D.svg2
-rw-r--r--editor/icons/AudioStreamPlayer3D.svg2
-rw-r--r--editor/plugins/gizmos/camera_3d_gizmo_plugin.cpp4
-rw-r--r--editor/plugins/node_3d_editor_gizmos.cpp37
-rw-r--r--editor/plugins/node_3d_editor_gizmos.h5
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp72
-rw-r--r--editor/plugins/node_3d_editor_plugin.h9
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp4
-rw-r--r--main/main.cpp4
-rw-r--r--modules/csg/csg_shape.cpp64
-rw-r--r--modules/csg/csg_shape.h5
-rw-r--r--modules/csg/doc_classes/CSGShape3D.xml19
-rw-r--r--modules/csg/editor/csg_gizmos.cpp146
-rw-r--r--modules/csg/editor/csg_gizmos.h36
-rw-r--r--modules/cvtt/image_compress_cvtt.cpp2
-rw-r--r--modules/gdscript/config.py1
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml10
-rw-r--r--modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml23
-rw-r--r--modules/gdscript/gdscript_parser.cpp52
-rw-r--r--modules/gdscript/gdscript_parser.h8
-rw-r--r--modules/gdscript/register_types.cpp7
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg5
-rw-r--r--modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd8
-rw-r--r--modules/gdscript/tests/scripts/completion/common/identifiers_in_call.cfg (renamed from modules/gdscript/tests/scripts/completion/common/identifiers.cfg)6
-rw-r--r--modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd18
-rw-r--r--modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg25
-rw-r--r--modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.gd (renamed from modules/gdscript/tests/scripts/completion/common/identifiers.gd)4
-rw-r--r--modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg25
-rw-r--r--modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd22
-rw-r--r--modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg26
-rw-r--r--modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd19
-rw-r--r--modules/gdscript/tests/scripts/completion/common/self.gd1
-rw-r--r--modules/gdscript/tests/scripts/completion/filter/organized_export.gd3
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.cfg (renamed from modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.gd (renamed from modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.cfg (renamed from modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.gd (renamed from modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.cfg (renamed from modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.gd (renamed from modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.cfg (renamed from modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.gd (renamed from modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.cfg (renamed from modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.gd (renamed from modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.cfg (renamed from modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.gd (renamed from modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/types/local/infered.cfg (renamed from modules/gdscript/tests/scripts/completion/types/local/interfered.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/types/local/infered.gd (renamed from modules/gdscript/tests/scripts/completion/types/local/interfered.gd)0
-rw-r--r--modules/gdscript/tests/scripts/completion/types/member/infered.cfg (renamed from modules/gdscript/tests/scripts/completion/types/member/interfered.cfg)0
-rw-r--r--modules/gdscript/tests/scripts/completion/types/member/infered.gd (renamed from modules/gdscript/tests/scripts/completion/types/member/interfered.gd)0
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp24
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.h1
-rw-r--r--modules/gltf/gltf_document.cpp9
-rw-r--r--modules/mono/csharp_script.cpp15
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs25
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs93
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs9
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp9
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs10
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs17
-rw-r--r--modules/navigation/2d/nav_mesh_generator_2d.cpp30
-rw-r--r--modules/navigation/3d/godot_navigation_server_3d.cpp21
-rw-r--r--modules/navigation/nav_map.cpp39
-rw-r--r--modules/navigation/nav_map.h6
-rw-r--r--modules/navigation/nav_region.cpp21
-rw-r--r--modules/navigation/nav_region.h8
-rw-r--r--modules/openxr/config.py1
-rw-r--r--modules/openxr/doc_classes/OpenXRVisibilityMask.xml12
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.cpp77
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.h3
-rw-r--r--modules/openxr/extensions/openxr_mxink_extension.cpp83
-rw-r--r--modules/openxr/extensions/openxr_mxink_extension.h48
-rw-r--r--modules/openxr/extensions/openxr_visibility_mask_extension.cpp279
-rw-r--r--modules/openxr/extensions/openxr_visibility_mask_extension.h95
-rw-r--r--modules/openxr/openxr_api.cpp13
-rw-r--r--modules/openxr/openxr_api.h1
-rw-r--r--modules/openxr/openxr_interface.cpp3
-rw-r--r--modules/openxr/register_types.cpp7
-rw-r--r--modules/openxr/scene/openxr_visibility_mask.cpp106
-rw-r--r--modules/openxr/scene/openxr_visibility_mask.h56
-rwxr-xr-xplatform/web/serve.py20
-rw-r--r--platform/windows/display_server_windows.cpp316
-rw-r--r--platform/windows/display_server_windows.h32
-rw-r--r--scene/2d/parallax_layer.cpp8
-rw-r--r--scene/2d/skeleton_2d.cpp40
-rw-r--r--scene/3d/bone_attachment_3d.cpp20
-rw-r--r--scene/3d/bone_attachment_3d.h3
-rw-r--r--scene/3d/multimesh_instance_3d.cpp19
-rw-r--r--scene/3d/multimesh_instance_3d.h5
-rw-r--r--scene/3d/skeleton_3d.cpp12
-rw-r--r--scene/3d/xr_nodes.cpp20
-rw-r--r--scene/3d/xr_nodes.h2
-rw-r--r--scene/gui/code_edit.cpp2
-rw-r--r--scene/gui/menu_bar.cpp5
-rw-r--r--scene/main/node.cpp6
-rw-r--r--scene/main/viewport.cpp1
-rw-r--r--scene/main/window.cpp2
-rw-r--r--scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp70
-rw-r--r--scene/resources/2d/navigation_mesh_source_geometry_data_2d.h5
-rw-r--r--scene/resources/2d/navigation_polygon.cpp18
-rw-r--r--scene/resources/2d/navigation_polygon.h10
-rw-r--r--scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp52
-rw-r--r--scene/resources/3d/navigation_mesh_source_geometry_data_3d.h5
-rw-r--r--scene/resources/multimesh.cpp28
-rw-r--r--scene/resources/multimesh.h16
-rw-r--r--scene/resources/navigation_mesh.h2
-rw-r--r--scene/resources/visual_shader_nodes.cpp36
-rw-r--r--scene/resources/visual_shader_nodes.h5
-rw-r--r--servers/movie_writer/movie_writer.cpp4
-rw-r--r--servers/rendering/dummy/rasterizer_scene_dummy.h1
-rw-r--r--servers/rendering/dummy/storage/material_storage.cpp6
-rw-r--r--servers/rendering/dummy/storage/mesh_storage.cpp10
-rw-r--r--servers/rendering/dummy/storage/mesh_storage.h58
-rw-r--r--servers/rendering/renderer_canvas_cull.cpp54
-rw-r--r--servers/rendering/renderer_canvas_cull.h3
-rw-r--r--servers/rendering/renderer_canvas_render.h1
-rw-r--r--servers/rendering/renderer_rd/effects/fsr2.cpp3
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp13
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h4
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp13
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h5
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp33
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.h2
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.cpp9
-rw-r--r--servers/rendering/renderer_rd/renderer_scene_render_rd.h5
-rw-r--r--servers/rendering/renderer_rd/shaders/environment/sky.glsl8
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl112
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl3
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl117
-rw-r--r--servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl3
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.cpp1
-rw-r--r--servers/rendering/renderer_rd/storage_rd/light_storage.h5
-rw-r--r--servers/rendering/renderer_rd/storage_rd/material_storage.cpp10
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp58
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.h48
-rw-r--r--servers/rendering/renderer_rd/storage_rd/render_data_rd.h2
-rw-r--r--servers/rendering/renderer_scene_cull.cpp6
-rw-r--r--servers/rendering/renderer_scene_cull.h1
-rw-r--r--servers/rendering/renderer_scene_render.h1
-rw-r--r--servers/rendering/renderer_viewport.cpp7
-rw-r--r--servers/rendering/renderer_viewport.h2
-rw-r--r--servers/rendering/rendering_method.h1
-rw-r--r--servers/rendering/rendering_server_default.h7
-rw-r--r--servers/rendering/shader_compiler.cpp4
-rw-r--r--servers/rendering/shader_language.cpp115
-rw-r--r--servers/rendering/shader_language.h10
-rw-r--r--servers/rendering/storage/mesh_storage.cpp485
-rw-r--r--servers/rendering/storage/mesh_storage.h128
-rw-r--r--servers/rendering_server.cpp9
-rw-r--r--servers/rendering_server.h15
-rw-r--r--servers/xr/xr_hand_tracker.cpp1
-rw-r--r--servers/xr/xr_hand_tracker.h1
-rw-r--r--tests/scene/test_code_edit.h20
-rw-r--r--thirdparty/README.md2
-rw-r--r--thirdparty/ufbx/ufbx.c12
-rw-r--r--thirdparty/ufbx/ufbx.h6
208 files changed, 4080 insertions, 826 deletions
diff --git a/SConstruct b/SConstruct
index 0ae8f1a387..599db4b8de 100644
--- a/SConstruct
+++ b/SConstruct
@@ -773,6 +773,10 @@ else:
# MSVC doesn't have clear C standard support, /std only covers C++.
# We apply it to CCFLAGS (both C and C++ code) in case it impacts C features.
env.Prepend(CCFLAGS=["/std:c++17"])
+ # MSVC is non-conforming with the C++ standard by default, so we enable more conformance.
+ # Note that this is still not complete conformance, as certain Windows-related headers
+ # don't compile under complete conformance.
+ env.Prepend(CCFLAGS=["/permissive-"])
# Disable exception handling. Godot doesn't use exceptions anywhere, and this
# saves around 20% of binary size and very significant build time (GH-80513).
diff --git a/core/error/error_macros.h b/core/error/error_macros.h
index d31adb72be..19c16667d0 100644
--- a/core/error/error_macros.h
+++ b/core/error/error_macros.h
@@ -843,6 +843,6 @@ void _physics_interpolation_warning(const char *p_function, const char *p_file,
_physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, m_object_id, m_string)
#define PHYSICS_INTERPOLATION_WARNING(m_string) \
- _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, UINT64_MAX, m_string)
+ _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, ObjectID(UINT64_MAX), m_string)
#endif // ERROR_MACROS_H
diff --git a/core/io/image.cpp b/core/io/image.cpp
index b35d405662..499364826d 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -3564,6 +3564,7 @@ void Image::_bind_methods() {
ClassDB::bind_method(D_METHOD("fix_alpha_edges"), &Image::fix_alpha_edges);
ClassDB::bind_method(D_METHOD("premultiply_alpha"), &Image::premultiply_alpha);
ClassDB::bind_method(D_METHOD("srgb_to_linear"), &Image::srgb_to_linear);
+ ClassDB::bind_method(D_METHOD("linear_to_srgb"), &Image::linear_to_srgb);
ClassDB::bind_method(D_METHOD("normal_map_to_xy"), &Image::normal_map_to_xy);
ClassDB::bind_method(D_METHOD("rgbe_to_srgb"), &Image::rgbe_to_srgb);
ClassDB::bind_method(D_METHOD("bump_map_to_normal_map", "bump_scale"), &Image::bump_map_to_normal_map, DEFVAL(1.0));
@@ -3831,6 +3832,37 @@ void Image::srgb_to_linear() {
}
}
+void Image::linear_to_srgb() {
+ if (data.size() == 0) {
+ return;
+ }
+
+ static const uint8_t lin2srgb[256] = { 0, 12, 21, 28, 33, 38, 42, 46, 49, 52, 55, 58, 61, 63, 66, 68, 70, 73, 75, 77, 79, 81, 82, 84, 86, 88, 89, 91, 93, 94, 96, 97, 99, 100, 102, 103, 104, 106, 107, 109, 110, 111, 112, 114, 115, 116, 117, 118, 120, 121, 122, 123, 124, 125, 126, 127, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 151, 152, 153, 154, 155, 156, 157, 157, 158, 159, 160, 161, 161, 162, 163, 164, 165, 165, 166, 167, 168, 168, 169, 170, 171, 171, 172, 173, 174, 174, 175, 176, 176, 177, 178, 179, 179, 180, 181, 181, 182, 183, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189, 190, 191, 191, 192, 193, 193, 194, 194, 195, 196, 196, 197, 197, 198, 199, 199, 200, 201, 201, 202, 202, 203, 204, 204, 205, 205, 206, 206, 207, 208, 208, 209, 209, 210, 210, 211, 212, 212, 213, 213, 214, 214, 215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 224, 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 236, 237, 237, 237, 238, 238, 239, 239, 240, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 245, 246, 246, 247, 247, 248, 248, 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 254, 254, 255 };
+
+ ERR_FAIL_COND(format != FORMAT_RGB8 && format != FORMAT_RGBA8);
+
+ if (format == FORMAT_RGBA8) {
+ int len = data.size() / 4;
+ uint8_t *data_ptr = data.ptrw();
+
+ for (int i = 0; i < len; i++) {
+ data_ptr[(i << 2) + 0] = lin2srgb[data_ptr[(i << 2) + 0]];
+ data_ptr[(i << 2) + 1] = lin2srgb[data_ptr[(i << 2) + 1]];
+ data_ptr[(i << 2) + 2] = lin2srgb[data_ptr[(i << 2) + 2]];
+ }
+
+ } else if (format == FORMAT_RGB8) {
+ int len = data.size() / 3;
+ uint8_t *data_ptr = data.ptrw();
+
+ for (int i = 0; i < len; i++) {
+ data_ptr[(i * 3) + 0] = lin2srgb[data_ptr[(i * 3) + 0]];
+ data_ptr[(i * 3) + 1] = lin2srgb[data_ptr[(i * 3) + 1]];
+ data_ptr[(i * 3) + 2] = lin2srgb[data_ptr[(i * 3) + 2]];
+ }
+ }
+}
+
void Image::premultiply_alpha() {
if (data.size() == 0) {
return;
diff --git a/core/io/image.h b/core/io/image.h
index d55cc39dbb..cc285dabe3 100644
--- a/core/io/image.h
+++ b/core/io/image.h
@@ -383,6 +383,7 @@ public:
void fix_alpha_edges();
void premultiply_alpha();
void srgb_to_linear();
+ void linear_to_srgb();
void normal_map_to_xy();
Ref<Image> rgbe_to_srgb();
Ref<Image> get_image_from_mipmap(int p_mipmap) const;
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index 4988e73624..0577ca6bca 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -245,9 +245,9 @@ 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()) {
+ if (load_paths_stack.size()) {
thread_load_mutex.lock();
- const String &parent_task_path = load_paths_stack->get(load_paths_stack->size() - 1);
+ 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;
@@ -256,7 +256,7 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
}
thread_load_mutex.unlock();
}
- load_paths_stack->push_back(original_path);
+ load_paths_stack.push_back(original_path);
// Try all loaders and pick the first match for the type hint
bool found = false;
@@ -272,7 +272,7 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
}
}
- load_paths_stack->resize(load_paths_stack->size() - 1);
+ load_paths_stack.resize(load_paths_stack.size() - 1);
res_ref_overrides.erase(load_nesting);
load_nesting--;
@@ -280,6 +280,10 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
return res;
}
+ if (r_error) {
+ *r_error = ERR_FILE_UNRECOGNIZED;
+ }
+
ERR_FAIL_COND_V_MSG(found, Ref<Resource>(),
vformat("Failed loading resource: %s. Make sure resources have been imported by opening the project in the editor at least once.", p_path));
@@ -306,8 +310,7 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
// Thread-safe either if it's the current thread or a brand new one.
CallQueue *own_mq_override = nullptr;
if (load_nesting == 0) {
- load_paths_stack = memnew(Vector<String>);
-
+ DEV_ASSERT(load_paths_stack.is_empty());
if (!Thread::is_main_thread()) {
// Let the caller thread use its own, for added flexibility. Provide one otherwise.
if (MessageQueue::get_singleton() == MessageQueue::get_main_singleton()) {
@@ -408,10 +411,7 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
MessageQueue::set_thread_singleton_override(nullptr);
memdelete(own_mq_override);
}
- if (load_paths_stack) {
- memdelete(load_paths_stack);
- load_paths_stack = nullptr;
- }
+ DEV_ASSERT(load_paths_stack.is_empty());
}
}
@@ -1299,7 +1299,7 @@ bool ResourceLoader::timestamp_on_load = false;
thread_local int ResourceLoader::load_nesting = 0;
thread_local WorkerThreadPool::TaskID ResourceLoader::caller_task_id = 0;
-thread_local Vector<String> *ResourceLoader::load_paths_stack = nullptr;
+thread_local Vector<String> ResourceLoader::load_paths_stack;
thread_local HashMap<int, HashMap<String, Ref<Resource>>> ResourceLoader::res_ref_overrides;
template <>
diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h
index 5f1831f0d9..9d07964105 100644
--- a/core/io/resource_loader.h
+++ b/core/io/resource_loader.h
@@ -188,7 +188,7 @@ private:
static thread_local int load_nesting;
static thread_local WorkerThreadPool::TaskID caller_task_id;
static thread_local HashMap<int, HashMap<String, Ref<Resource>>> res_ref_overrides; // Outermost key is nesting level.
- static thread_local Vector<String> *load_paths_stack; // A pointer to avoid broken TLS implementations from double-running the destructor.
+ static thread_local Vector<String> load_paths_stack;
static SafeBinaryMutex<BINARY_MUTEX_TAG> thread_load_mutex;
static HashMap<String, ThreadLoadTask> thread_load_tasks;
static bool cleaning_tasks;
diff --git a/core/object/object.cpp b/core/object/object.cpp
index ac23bf831f..a2926a478d 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -2097,7 +2097,11 @@ Object::~Object() {
// Disconnect signals that connect to this object.
while (connections.size()) {
Connection c = connections.front()->get();
- bool disconnected = c.signal.get_object()->_disconnect(c.signal.get_name(), c.callable, true);
+ Object *obj = c.callable.get_object();
+ bool disconnected = false;
+ if (likely(obj)) {
+ disconnected = c.signal.get_object()->_disconnect(c.signal.get_name(), c.callable, true);
+ }
if (unlikely(!disconnected)) {
// If the disconnect has failed, abandon the connection to avoid getting trapped in an infinite loop here.
connections.pop_front();
diff --git a/core/object/script_language_extension.cpp b/core/object/script_language_extension.cpp
index 7b643e4637..73f7ec5a54 100644
--- a/core/object/script_language_extension.cpp
+++ b/core/object/script_language_extension.cpp
@@ -142,6 +142,7 @@ void ScriptLanguageExtension::_bind_methods() {
GDVIRTUAL_BIND(_debug_get_current_stack_info);
GDVIRTUAL_BIND(_reload_all_scripts);
+ GDVIRTUAL_BIND(_reload_scripts, "scripts", "soft_reload");
GDVIRTUAL_BIND(_reload_tool_script, "script", "soft_reload");
GDVIRTUAL_BIND(_get_recognized_extensions);
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
index 658297d805..5d59d65f92 100644
--- a/core/string/string_name.cpp
+++ b/core/string/string_name.cpp
@@ -39,19 +39,10 @@ StaticCString StaticCString::create(const char *p_ptr) {
return scs;
}
-StringName::_Data *StringName::_table[STRING_TABLE_LEN];
-
StringName _scs_create(const char *p_chr, bool p_static) {
return (p_chr[0] ? StringName(StaticCString::create(p_chr), p_static) : StringName());
}
-bool StringName::configured = false;
-Mutex StringName::mutex;
-
-#ifdef DEBUG_ENABLED
-bool StringName::debug_stringname = false;
-#endif
-
void StringName::setup() {
ERR_FAIL_COND(configured);
for (int i = 0; i < STRING_TABLE_LEN; i++) {
diff --git a/core/string/string_name.h b/core/string/string_name.h
index 89b4c07e0e..0eb98cf64b 100644
--- a/core/string/string_name.h
+++ b/core/string/string_name.h
@@ -67,7 +67,7 @@ class StringName {
_Data() {}
};
- static _Data *_table[STRING_TABLE_LEN];
+ static inline _Data *_table[STRING_TABLE_LEN];
_Data *_data = nullptr;
@@ -75,10 +75,10 @@ class StringName {
friend void register_core_types();
friend void unregister_core_types();
friend class Main;
- static Mutex mutex;
+ static inline Mutex mutex;
static void setup();
static void cleanup();
- static bool configured;
+ static inline bool configured = false;
#ifdef DEBUG_ENABLED
struct DebugSortReferences {
bool operator()(const _Data *p_left, const _Data *p_right) const {
@@ -86,7 +86,7 @@ class StringName {
}
};
- static bool debug_stringname;
+ static inline bool debug_stringname = false;
#endif
StringName(_Data *p_data) { _data = p_data; }
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 3aaaf46b06..2cfc48d395 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -4032,54 +4032,161 @@ String String::format(const Variant &values, const String &placeholder) const {
return new_string;
}
-String String::replace(const String &p_key, const String &p_with) const {
- String new_string;
+static String _replace_common(const String &p_this, const String &p_key, const String &p_with, bool p_case_insensitive) {
+ if (p_key.is_empty() || p_this.is_empty()) {
+ return p_this;
+ }
+
+ const int key_length = p_key.length();
+
int search_from = 0;
int result = 0;
- while ((result = find(p_key, search_from)) >= 0) {
- new_string += substr(search_from, result - search_from);
- new_string += p_with;
- search_from = result + p_key.length();
+ LocalVector<int> found;
+
+ while ((result = (p_case_insensitive ? p_this.findn(p_key, search_from) : p_this.find(p_key, search_from))) >= 0) {
+ found.push_back(result);
+ search_from = result + key_length;
}
- if (search_from == 0) {
- return *this;
+ if (found.is_empty()) {
+ return p_this;
}
- new_string += substr(search_from, length() - search_from);
+ String new_string;
+
+ const int with_length = p_with.length();
+ const int old_length = p_this.length();
+
+ new_string.resize(old_length + found.size() * (with_length - key_length) + 1);
+
+ char32_t *new_ptrw = new_string.ptrw();
+ const char32_t *old_ptr = p_this.ptr();
+ const char32_t *with_ptr = p_with.ptr();
+
+ int last_pos = 0;
+
+ for (const int &pos : found) {
+ if (last_pos != pos) {
+ memcpy(new_ptrw, old_ptr + last_pos, (pos - last_pos) * sizeof(char32_t));
+ new_ptrw += (pos - last_pos);
+ }
+ if (with_length) {
+ memcpy(new_ptrw, with_ptr, with_length * sizeof(char32_t));
+ new_ptrw += with_length;
+ }
+ last_pos = pos + key_length;
+ }
+
+ if (last_pos != old_length) {
+ memcpy(new_ptrw, old_ptr + last_pos, (old_length - last_pos) * sizeof(char32_t));
+ new_ptrw += old_length - last_pos;
+ }
+
+ *new_ptrw = 0;
return new_string;
}
-String String::replace(const char *p_key, const char *p_with) const {
- String new_string;
+static String _replace_common(const String &p_this, char const *p_key, char const *p_with, bool p_case_insensitive) {
+ int key_length = strlen(p_key);
+
+ if (key_length == 0 || p_this.is_empty()) {
+ return p_this;
+ }
+
int search_from = 0;
int result = 0;
- while ((result = find(p_key, search_from)) >= 0) {
- new_string += substr(search_from, result - search_from);
- new_string += p_with;
- int k = 0;
- while (p_key[k] != '\0') {
- k++;
+ LocalVector<int> found;
+
+ while ((result = (p_case_insensitive ? p_this.findn(p_key, search_from) : p_this.find(p_key, search_from))) >= 0) {
+ found.push_back(result);
+ search_from = result + key_length;
+ }
+
+ if (found.is_empty()) {
+ return p_this;
+ }
+
+ String new_string;
+
+ // Create string to speed up copying as we can't do `memcopy` between `char32_t` and `char`.
+ const String with_string(p_with);
+ const int with_length = with_string.length();
+ const int old_length = p_this.length();
+
+ new_string.resize(old_length + found.size() * (with_length - key_length) + 1);
+
+ char32_t *new_ptrw = new_string.ptrw();
+ const char32_t *old_ptr = p_this.ptr();
+ const char32_t *with_ptr = with_string.ptr();
+
+ int last_pos = 0;
+
+ for (const int &pos : found) {
+ if (last_pos != pos) {
+ memcpy(new_ptrw, old_ptr + last_pos, (pos - last_pos) * sizeof(char32_t));
+ new_ptrw += (pos - last_pos);
}
- search_from = result + k;
+ if (with_length) {
+ memcpy(new_ptrw, with_ptr, with_length * sizeof(char32_t));
+ new_ptrw += with_length;
+ }
+ last_pos = pos + key_length;
}
- if (search_from == 0) {
- return *this;
+ if (last_pos != old_length) {
+ memcpy(new_ptrw, old_ptr + last_pos, (old_length - last_pos) * sizeof(char32_t));
+ new_ptrw += old_length - last_pos;
}
- new_string += substr(search_from, length() - search_from);
+ *new_ptrw = 0;
return new_string;
}
+String String::replace(const String &p_key, const String &p_with) const {
+ return _replace_common(*this, p_key, p_with, false);
+}
+
+String String::replace(const char *p_key, const char *p_with) const {
+ return _replace_common(*this, p_key, p_with, false);
+}
+
String String::replace_first(const String &p_key, const String &p_with) const {
int pos = find(p_key);
if (pos >= 0) {
- return substr(0, pos) + p_with + substr(pos + p_key.length(), length());
+ const int old_length = length();
+ const int key_length = p_key.length();
+ const int with_length = p_with.length();
+
+ String new_string;
+ new_string.resize(old_length + (with_length - key_length) + 1);
+
+ char32_t *new_ptrw = new_string.ptrw();
+ const char32_t *old_ptr = ptr();
+ const char32_t *with_ptr = p_with.ptr();
+
+ if (pos > 0) {
+ memcpy(new_ptrw, old_ptr, pos * sizeof(char32_t));
+ new_ptrw += pos;
+ }
+
+ if (with_length) {
+ memcpy(new_ptrw, with_ptr, with_length * sizeof(char32_t));
+ new_ptrw += with_length;
+ }
+ pos += key_length;
+
+ if (pos != old_length) {
+ memcpy(new_ptrw, old_ptr + pos, (old_length - pos) * sizeof(char32_t));
+ new_ptrw += (old_length - pos);
+ }
+
+ *new_ptrw = 0;
+
+ return new_string;
}
return *this;
@@ -4088,55 +4195,45 @@ String String::replace_first(const String &p_key, const String &p_with) const {
String String::replace_first(const char *p_key, const char *p_with) const {
int pos = find(p_key);
if (pos >= 0) {
- int substring_length = strlen(p_key);
- return substr(0, pos) + p_with + substr(pos + substring_length, length());
- }
+ const int old_length = length();
+ const int key_length = strlen(p_key);
+ const int with_length = strlen(p_with);
- return *this;
-}
-
-String String::replacen(const String &p_key, const String &p_with) const {
- String new_string;
- int search_from = 0;
- int result = 0;
+ String new_string;
+ new_string.resize(old_length + (with_length - key_length) + 1);
- while ((result = findn(p_key, search_from)) >= 0) {
- new_string += substr(search_from, result - search_from);
- new_string += p_with;
- search_from = result + p_key.length();
- }
+ char32_t *new_ptrw = new_string.ptrw();
+ const char32_t *old_ptr = ptr();
- if (search_from == 0) {
- return *this;
- }
+ if (pos > 0) {
+ memcpy(new_ptrw, old_ptr, pos * sizeof(char32_t));
+ new_ptrw += pos;
+ }
- new_string += substr(search_from, length() - search_from);
- return new_string;
-}
+ for (int i = 0; i < with_length; ++i) {
+ *new_ptrw++ = p_with[i];
+ }
+ pos += key_length;
-String String::replacen(const char *p_key, const char *p_with) const {
- String new_string;
- int search_from = 0;
- int result = 0;
- int substring_length = strlen(p_key);
+ if (pos != old_length) {
+ memcpy(new_ptrw, old_ptr + pos, (old_length - pos) * sizeof(char32_t));
+ new_ptrw += (old_length - pos);
+ }
- if (substring_length == 0) {
- return *this; // there's nothing to match or substitute
- }
+ *new_ptrw = 0;
- while ((result = findn(p_key, search_from)) >= 0) {
- new_string += substr(search_from, result - search_from);
- new_string += p_with;
- search_from = result + substring_length;
+ return new_string;
}
- if (search_from == 0) {
- return *this;
- }
+ return *this;
+}
- new_string += substr(search_from, length() - search_from);
+String String::replacen(const String &p_key, const String &p_with) const {
+ return _replace_common(*this, p_key, p_with, true);
+}
- return new_string;
+String String::replacen(const char *p_key, const char *p_with) const {
+ return _replace_common(*this, p_key, p_with, true);
}
String String::repeat(int p_count) const {
diff --git a/doc/classes/BoneAttachment3D.xml b/doc/classes/BoneAttachment3D.xml
index a51b2d9a94..c66eb159cb 100644
--- a/doc/classes/BoneAttachment3D.xml
+++ b/doc/classes/BoneAttachment3D.xml
@@ -15,6 +15,12 @@
Returns the [NodePath] to the external [Skeleton3D] node, if one has been set.
</description>
</method>
+ <method name="get_skeleton">
+ <return type="Skeleton3D" />
+ <description>
+ Get parent or external [Skeleton3D] node if found.
+ </description>
+ </method>
<method name="get_use_external_skeleton" qualifiers="const">
<return type="bool" />
<description>
diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml
index db7796778e..e254fd56e9 100644
--- a/doc/classes/Image.xml
+++ b/doc/classes/Image.xml
@@ -321,6 +321,12 @@
Returns [code]true[/code] if all the image's pixels have an alpha value of 0. Returns [code]false[/code] if any pixel has an alpha value higher than 0.
</description>
</method>
+ <method name="linear_to_srgb">
+ <return type="void" />
+ <description>
+ Converts the entire image from the linear colorspace to the sRGB colorspace. Only works on images with [constant FORMAT_RGB8] or [constant FORMAT_RGBA8] formats.
+ </description>
+ </method>
<method name="load">
<return type="int" enum="Error" />
<param index="0" name="path" type="String" />
@@ -590,7 +596,7 @@
<method name="srgb_to_linear">
<return type="void" />
<description>
- Converts the raw data from the sRGB colorspace to a linear scale.
+ Converts the raw data from the sRGB colorspace to a linear scale. Only works on images with [constant FORMAT_RGB8] or [constant FORMAT_RGBA8] formats.
</description>
</method>
</methods>
diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml
index 1e7b63ecce..5fdcc0b26b 100644
--- a/doc/classes/Input.xml
+++ b/doc/classes/Input.xml
@@ -133,6 +133,7 @@
[code]vendor_id[/code]: The USB vendor ID of the device.
[code]product_id[/code]: The USB product ID of the device.
[code]steam_input_index[/code]: The Steam Input gamepad index, if the device is not a Steam Input device this key won't be present.
+ [b]Note:[/b] The returned dictionary is always empty on Web, iOS, Android, and macOS.
</description>
</method>
<method name="get_joy_name">
diff --git a/doc/classes/MultiMesh.xml b/doc/classes/MultiMesh.xml
index 529912171c..e99694c3f0 100644
--- a/doc/classes/MultiMesh.xml
+++ b/doc/classes/MultiMesh.xml
@@ -51,6 +51,24 @@
Returns the [Transform2D] of a specific instance.
</description>
</method>
+ <method name="reset_instance_physics_interpolation">
+ <return type="void" />
+ <param index="0" name="instance" type="int" />
+ <description>
+ When using [i]physics interpolation[/i], this function allows you to prevent interpolation on an instance in the current physics tick.
+ This allows you to move instances instantaneously, and should usually be used when initially placing an instance such as a bullet to prevent graphical glitches.
+ </description>
+ </method>
+ <method name="set_buffer_interpolated">
+ <return type="void" />
+ <param index="0" name="buffer_curr" type="PackedFloat32Array" />
+ <param index="1" name="buffer_prev" type="PackedFloat32Array" />
+ <description>
+ An alternative to setting the [member buffer] property, which can be used with [i]physics interpolation[/i]. This method takes two arrays, and can set the data for the current and previous tick in one go. The renderer will automatically interpolate the data at each frame.
+ This is useful for situations where the order of instances may change from physics tick to tick, such as particle systems.
+ When the order of instances is coherent, the simpler alternative of setting [member buffer] can still be used with interpolation.
+ </description>
+ </method>
<method name="set_instance_color">
<return type="void" />
<param index="0" name="instance" type="int" />
@@ -109,6 +127,11 @@
[Mesh] resource to be instanced.
The looks of the individual instances can be modified using [method set_instance_color] and [method set_instance_custom_data].
</member>
+ <member name="physics_interpolation_quality" type="int" setter="set_physics_interpolation_quality" getter="get_physics_interpolation_quality" enum="MultiMesh.PhysicsInterpolationQuality" default="0">
+ Choose whether to use an interpolation method that favors speed or quality.
+ When using low physics tick rates (typically below 20) or high rates of object rotation, you may get better results from the high quality setting.
+ [b]Note:[/b] Fast quality does not equate to low quality. Except in the special cases mentioned above, the quality should be comparable to high quality.
+ </member>
<member name="transform_2d_array" type="PackedVector2Array" setter="_set_transform_2d_array" getter="_get_transform_2d_array" deprecated="Accessing this property is very slow. Use [method set_instance_transform_2d] and [method get_instance_transform_2d] instead.">
Array containing each [Transform2D] value used by all instances of this mesh, as a [PackedVector2Array]. Each transform is divided into 3 [Vector2] values corresponding to the transforms' [code]x[/code], [code]y[/code], and [code]origin[/code].
</member>
@@ -135,5 +158,11 @@
<constant name="TRANSFORM_3D" value="1" enum="TransformFormat">
Use this when using 3D transforms.
</constant>
+ <constant name="INTERP_QUALITY_FAST" value="0" enum="PhysicsInterpolationQuality">
+ Always interpolate using Basis lerping, which can produce warping artifacts in some situations.
+ </constant>
+ <constant name="INTERP_QUALITY_HIGH" value="1" enum="PhysicsInterpolationQuality">
+ Attempt to interpolate using Basis slerping (spherical linear interpolation) where possible, otherwise fall back to lerping.
+ </constant>
</constants>
</class>
diff --git a/doc/classes/NavigationMesh.xml b/doc/classes/NavigationMesh.xml
index 8be8a89543..5898e6841a 100644
--- a/doc/classes/NavigationMesh.xml
+++ b/doc/classes/NavigationMesh.xml
@@ -139,7 +139,7 @@
The physics layers to scan for static colliders.
Only used when [member geometry_parsed_geometry_type] is [constant PARSED_GEOMETRY_STATIC_COLLIDERS] or [constant PARSED_GEOMETRY_BOTH].
</member>
- <member name="geometry_parsed_geometry_type" type="int" setter="set_parsed_geometry_type" getter="get_parsed_geometry_type" enum="NavigationMesh.ParsedGeometryType" default="0">
+ <member name="geometry_parsed_geometry_type" type="int" setter="set_parsed_geometry_type" getter="get_parsed_geometry_type" enum="NavigationMesh.ParsedGeometryType" default="2">
Determines which type of nodes will be parsed as geometry. See [enum ParsedGeometryType] for possible values.
</member>
<member name="geometry_source_geometry_mode" type="int" setter="set_source_geometry_mode" getter="get_source_geometry_mode" enum="NavigationMesh.SourceGeometryMode" default="0">
diff --git a/doc/classes/NavigationMeshSourceGeometryData2D.xml b/doc/classes/NavigationMeshSourceGeometryData2D.xml
index 1d8689420b..82b6e077fe 100644
--- a/doc/classes/NavigationMeshSourceGeometryData2D.xml
+++ b/doc/classes/NavigationMeshSourceGeometryData2D.xml
@@ -57,6 +57,12 @@
Clears all projected obstructions.
</description>
</method>
+ <method name="get_bounds">
+ <return type="Rect2" />
+ <description>
+ Returns an axis-aligned bounding box that covers all the stored geometry data. The bounds are calculated when calling this function with the result cached until further geometry changes are made.
+ </description>
+ </method>
<method name="get_obstruction_outlines" qualifiers="const">
<return type="PackedVector2Array[]" />
<description>
diff --git a/doc/classes/NavigationMeshSourceGeometryData3D.xml b/doc/classes/NavigationMeshSourceGeometryData3D.xml
index 0b3126a63b..aa43dff52d 100644
--- a/doc/classes/NavigationMeshSourceGeometryData3D.xml
+++ b/doc/classes/NavigationMeshSourceGeometryData3D.xml
@@ -63,6 +63,12 @@
Clears all projected obstructions.
</description>
</method>
+ <method name="get_bounds">
+ <return type="AABB" />
+ <description>
+ Returns an axis-aligned bounding box that covers all the stored geometry data. The bounds are calculated when calling this function with the result cached until further geometry changes are made.
+ </description>
+ </method>
<method name="get_indices" qualifiers="const">
<return type="PackedInt32Array" />
<description>
diff --git a/doc/classes/NavigationPolygon.xml b/doc/classes/NavigationPolygon.xml
index eebdc817a7..68fbc05931 100644
--- a/doc/classes/NavigationPolygon.xml
+++ b/doc/classes/NavigationPolygon.xml
@@ -193,6 +193,9 @@
<member name="parsed_geometry_type" type="int" setter="set_parsed_geometry_type" getter="get_parsed_geometry_type" enum="NavigationPolygon.ParsedGeometryType" default="2">
Determines which type of nodes will be parsed as geometry. See [enum ParsedGeometryType] for possible values.
</member>
+ <member name="sample_partition_type" type="int" setter="set_sample_partition_type" getter="get_sample_partition_type" enum="NavigationPolygon.SamplePartitionType" default="0">
+ Partitioning algorithm for creating the navigation mesh polys. See [enum SamplePartitionType] for possible values.
+ </member>
<member name="source_geometry_group_name" type="StringName" setter="set_source_geometry_group_name" getter="get_source_geometry_group_name" default="&amp;&quot;navigation_polygon_source_geometry_group&quot;">
The group name of nodes that should be parsed for baking source geometry.
Only used when [member source_geometry_mode] is [constant SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN] or [constant SOURCE_GEOMETRY_GROUPS_EXPLICIT].
@@ -202,6 +205,15 @@
</member>
</members>
<constants>
+ <constant name="SAMPLE_PARTITION_CONVEX_PARTITION" value="0" enum="SamplePartitionType">
+ Convex partitioning that yields navigation mesh with convex polygons.
+ </constant>
+ <constant name="SAMPLE_PARTITION_TRIANGULATE" value="1" enum="SamplePartitionType">
+ Triangulation partitioning that yields navigation mesh with triangle polygons.
+ </constant>
+ <constant name="SAMPLE_PARTITION_MAX" value="2" enum="SamplePartitionType">
+ Represents the size of the [enum SamplePartitionType] enum.
+ </constant>
<constant name="PARSED_GEOMETRY_MESH_INSTANCES" value="0" enum="ParsedGeometryType">
Parses mesh instances as obstruction geometry. This includes [Polygon2D], [MeshInstance2D], [MultiMeshInstance2D], and [TileMap] nodes.
Meshes are only parsed when they use a 2D vertices surface format.
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 1d65f6fb9a..34c41195af 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -2604,6 +2604,10 @@
- Intel GPUs: SYCL libraries
If no GPU acceleration is configured on the system, multi-threaded CPU-based denoising will be performed instead. This CPU-based denoising is significantly slower than the JNLM denoiser in most cases.
</member>
+ <member name="rendering/lightmapping/lightmap_gi/use_bicubic_filter" type="bool" setter="" getter="" default="true">
+ If [code]true[/code], applies a bicubic filter during lightmap sampling. This makes lightmaps look much smoother, at a moderate performance cost.
+ [b]Note:[/b] The bicubic filter exaggerates the 'bleeding' effect that occurs when a lightmap's resolution is low enough.
+ </member>
<member name="rendering/lightmapping/primitive_meshes/texel_size" type="float" setter="" getter="" default="0.2">
The texel_size that is used to calculate the [member Mesh.lightmap_size_hint] on [PrimitiveMesh] resources if [member PrimitiveMesh.add_uv2] is enabled.
</member>
@@ -2947,10 +2951,19 @@
Specify whether to enable eye tracking for this project. Depending on the platform, additional export configuration may be needed.
</member>
<member name="xr/openxr/extensions/hand_interaction_profile" type="bool" setter="" getter="" default="false">
- If true the hand interaction profile extension will be activated if supported by the platform.
+ If [code]true[/code] the hand interaction profile extension will be activated if supported by the platform.
+ </member>
+ <member name="xr/openxr/extensions/hand_tracking" type="bool" setter="" getter="" default="false">
+ If [code]true[/code], the hand tracking extension is enabled if available.
+ [b]Note:[/b] By default hand tracking will only work for data sources chosen by the XR runtime. For SteamVR this is the controller inferred data source, for most other runtimes this is the unobstructed data source. There is no way to query this. If a runtime supports the OpenXR data source extension you can use the [member xr/openxr/extensions/hand_tracking_controller_data_source] and/or [member xr/openxr/extensions/hand_tracking_unobstructed_data_source] to indicate you wish to enable these data sources. If neither is selected the data source extension is not enabled and the XR runtimes default behavior persists.
+ </member>
+ <member name="xr/openxr/extensions/hand_tracking_controller_data_source" type="bool" setter="" getter="" default="false">
+ If [code]true[/code], support for the controller inferred data source is requested. If supported, you will receive hand tracking data even if the user has a controller in hand, with finger positions automatically inferred from controller input and/or sensors.
+ [b]Node:[/b] This requires the OpenXR data source extension and controller inferred handtracking to be supported by the XR runtime. If not supported this setting will be ignored. [member xr/openxr/extensions/hand_tracking] must be enabled for this setting to be used.
</member>
- <member name="xr/openxr/extensions/hand_tracking" type="bool" setter="" getter="" default="true">
- If true we enable the hand tracking extension if available.
+ <member name="xr/openxr/extensions/hand_tracking_unobstructed_data_source" type="bool" setter="" getter="" default="false">
+ If [code]true[/code], support for the unobstructed data source is requested. If supported, you will receive hand tracking data based on the actual finger positions of the user often determined by optical tracking.
+ [b]Node:[/b] This requires the OpenXR data source extension and unobstructed handtracking to be supported by the XR runtime. If not supported this setting will be ignored. [member xr/openxr/extensions/hand_tracking] must be enabled for this setting to be used.
</member>
<member name="xr/openxr/form_factor" type="int" setter="" getter="" default="&quot;0&quot;">
Specify whether OpenXR should be configured for an HMD or a hand held device.
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index d86b82b72a..c81d5d4fab 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -944,7 +944,8 @@
<param index="1" name="item" type="RID" />
<param index="2" name="mirroring" type="Vector2" />
<description>
- A copy of the canvas item will be drawn with a local offset of the mirroring [Vector2].
+ A copy of the canvas item will be drawn with a local offset of the [param mirroring].
+ [b]Note:[/b] This is equivalent to calling [method canvas_set_item_repeat] like [code]canvas_set_item_repeat(item, mirroring, 1)[/code], with an additional check ensuring [param canvas] is a parent of [param item].
</description>
</method>
<method name="canvas_set_item_repeat">
@@ -2206,6 +2207,13 @@
Set the textures on the given [param lightmap] GI instance to the texture array pointed to by the [param light] RID. If the lightmap texture was baked with [member LightmapGI.directional] set to [code]true[/code], then [param uses_sh] must also be [code]true[/code].
</description>
</method>
+ <method name="lightmaps_set_bicubic_filter">
+ <return type="void" />
+ <param index="0" name="enable" type="bool" />
+ <description>
+ Toggles whether a bicubic filter should be used when lightmaps are sampled. This smoothens their appearance at a performance cost.
+ </description>
+ </method>
<method name="make_sphere_mesh">
<return type="RID" />
<param index="0" name="latitudes" type="int" />
@@ -2558,6 +2566,15 @@
Returns the [Transform2D] of the specified instance. For use when the multimesh is set to use 2D transforms.
</description>
</method>
+ <method name="multimesh_instance_reset_physics_interpolation">
+ <return type="void" />
+ <param index="0" name="multimesh" type="RID" />
+ <param index="1" name="index" type="int" />
+ <description>
+ Prevents physics interpolation for the specified instance during the current physics tick.
+ This is useful when moving an instance to a new location, to give an instantaneous change rather than interpolation from the previous location.
+ </description>
+ </method>
<method name="multimesh_instance_set_color">
<return type="void" />
<param index="0" name="multimesh" type="RID" />
@@ -2615,6 +2632,16 @@
[/codeblock]
</description>
</method>
+ <method name="multimesh_set_buffer_interpolated">
+ <return type="void" />
+ <param index="0" name="multimesh" type="RID" />
+ <param index="1" name="buffer" type="PackedFloat32Array" />
+ <param index="2" name="buffer_previous" type="PackedFloat32Array" />
+ <description>
+ Alternative version of [method multimesh_set_buffer] for use with physics interpolation.
+ Takes both an array of current data and an array of data for the previous physics tick.
+ </description>
+ </method>
<method name="multimesh_set_custom_aabb">
<return type="void" />
<param index="0" name="multimesh" type="RID" />
@@ -2631,6 +2658,23 @@
Sets the mesh to be drawn by the multimesh. Equivalent to [member MultiMesh.mesh].
</description>
</method>
+ <method name="multimesh_set_physics_interpolated">
+ <return type="void" />
+ <param index="0" name="multimesh" type="RID" />
+ <param index="1" name="interpolated" type="bool" />
+ <description>
+ Turns on and off physics interpolation for this MultiMesh resource.
+ </description>
+ </method>
+ <method name="multimesh_set_physics_interpolation_quality">
+ <return type="void" />
+ <param index="0" name="multimesh" type="RID" />
+ <param index="1" name="quality" type="int" enum="RenderingServer.MultimeshPhysicsInterpolationQuality" />
+ <description>
+ Sets the physics interpolation quality for the [MultiMesh].
+ A value of [constant MULTIMESH_INTERP_QUALITY_FAST] gives fast but low quality interpolation, a value of [constant MULTIMESH_INTERP_QUALITY_HIGH] gives slower but higher quality interpolation.
+ </description>
+ </method>
<method name="multimesh_set_visible_instances">
<return type="void" />
<param index="0" name="multimesh" type="RID" />
@@ -4500,6 +4544,12 @@
<constant name="MULTIMESH_TRANSFORM_3D" value="1" enum="MultimeshTransformFormat">
Use [Transform3D] to store MultiMesh transform.
</constant>
+ <constant name="MULTIMESH_INTERP_QUALITY_FAST" value="0" enum="MultimeshPhysicsInterpolationQuality">
+ MultiMesh physics interpolation favors speed over quality.
+ </constant>
+ <constant name="MULTIMESH_INTERP_QUALITY_HIGH" value="1" enum="MultimeshPhysicsInterpolationQuality">
+ MultiMesh physics interpolation favors quality over speed.
+ </constant>
<constant name="LIGHT_PROJECTOR_FILTER_NEAREST" value="0" enum="LightProjectorFilter">
Nearest-neighbor filter for light projectors (use for pixel art light projectors). No mipmaps are used for rendering, which means light projectors at a distance will look sharp but grainy. This has roughly the same performance cost as using mipmaps.
</constant>
diff --git a/doc/classes/ScriptLanguageExtension.xml b/doc/classes/ScriptLanguageExtension.xml
index cc47ca274d..f9d9e4f513 100644
--- a/doc/classes/ScriptLanguageExtension.xml
+++ b/doc/classes/ScriptLanguageExtension.xml
@@ -314,6 +314,13 @@
<description>
</description>
</method>
+ <method name="_reload_scripts" qualifiers="virtual">
+ <return type="void" />
+ <param index="0" name="scripts" type="Array" />
+ <param index="1" name="soft_reload" type="bool" />
+ <description>
+ </description>
+ </method>
<method name="_reload_tool_script" qualifiers="virtual">
<return type="void" />
<param index="0" name="script" type="Script" />
diff --git a/doc/classes/ShapeCast2D.xml b/doc/classes/ShapeCast2D.xml
index 385e3a9285..6f5da28825 100644
--- a/doc/classes/ShapeCast2D.xml
+++ b/doc/classes/ShapeCast2D.xml
@@ -15,7 +15,7 @@
<return type="void" />
<param index="0" name="node" type="CollisionObject2D" />
<description>
- Adds a collision exception so the shape does not report collisions with the specified [CollisionObject2D] node.
+ Adds a collision exception so the shape does not report collisions with the specified node.
</description>
</method>
<method name="add_exception_rid">
@@ -35,19 +35,19 @@
<return type="void" />
<description>
Updates the collision information for the shape immediately, without waiting for the next [code]_physics_process[/code] call. Use this method, for example, when the shape or its parent has changed state.
- [b]Note:[/b] [code]enabled == true[/code] is not required for this to work.
+ [b]Note:[/b] Setting [member enabled] to [code]true[/code] is not required for this to work.
</description>
</method>
<method name="get_closest_collision_safe_fraction" qualifiers="const">
<return type="float" />
<description>
- The fraction from the [ShapeCast2D]'s origin to its [member target_position] (between 0 and 1) of how far the shape can move without triggering a collision.
+ Returns the fraction from this cast's origin to its [member target_position] of how far the shape can move without triggering a collision, as a value between [code]0.0[/code] and [code]1.0[/code].
</description>
</method>
<method name="get_closest_collision_unsafe_fraction" qualifiers="const">
<return type="float" />
<description>
- The fraction from the [ShapeCast2D]'s origin to its [member target_position] (between 0 and 1) of how far the shape must move to trigger a collision.
+ Returns the fraction from this cast's origin to its [member target_position] of how far the shape must move to trigger a collision, as a value between [code]0.0[/code] and [code]1.0[/code].
In ideal conditions this would be the same as [method get_closest_collision_safe_fraction], however shape casting is calculated in discrete steps, so the precise point of collision can occur between two calculated positions.
</description>
</method>
@@ -97,7 +97,7 @@
<param index="0" name="index" type="int" />
<description>
Returns the collision point of one of the multiple collisions at [param index] where the shape intersects the colliding object.
- [b]Note:[/b] this point is in the [b]global[/b] coordinate system.
+ [b]Note:[/b] This point is in the [b]global[/b] coordinate system.
</description>
</method>
<method name="is_colliding" qualifiers="const">
@@ -110,7 +110,7 @@
<return type="void" />
<param index="0" name="node" type="CollisionObject2D" />
<description>
- Removes a collision exception so the shape does report collisions with the specified [CollisionObject2D] node.
+ Removes a collision exception so the shape does report collisions with the specified node.
</description>
</method>
<method name="remove_exception_rid">
@@ -137,7 +137,7 @@
If [code]true[/code], collisions with [PhysicsBody2D]s will be reported.
</member>
<member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1">
- The shape's collision mask. Only objects in at least one collision layer enabled in the mask will be detected.
+ The shape's collision mask. Only objects in at least one collision layer enabled in the mask will be detected. See [url=$DOCS_URL/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
</member>
<member name="collision_result" type="Array" setter="" getter="get_collision_result" default="[]">
Returns the complete collision information from the collision sweep. The data returned is the same as in the [method PhysicsDirectSpaceState2D.get_rest_info] method.
@@ -155,10 +155,10 @@
The number of intersections can be limited with this parameter, to reduce the processing time.
</member>
<member name="shape" type="Shape2D" setter="set_shape" getter="get_shape">
- The [Shape2D]-derived shape to be used for collision queries.
+ The shape to be used for collision queries.
</member>
<member name="target_position" type="Vector2" setter="set_target_position" getter="get_target_position" default="Vector2(0, 50)">
- The shape's destination point, relative to this node's [code]position[/code].
+ The shape's destination point, relative to this node's [member Node2D.position].
</member>
</members>
</class>
diff --git a/doc/classes/ShapeCast3D.xml b/doc/classes/ShapeCast3D.xml
index f70cf169df..ba0b021b4d 100644
--- a/doc/classes/ShapeCast3D.xml
+++ b/doc/classes/ShapeCast3D.xml
@@ -15,7 +15,7 @@
<return type="void" />
<param index="0" name="node" type="CollisionObject3D" />
<description>
- Adds a collision exception so the shape does not report collisions with the specified [CollisionObject3D] node.
+ Adds a collision exception so the shape does not report collisions with the specified node.
</description>
</method>
<method name="add_exception_rid">
@@ -28,26 +28,26 @@
<method name="clear_exceptions">
<return type="void" />
<description>
- Removes all collision exceptions for this [ShapeCast3D].
+ Removes all collision exceptions for this shape.
</description>
</method>
<method name="force_shapecast_update">
<return type="void" />
<description>
Updates the collision information for the shape immediately, without waiting for the next [code]_physics_process[/code] call. Use this method, for example, when the shape or its parent has changed state.
- [b]Note:[/b] [code]enabled == true[/code] is not required for this to work.
+ [b]Note:[/b] Setting [member enabled] to [code]true[/code] is not required for this to work.
</description>
</method>
<method name="get_closest_collision_safe_fraction" qualifiers="const">
<return type="float" />
<description>
- The fraction from the [ShapeCast3D]'s origin to its [member target_position] (between 0 and 1) of how far the shape can move without triggering a collision.
+ Returns the fraction from this cast's origin to its [member target_position] of how far the shape can move without triggering a collision, as a value between [code]0.0[/code] and [code]1.0[/code].
</description>
</method>
<method name="get_closest_collision_unsafe_fraction" qualifiers="const">
<return type="float" />
<description>
- The fraction from the [ShapeCast3D]'s origin to its [member target_position] (between 0 and 1) of how far the shape must move to trigger a collision.
+ Returns the fraction from this cast's origin to its [member target_position] of how far the shape must move to trigger a collision, as a value between [code]0.0[/code] and [code]1.0[/code].
In ideal conditions this would be the same as [method get_closest_collision_safe_fraction], however shape casting is calculated in discrete steps, so the precise point of collision can occur between two calculated positions.
</description>
</method>
@@ -97,7 +97,7 @@
<param index="0" name="index" type="int" />
<description>
Returns the collision point of one of the multiple collisions at [param index] where the shape intersects the colliding object.
- [b]Note:[/b] this point is in the [b]global[/b] coordinate system.
+ [b]Note:[/b] This point is in the [b]global[/b] coordinate system.
</description>
</method>
<method name="is_colliding" qualifiers="const">
@@ -110,7 +110,7 @@
<return type="void" />
<param index="0" name="node" type="CollisionObject3D" />
<description>
- Removes a collision exception so the shape does report collisions with the specified [CollisionObject3D] node.
+ Removes a collision exception so the shape does report collisions with the specified node.
</description>
</method>
<method name="remove_exception_rid">
@@ -166,10 +166,10 @@
The number of intersections can be limited with this parameter, to reduce the processing time.
</member>
<member name="shape" type="Shape3D" setter="set_shape" getter="get_shape">
- The [Shape3D]-derived shape to be used for collision queries.
+ The shape to be used for collision queries.
</member>
<member name="target_position" type="Vector3" setter="set_target_position" getter="get_target_position" default="Vector3(0, -1, 0)">
- The shape's destination point, relative to this node's [code]position[/code].
+ The shape's destination point, relative to this node's [member Node3D.position].
</member>
</members>
</class>
diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index f57185ae87..b24f26a764 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -102,6 +102,7 @@
await RenderingServer.frame_post_draw
$Viewport.get_texture().get_image().save_png("user://Screenshot.png")
[/codeblock]
+ [b]Note:[/b] When [member use_hdr_2d] is [code]true[/code] the returned texture will be an HDR image encoded in linear space.
</description>
</method>
<method name="get_viewport_rid" qualifiers="const">
diff --git a/doc/classes/ViewportTexture.xml b/doc/classes/ViewportTexture.xml
index ba2352ab61..f6840d6b09 100644
--- a/doc/classes/ViewportTexture.xml
+++ b/doc/classes/ViewportTexture.xml
@@ -8,6 +8,11 @@
To get a [ViewportTexture] in code, use the [method Viewport.get_texture] method on the target viewport.
[b]Note:[/b] A [ViewportTexture] is always local to its scene (see [member Resource.resource_local_to_scene]). If the scene root is not ready, it may return incorrect data (see [signal Node.ready]).
[b]Note:[/b] Instantiating scenes containing a high-resolution [ViewportTexture] may cause noticeable stutter.
+ [b]Note:[/b] When using a [Viewport] with [member Viewport.use_hdr_2d] set to [code]true[/code] the returned texture will be an HDR image encoded in linear space. This may look darker than normal when displayed directly on screen. To convert to gamma space, you can do the following:
+ [codeblock]
+ img.convert(Image.FORMAT_RGBA8)
+ imb.linear_to_srgb()
+ [/codeblock]
</description>
<tutorials>
<link title="GUI in 3D Viewport Demo">https://godotengine.org/asset-library/asset/2807</link>
diff --git a/doc/classes/VisualShaderNodeIntParameter.xml b/doc/classes/VisualShaderNodeIntParameter.xml
index ed72584b1b..ba17da49a8 100644
--- a/doc/classes/VisualShaderNodeIntParameter.xml
+++ b/doc/classes/VisualShaderNodeIntParameter.xml
@@ -15,6 +15,9 @@
<member name="default_value_enabled" type="bool" setter="set_default_value_enabled" getter="is_default_value_enabled" default="false">
If [code]true[/code], the node will have a custom default value.
</member>
+ <member name="enum_names" type="PackedStringArray" setter="set_enum_names" getter="get_enum_names" default="PackedStringArray()">
+ The names used for the enum select in the editor. [member hint] must be [constant HINT_ENUM] for this to take effect.
+ </member>
<member name="hint" type="int" setter="set_hint" getter="get_hint" enum="VisualShaderNodeIntParameter.Hint" default="0">
Range hint of this node. Use it to customize valid parameter range.
</member>
@@ -38,7 +41,10 @@
<constant name="HINT_RANGE_STEP" value="2" enum="Hint">
The parameter's value must be within the specified range, with the given [member step] between values.
</constant>
- <constant name="HINT_MAX" value="3" enum="Hint">
+ <constant name="HINT_ENUM" value="3" enum="Hint">
+ The parameter uses an enum to associate preset values to names in the editor.
+ </constant>
+ <constant name="HINT_MAX" value="4" enum="Hint">
Represents the size of the [enum Hint] enum.
</constant>
</constants>
diff --git a/doc/classes/XRHandTracker.xml b/doc/classes/XRHandTracker.xml
index 636af6625b..79ea237480 100644
--- a/doc/classes/XRHandTracker.xml
+++ b/doc/classes/XRHandTracker.xml
@@ -107,7 +107,10 @@
<constant name="HAND_TRACKING_SOURCE_CONTROLLER" value="2" enum="HandTrackingSource">
The source of hand tracking data is a controller, meaning that joint positions are inferred from controller inputs.
</constant>
- <constant name="HAND_TRACKING_SOURCE_MAX" value="3" enum="HandTrackingSource">
+ <constant name="HAND_TRACKING_SOURCE_NOT_TRACKED" value="3" enum="HandTrackingSource">
+ No hand tracking data is tracked, this either means the hand is obscured, the controller is turned off, or tracking is not supported for the current input type.
+ </constant>
+ <constant name="HAND_TRACKING_SOURCE_MAX" value="4" enum="HandTrackingSource">
Represents the size of the [enum HandTrackingSource] enum.
</constant>
<constant name="HAND_JOINT_PALM" value="0" enum="HandJoint">
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index 941b1a1b28..7a6df3d2a4 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -647,18 +647,17 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou
_record_item_commands(ci, p_to_render_target, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken, r_sdf_used, Point2());
} else {
Point2 start_pos = ci->repeat_size * -(ci->repeat_times / 2);
- Point2 end_pos = ci->repeat_size * ci->repeat_times + ci->repeat_size + start_pos;
- Point2 pos = start_pos;
-
- do {
- do {
- _record_item_commands(ci, p_to_render_target, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken, r_sdf_used, pos);
- pos.y += ci->repeat_size.y;
- } while (pos.y < end_pos.y);
-
- pos.x += ci->repeat_size.x;
- pos.y = start_pos.y;
- } while (pos.x < end_pos.x);
+ Point2 offset;
+
+ int repeat_times_x = ci->repeat_size.x ? ci->repeat_times : 0;
+ int repeat_times_y = ci->repeat_size.y ? ci->repeat_times : 0;
+ for (int ry = 0; ry <= repeat_times_y; ry++) {
+ offset.y = start_pos.y + ry * ci->repeat_size.y;
+ for (int rx = 0; rx <= repeat_times_x; rx++) {
+ offset.x = start_pos.x + rx * ci->repeat_size.x;
+ _record_item_commands(ci, p_to_render_target, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken, r_sdf_used, offset);
+ }
+ }
}
}
@@ -809,7 +808,7 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou
state.last_item_index += index;
}
-void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used, const Point2 &p_offset) {
+void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used, const Point2 &p_repeat_offset) {
RenderingServer::CanvasItemTextureFilter texture_filter = p_item->texture_filter == RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT ? state.default_filter : p_item->texture_filter;
if (texture_filter != state.canvas_instance_batches[state.current_batch_index].filter) {
@@ -826,11 +825,11 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
state.canvas_instance_batches[state.current_batch_index].repeat = texture_repeat;
}
- Transform2D base_transform = p_canvas_transform_inverse * p_item->final_transform;
-
- if (p_offset.x || p_offset.y) {
- base_transform *= Transform2D(0, p_offset / p_item->xform_curr.get_scale()); // TODO: Interpolate or explain why not needed.
+ Transform2D base_transform = p_item->final_transform;
+ if (p_item->repeat_source_item && (p_repeat_offset.x || p_repeat_offset.y)) {
+ base_transform.columns[2] += p_item->repeat_source_item->final_transform.basis_xform(p_repeat_offset);
}
+ base_transform = p_canvas_transform_inverse * base_transform;
Transform2D draw_transform; // Used by transform command
diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h
index 46ed479a3d..027f717eb7 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.h
+++ b/drivers/gles3/rasterizer_canvas_gles3.h
@@ -362,7 +362,7 @@ public:
void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used, RenderingMethod::RenderInfo *r_render_info = nullptr) override;
void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr);
- void _record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch, bool &r_sdf_used, const Point2 &p_offset);
+ void _record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch, bool &r_sdf_used, const Point2 &p_repeat_offset);
void _render_batch(Light *p_lights, uint32_t p_index, RenderingMethod::RenderInfo *r_render_info = nullptr);
bool _bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization);
void _new_batch(bool &r_batch_broken);
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index 84b6ab4bd8..3ed8042f3f 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -777,7 +777,6 @@ void RasterizerSceneGLES3::_draw_sky(RID p_env, const Projection &p_projection,
ERR_FAIL_COND(p_env.is_null());
Sky *sky = sky_owner.get_or_null(environment_get_sky(p_env));
- ERR_FAIL_NULL(sky);
GLES3::SkyMaterialData *material_data = nullptr;
RID sky_material;
@@ -851,6 +850,15 @@ void RasterizerSceneGLES3::_draw_sky(RID p_env, const Projection &p_projection,
material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::SKY_ENERGY_MULTIPLIER, p_sky_energy_multiplier, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::LUMINANCE_MULTIPLIER, p_luminance_multiplier, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
+ Color fog_color = environment_get_fog_light_color(p_env).srgb_to_linear() * environment_get_fog_light_energy(p_env);
+ material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_ENABLED, environment_get_fog_enabled(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
+ material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_AERIAL_PERSPECTIVE, environment_get_fog_aerial_perspective(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
+ material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_LIGHT_COLOR, fog_color, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
+ material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_SUN_SCATTER, environment_get_fog_sun_scatter(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
+ material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_DENSITY, environment_get_fog_density(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
+ material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::FOG_SKY_AFFECT, environment_get_fog_sky_affect(p_env), shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
+ material_storage->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::DIRECTIONAL_LIGHT_COUNT, sky_globals.directional_light_count, shader_data->version, SkyShaderGLES3::MODE_BACKGROUND, spec_constants);
+
if (p_use_multiview) {
glBindBufferBase(GL_UNIFORM_BUFFER, SKY_MULTIVIEW_UNIFORM_LOCATION, scene_state.multiview_buffer);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
@@ -2587,7 +2595,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
scene_state.enable_gl_depth_draw(false);
- if (draw_sky) {
+ if (draw_sky || draw_sky_fog_only) {
RENDER_TIMESTAMP("Render Sky");
scene_state.enable_gl_depth_test(true);
@@ -3202,6 +3210,10 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
if (lm->uses_spherical_harmonics) {
spec_constants |= SceneShaderGLES3::USE_SH_LIGHTMAP;
}
+
+ if (lightmap_bicubic_upscale) {
+ spec_constants |= SceneShaderGLES3::LIGHTMAP_BICUBIC_FILTER;
+ }
} else if (inst->lightmap_sh) {
spec_constants |= SceneShaderGLES3::USE_LIGHTMAP_CAPTURE;
} else {
@@ -3344,6 +3356,11 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
Vector4 uv_scale(inst->lightmap_uv_scale.position.x, inst->lightmap_uv_scale.position.y, inst->lightmap_uv_scale.size.x, inst->lightmap_uv_scale.size.y);
material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::LIGHTMAP_UV_SCALE, uv_scale, shader->version, instance_variant, spec_constants);
+ if (lightmap_bicubic_upscale) {
+ Vector2 light_texture_size(lm->light_texture_size.x, lm->light_texture_size.y);
+ material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::LIGHTMAP_TEXTURE_SIZE, light_texture_size, shader->version, instance_variant, spec_constants);
+ }
+
float exposure_normalization = 1.0;
if (p_render_data->camera_attributes.is_valid()) {
float enf = RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_render_data->camera_attributes);
@@ -4039,6 +4056,10 @@ void RasterizerSceneGLES3::decals_set_filter(RS::DecalFilter p_filter) {
void RasterizerSceneGLES3::light_projectors_set_filter(RS::LightProjectorFilter p_filter) {
}
+void RasterizerSceneGLES3::lightmaps_set_bicubic_filter(bool p_enable) {
+ lightmap_bicubic_upscale = p_enable;
+}
+
RasterizerSceneGLES3::RasterizerSceneGLES3() {
singleton = this;
@@ -4052,6 +4073,7 @@ RasterizerSceneGLES3::RasterizerSceneGLES3() {
positional_soft_shadow_filter_set_quality((RS::ShadowQuality)(int)GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/soft_shadow_filter_quality"));
directional_soft_shadow_filter_set_quality((RS::ShadowQuality)(int)GLOBAL_GET("rendering/lights_and_shadows/directional_shadow/soft_shadow_filter_quality"));
+ lightmaps_set_bicubic_filter(GLOBAL_GET("rendering/lightmapping/lightmap_gi/use_bicubic_filter"));
{
// Setup Lights
diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h
index 4c70c43244..e4af8f99e9 100644
--- a/drivers/gles3/rasterizer_scene_gles3.h
+++ b/drivers/gles3/rasterizer_scene_gles3.h
@@ -680,6 +680,8 @@ protected:
bool glow_bicubic_upscale = false;
RS::EnvironmentSSRRoughnessQuality ssr_roughness_quality = RS::ENV_SSR_ROUGHNESS_QUALITY_LOW;
+ bool lightmap_bicubic_upscale = false;
+
/* Sky */
struct SkyGlobals {
@@ -863,6 +865,7 @@ public:
void decals_set_filter(RS::DecalFilter p_filter) override;
void light_projectors_set_filter(RS::LightProjectorFilter p_filter) override;
+ virtual void lightmaps_set_bicubic_filter(bool p_enable) override;
RasterizerSceneGLES3();
~RasterizerSceneGLES3();
diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl
index ba70de9e34..3e3b4d11f7 100644
--- a/drivers/gles3/shaders/scene.glsl
+++ b/drivers/gles3/shaders/scene.glsl
@@ -36,6 +36,7 @@ ADDITIVE_OMNI = false
ADDITIVE_SPOT = false
RENDER_MATERIAL = false
SECOND_REFLECTION_PROBE = false
+LIGHTMAP_BICUBIC_FILTER = false
#[vertex]
@@ -583,6 +584,8 @@ void main() {
#define SHADER_IS_SRGB true
+#define FLAGS_NON_UNIFORM_SCALE (1 << 4)
+
/* Varyings */
#if defined(COLOR_USED)
@@ -923,6 +926,10 @@ uniform lowp uint lightmap_slice;
uniform highp vec4 lightmap_uv_scale;
uniform float lightmap_exposure_normalization;
+#ifdef LIGHTMAP_BICUBIC_FILTER
+uniform highp vec2 lightmap_texture_size;
+#endif
+
#ifdef USE_SH_LIGHTMAP
uniform mediump mat3 lightmap_normal_xform;
#endif // USE_SH_LIGHTMAP
@@ -955,6 +962,7 @@ ivec2 multiview_uv(ivec2 uv) {
uniform highp mat4 world_transform;
uniform mediump float opaque_prepass_threshold;
+uniform highp uint model_flags;
#if defined(RENDER_MATERIAL)
layout(location = 0) out vec4 albedo_output_buffer;
@@ -1414,6 +1422,67 @@ void reflection_process(samplerCube reflection_map,
#endif // !MODE_RENDER_DEPTH
+#ifdef LIGHTMAP_BICUBIC_FILTER
+// 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));
+}
+
+vec4 textureArray_bicubic(sampler2DArray tex, vec3 uv, vec2 texture_size) {
+ vec2 texel_size = vec2(1.0) / texture_size;
+
+ uv.xy = uv.xy * texture_size + vec2(0.5);
+
+ vec2 iuv = floor(uv.xy);
+ vec2 fuv = fract(uv.xy);
+
+ 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 * texture(tex, vec3(p0, uv.z)) + g1x * texture(tex, vec3(p1, uv.z)))) +
+ (g1(fuv.y) * (g0x * texture(tex, vec3(p2, uv.z)) + g1x * texture(tex, vec3(p3, uv.z))));
+}
+#endif //LIGHTMAP_BICUBIC_FILTER
+
void main() {
//lay out everything, whatever is unused is optimized away anyway
vec3 vertex = vertex_interp;
@@ -1521,6 +1590,13 @@ void main() {
vec3 light_vertex = vertex;
#endif //LIGHT_VERTEX_USED
+ highp mat3 model_normal_matrix;
+ if (bool(model_flags & uint(FLAGS_NON_UNIFORM_SCALE))) {
+ model_normal_matrix = transpose(inverse(mat3(model_matrix)));
+ } else {
+ model_normal_matrix = mat3(model_matrix);
+ }
+
{
#CODE : FRAGMENT
}
@@ -1609,6 +1685,7 @@ void main() {
#ifdef BASE_PASS
/////////////////////// LIGHTING //////////////////////////////
+#ifndef AMBIENT_LIGHT_DISABLED
// IBL precalculations
float ndotv = clamp(dot(normal, view), 0.0, 1.0);
vec3 F = f0 + (max(vec3(1.0 - roughness), f0) - f0) * pow(1.0 - ndotv, 5.0);
@@ -1721,10 +1798,18 @@ void main() {
#ifdef USE_SH_LIGHTMAP
uvw.z *= 4.0; // SH textures use 4 times more data.
+
+#ifdef LIGHTMAP_BICUBIC_FILTER
+ vec3 lm_light_l0 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 0.0), lightmap_texture_size).rgb;
+ vec3 lm_light_l1n1 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), lightmap_texture_size).rgb;
+ vec3 lm_light_l1_0 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), lightmap_texture_size).rgb;
+ vec3 lm_light_l1p1 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), lightmap_texture_size).rgb;
+#else
vec3 lm_light_l0 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
vec3 lm_light_l1n1 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
vec3 lm_light_l1_0 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
vec3 lm_light_l1p1 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+#endif
vec3 n = normalize(lightmap_normal_xform * normal);
@@ -1739,25 +1824,25 @@ void main() {
specular_light += lm_light_l1p1 * 0.32573 * r.x * lightmap_exposure_normalization;
}
#else
+#ifdef LIGHTMAP_BICUBIC_FILTER
+ ambient_light += textureArray_bicubic(lightmap_textures, uvw, lightmap_texture_size).rgb * lightmap_exposure_normalization;
+#else
ambient_light += textureLod(lightmap_textures, uvw, 0.0).rgb * lightmap_exposure_normalization;
#endif
+#endif
}
#endif // USE_LIGHTMAP
#endif // USE_LIGHTMAP_CAPTURE
#endif // !DISABLE_LIGHTMAP
- {
-#if defined(AMBIENT_LIGHT_DISABLED)
- ambient_light = vec3(0.0, 0.0, 0.0);
-#else
- ambient_light *= albedo.rgb;
- ambient_light *= ao;
-#endif // AMBIENT_LIGHT_DISABLED
- }
+ ambient_light *= albedo.rgb;
+ ambient_light *= ao;
+
+#endif // !AMBIENT_LIGHT_DISABLED
// convert ao to direct light ao
ao = mix(1.0, ao, ao_light_affect);
-
+#ifndef AMBIENT_LIGHT_DISABLED
{
#if defined(DIFFUSE_TOON)
//simplify for toon, as
@@ -1779,6 +1864,8 @@ void main() {
#endif
}
+#endif // !AMBIENT_LIGHT_DISABLED
+
#ifndef DISABLE_LIGHT_DIRECTIONAL
for (uint i = uint(0); i < scene_data.directional_light_count; i++) {
#if defined(USE_LIGHTMAP) && !defined(DISABLE_LIGHTMAP)
diff --git a/drivers/gles3/shaders/sky.glsl b/drivers/gles3/shaders/sky.glsl
index 9de65ba960..f734e4b355 100644
--- a/drivers/gles3/shaders/sky.glsl
+++ b/drivers/gles3/shaders/sky.glsl
@@ -108,11 +108,11 @@ uniform float sky_energy_multiplier;
uniform float luminance_multiplier;
uniform float fog_aerial_perspective;
-uniform vec3 fog_light_color;
+uniform vec4 fog_light_color;
uniform float fog_sun_scatter;
uniform bool fog_enabled;
uniform float fog_density;
-uniform float z_far;
+uniform float fog_sky_affect;
uniform uint directional_light_count;
#ifdef USE_MULTIVIEW
@@ -135,6 +135,24 @@ vec3 interleaved_gradient_noise(vec2 pos) {
}
#endif
+#if !defined(DISABLE_FOG)
+vec4 fog_process(vec3 view, vec3 sky_color) {
+ vec3 fog_color = mix(fog_light_color.rgb, sky_color, fog_aerial_perspective);
+
+ if (fog_sun_scatter > 0.001) {
+ vec4 sun_scatter = vec4(0.0);
+ float sun_total = 0.0;
+ for (uint i = 0u; i < directional_light_count; i++) {
+ vec3 light_color = directional_lights.data[i].color_size.xyz * directional_lights.data[i].direction_energy.w;
+ float light_amount = pow(max(dot(view, directional_lights.data[i].direction_energy.xyz), 0.0), 8.0);
+ fog_color += light_color * light_amount * fog_sun_scatter;
+ }
+ }
+
+ return vec4(fog_color, 1.0);
+}
+#endif // !DISABLE_FOG
+
void main() {
vec3 cube_normal;
#ifdef USE_MULTIVIEW
@@ -203,6 +221,21 @@ void main() {
// Convert to Linear for tonemapping so color matches scene shader better
color = srgb_to_linear(color);
+
+#if !defined(DISABLE_FOG) && !defined(USE_CUBEMAP_PASS)
+
+ // Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky.
+ if (fog_enabled) {
+ vec4 fog = fog_process(cube_normal, color.rgb);
+ color.rgb = mix(color.rgb, fog.rgb, fog.a * fog_sky_affect);
+ }
+
+ if (custom_fog.a > 0.0) {
+ color.rgb = mix(color.rgb, custom_fog.rgb, custom_fog.a);
+ }
+
+#endif // DISABLE_FOG
+
color *= exposure;
#ifdef APPLY_TONEMAPPING
color = apply_tonemapping(color, white);
diff --git a/drivers/gles3/storage/light_storage.cpp b/drivers/gles3/storage/light_storage.cpp
index f9547502f4..aab1aadf02 100644
--- a/drivers/gles3/storage/light_storage.cpp
+++ b/drivers/gles3/storage/light_storage.cpp
@@ -1046,6 +1046,9 @@ void LightStorage::lightmap_set_textures(RID p_lightmap, RID p_light, bool p_use
lightmap->light_texture = p_light;
lightmap->uses_spherical_harmonics = p_uses_spherical_haromics;
+ Vector3i light_texture_size = GLES3::TextureStorage::get_singleton()->texture_get_size(lightmap->light_texture);
+ lightmap->light_texture_size = Vector2i(light_texture_size.x, light_texture_size.y);
+
GLuint tex = GLES3::TextureStorage::get_singleton()->texture_get_texid(lightmap->light_texture);
glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h
index b6e64c9492..81e7220439 100644
--- a/drivers/gles3/storage/light_storage.h
+++ b/drivers/gles3/storage/light_storage.h
@@ -180,6 +180,7 @@ struct Lightmap {
bool interior = false;
AABB bounds = AABB(Vector3(), Vector3(1, 1, 1));
float baked_exposure = 1.0;
+ Vector2i light_texture_size;
int32_t array_index = -1; //unassigned
PackedVector3Array points;
PackedColorArray point_sh;
diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp
index 41c23fc3ec..25af7ff691 100644
--- a/drivers/gles3/storage/material_storage.cpp
+++ b/drivers/gles3/storage/material_storage.cpp
@@ -586,11 +586,7 @@ void ShaderData::get_shader_uniform_list(List<PropertyInfo> *p_param_list) const
if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) {
continue;
}
- if (E.value.texture_order >= 0) {
- filtered_uniforms.push_back(Pair<StringName, int>(E.key, E.value.texture_order + 100000));
- } else {
- filtered_uniforms.push_back(Pair<StringName, int>(E.key, E.value.order));
- }
+ filtered_uniforms.push_back(Pair<StringName, int>(E.key, E.value.prop_order));
}
int uniform_count = filtered_uniforms.size();
sorter.sort(filtered_uniforms.ptr(), uniform_count);
@@ -640,7 +636,7 @@ bool ShaderData::is_parameter_texture(const StringName &p_param) const {
return false;
}
- return uniforms[p_param].texture_order >= 0;
+ return uniforms[p_param].is_texture();
}
///////////////////////////////////////////////////////////////////////////
@@ -719,7 +715,7 @@ void MaterialData::update_uniform_buffer(const HashMap<StringName, ShaderLanguag
bool uses_global_buffer = false;
for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : p_uniforms) {
- if (E.value.order < 0) {
+ if (E.value.is_texture()) {
continue; // texture, does not go here
}
diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp
index d8a5b960b8..b55a2e0a8a 100644
--- a/drivers/gles3/storage/mesh_storage.cpp
+++ b/drivers/gles3/storage/mesh_storage.cpp
@@ -301,7 +301,7 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface)
Vector<uint8_t> ir = new_surface.index_data;
wr = wf_indices.ptrw();
- if (new_surface.vertex_count < (1 << 16)) {
+ if (new_surface.vertex_count <= 65536) {
// Read 16 bit indices.
const uint16_t *src_idx = (const uint16_t *)ir.ptr();
for (uint32_t i = 0; i + 5 < wf_index_count; i += 6) {
@@ -1432,15 +1432,17 @@ void MeshStorage::update_mesh_instances() {
/* MULTIMESH API */
-RID MeshStorage::multimesh_allocate() {
+RID MeshStorage::_multimesh_allocate() {
return multimesh_owner.allocate_rid();
}
-void MeshStorage::multimesh_initialize(RID p_rid) {
+void MeshStorage::_multimesh_initialize(RID p_rid) {
multimesh_owner.initialize_rid(p_rid, MultiMesh());
}
-void MeshStorage::multimesh_free(RID p_rid) {
+void MeshStorage::_multimesh_free(RID p_rid) {
+ // Remove from interpolator.
+ _interpolation_data.notify_free_multimesh(p_rid);
_update_dirty_multimeshes();
multimesh_allocate_data(p_rid, 0, RS::MULTIMESH_TRANSFORM_2D);
MultiMesh *multimesh = multimesh_owner.get_or_null(p_rid);
@@ -1448,7 +1450,7 @@ void MeshStorage::multimesh_free(RID p_rid) {
multimesh_owner.free(p_rid);
}
-void MeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
+void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
@@ -1495,13 +1497,13 @@ void MeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS::
multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_MULTIMESH);
}
-int MeshStorage::multimesh_get_instance_count(RID p_multimesh) const {
+int MeshStorage::_multimesh_get_instance_count(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, 0);
return multimesh->instances;
}
-void MeshStorage::multimesh_set_mesh(RID p_multimesh, RID p_mesh) {
+void MeshStorage::_multimesh_set_mesh(RID p_multimesh, RID p_mesh) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
if (multimesh->mesh == p_mesh || p_mesh.is_null()) {
@@ -1651,7 +1653,7 @@ void MeshStorage::_multimesh_re_create_aabb(MultiMesh *multimesh, const float *p
multimesh->aabb = aabb;
}
-void MeshStorage::multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) {
+void MeshStorage::_multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1681,7 +1683,7 @@ void MeshStorage::multimesh_instance_set_transform(RID p_multimesh, int p_index,
_multimesh_mark_dirty(multimesh, p_index, true);
}
-void MeshStorage::multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) {
+void MeshStorage::_multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1707,7 +1709,7 @@ void MeshStorage::multimesh_instance_set_transform_2d(RID p_multimesh, int p_ind
_multimesh_mark_dirty(multimesh, p_index, true);
}
-void MeshStorage::multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) {
+void MeshStorage::_multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1727,7 +1729,7 @@ void MeshStorage::multimesh_instance_set_color(RID p_multimesh, int p_index, con
_multimesh_mark_dirty(multimesh, p_index, false);
}
-void MeshStorage::multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) {
+void MeshStorage::_multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1746,27 +1748,27 @@ void MeshStorage::multimesh_instance_set_custom_data(RID p_multimesh, int p_inde
_multimesh_mark_dirty(multimesh, p_index, false);
}
-RID MeshStorage::multimesh_get_mesh(RID p_multimesh) const {
+RID MeshStorage::_multimesh_get_mesh(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, RID());
return multimesh->mesh;
}
-void MeshStorage::multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) {
+void MeshStorage::_multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
multimesh->custom_aabb = p_aabb;
multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB);
}
-AABB MeshStorage::multimesh_get_custom_aabb(RID p_multimesh) const {
+AABB MeshStorage::_multimesh_get_custom_aabb(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, AABB());
return multimesh->custom_aabb;
}
-AABB MeshStorage::multimesh_get_aabb(RID p_multimesh) const {
+AABB MeshStorage::_multimesh_get_aabb(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, AABB());
if (multimesh->custom_aabb != AABB()) {
@@ -1778,7 +1780,7 @@ AABB MeshStorage::multimesh_get_aabb(RID p_multimesh) const {
return multimesh->aabb;
}
-Transform3D MeshStorage::multimesh_instance_get_transform(RID p_multimesh, int p_index) const {
+Transform3D MeshStorage::_multimesh_instance_get_transform(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Transform3D());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Transform3D());
@@ -1809,7 +1811,7 @@ Transform3D MeshStorage::multimesh_instance_get_transform(RID p_multimesh, int p
return t;
}
-Transform2D MeshStorage::multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const {
+Transform2D MeshStorage::_multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Transform2D());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Transform2D());
@@ -1834,7 +1836,7 @@ Transform2D MeshStorage::multimesh_instance_get_transform_2d(RID p_multimesh, in
return t;
}
-Color MeshStorage::multimesh_instance_get_color(RID p_multimesh, int p_index) const {
+Color MeshStorage::_multimesh_instance_get_color(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Color());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Color());
@@ -1858,7 +1860,7 @@ Color MeshStorage::multimesh_instance_get_color(RID p_multimesh, int p_index) co
return c;
}
-Color MeshStorage::multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const {
+Color MeshStorage::_multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Color());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Color());
@@ -1882,7 +1884,7 @@ Color MeshStorage::multimesh_instance_get_custom_data(RID p_multimesh, int p_ind
return c;
}
-void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
+void MeshStorage::_multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
@@ -1971,7 +1973,7 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b
}
}
-Vector<float> MeshStorage::multimesh_get_buffer(RID p_multimesh) const {
+Vector<float> MeshStorage::_multimesh_get_buffer(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Vector<float>());
Vector<float> ret;
@@ -2043,7 +2045,7 @@ Vector<float> MeshStorage::multimesh_get_buffer(RID p_multimesh) const {
}
}
-void MeshStorage::multimesh_set_visible_instances(RID p_multimesh, int p_visible) {
+void MeshStorage::_multimesh_set_visible_instances(RID p_multimesh, int p_visible) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_COND(p_visible < -1 || p_visible > multimesh->instances);
@@ -2065,12 +2067,19 @@ void MeshStorage::multimesh_set_visible_instances(RID p_multimesh, int p_visible
multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_MULTIMESH_VISIBLE_INSTANCES);
}
-int MeshStorage::multimesh_get_visible_instances(RID p_multimesh) const {
+int MeshStorage::_multimesh_get_visible_instances(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, 0);
return multimesh->visible_instances;
}
+MeshStorage::MultiMeshInterpolator *MeshStorage::_multimesh_get_interpolator(RID p_multimesh) const {
+ MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
+ ERR_FAIL_NULL_V_MSG(multimesh, nullptr, "Multimesh not found: " + itos(p_multimesh.get_id()));
+
+ return &multimesh->interpolator;
+}
+
void MeshStorage::_update_dirty_multimeshes() {
while (multimesh_dirty_list) {
MultiMesh *multimesh = multimesh_dirty_list;
diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h
index d246e7725c..a2edbb9c48 100644
--- a/drivers/gles3/storage/mesh_storage.h
+++ b/drivers/gles3/storage/mesh_storage.h
@@ -205,6 +205,8 @@ struct MultiMesh {
bool dirty = false;
MultiMesh *dirty_list = nullptr;
+ RendererMeshStorage::MultiMeshInterpolator interpolator;
+
Dependency dependency;
};
@@ -493,32 +495,34 @@ public:
MultiMesh *get_multimesh(RID p_rid) { return multimesh_owner.get_or_null(p_rid); };
bool owns_multimesh(RID p_rid) { return multimesh_owner.owns(p_rid); };
- virtual RID multimesh_allocate() override;
- virtual void multimesh_initialize(RID p_rid) override;
- virtual void multimesh_free(RID p_rid) override;
- virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override;
- virtual int multimesh_get_instance_count(RID p_multimesh) const override;
-
- virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh) override;
- virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) override;
- virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) override;
- virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override;
- virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override;
-
- virtual RID multimesh_get_mesh(RID p_multimesh) const override;
- virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override;
- virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const override;
- virtual AABB multimesh_get_aabb(RID p_multimesh) const override;
-
- virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const override;
- virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override;
- virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const override;
- virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override;
- virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override;
- virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const override;
-
- virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) override;
- virtual int multimesh_get_visible_instances(RID p_multimesh) const override;
+ virtual RID _multimesh_allocate() override;
+ virtual void _multimesh_initialize(RID p_rid) override;
+ virtual void _multimesh_free(RID p_rid) override;
+ virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override;
+ virtual int _multimesh_get_instance_count(RID p_multimesh) const override;
+
+ virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) override;
+ virtual void _multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) override;
+ virtual void _multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) override;
+ virtual void _multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override;
+ virtual void _multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override;
+
+ virtual RID _multimesh_get_mesh(RID p_multimesh) const override;
+ virtual void _multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override;
+ virtual AABB _multimesh_get_custom_aabb(RID p_multimesh) const override;
+ virtual AABB _multimesh_get_aabb(RID p_multimesh) const override;
+
+ virtual Transform3D _multimesh_instance_get_transform(RID p_multimesh, int p_index) const override;
+ virtual Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override;
+ virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const override;
+ virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override;
+ virtual void _multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override;
+ virtual Vector<float> _multimesh_get_buffer(RID p_multimesh) const override;
+
+ virtual void _multimesh_set_visible_instances(RID p_multimesh, int p_visible) override;
+ virtual int _multimesh_get_visible_instances(RID p_multimesh) const override;
+
+ virtual MultiMeshInterpolator *_multimesh_get_interpolator(RID p_multimesh) const override;
void _update_dirty_multimeshes();
diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp
index 3b1373c928..8251c8f52e 100644
--- a/drivers/gles3/storage/texture_storage.cpp
+++ b/drivers/gles3/storage/texture_storage.cpp
@@ -1680,6 +1680,14 @@ uint32_t TextureStorage::texture_get_texid(RID p_texture) const {
return texture->tex_id;
}
+Vector3i TextureStorage::texture_get_size(RID p_texture) const {
+ Texture *texture = texture_owner.get_or_null(p_texture);
+
+ ERR_FAIL_NULL_V(texture, Vector3i(0, 0, 0));
+
+ return Vector3i(texture->width, texture->height, texture->depth);
+}
+
uint32_t TextureStorage::texture_get_width(RID p_texture) const {
Texture *texture = texture_owner.get_or_null(p_texture);
diff --git a/drivers/gles3/storage/texture_storage.h b/drivers/gles3/storage/texture_storage.h
index 1b83efee32..5569abcc73 100644
--- a/drivers/gles3/storage/texture_storage.h
+++ b/drivers/gles3/storage/texture_storage.h
@@ -553,6 +553,7 @@ public:
void texture_set_data(RID p_texture, const Ref<Image> &p_image, int p_layer = 0);
virtual Image::Format texture_get_format(RID p_texture) const override;
uint32_t texture_get_texid(RID p_texture) const;
+ Vector3i texture_get_size(RID p_texture) const;
uint32_t texture_get_width(RID p_texture) const;
uint32_t texture_get_height(RID p_texture) const;
uint32_t texture_get_depth(RID p_texture) const;
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
index 48b9e01fd8..f74bdd8bb9 100644
--- a/editor/animation_bezier_editor.cpp
+++ b/editor/animation_bezier_editor.cpp
@@ -1083,7 +1083,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
if (I.key == REMOVE_ICON) {
if (!read_only) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- undo_redo->create_action("Remove Bezier Track");
+ undo_redo->create_action("Remove Bezier Track", UndoRedo::MERGE_DISABLE, animation.ptr());
undo_redo->add_do_method(this, "_update_locked_tracks_after", track);
undo_redo->add_do_method(this, "_update_hidden_tracks_after", track);
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index d24b1edd70..8664c167b5 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -994,6 +994,9 @@ Ref<Texture2D> CodeTextEditor::_get_completion_icon(const ScriptLanguage::CodeCo
tex = get_editor_theme_icon(p_option.display);
} else {
tex = EditorNode::get_singleton()->get_class_icon(p_option.display);
+ if (!tex.is_valid()) {
+ tex = get_editor_theme_icon(SNAME("Object"));
+ }
}
} break;
case ScriptLanguage::CODE_COMPLETION_KIND_ENUM:
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index 02a95fd836..f75e438582 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -386,6 +386,8 @@ void EditorFileSystem::_scan_filesystem() {
// On the first scan, the first_scan_root_dir is created in _first_scan_filesystem.
if (first_scan) {
sd = first_scan_root_dir;
+ // Will be updated on scan.
+ ResourceUID::get_singleton()->clear();
} else {
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
sd = memnew(ScannedDirectory);
@@ -3061,7 +3063,6 @@ EditorFileSystem::EditorFileSystem() {
using_fat32_or_exfat = (da->get_filesystem_type() == "FAT32" || da->get_filesystem_type() == "exFAT");
scan_total = 0;
- callable_mp(ResourceUID::get_singleton(), &ResourceUID::clear).call_deferred(); // Will be updated on scan.
ResourceSaver::set_get_resource_id_for_path(_resource_saver_get_resource_id_for_path);
}
diff --git a/editor/editor_folding.cpp b/editor/editor_folding.cpp
index ae065aebf8..18f5610655 100644
--- a/editor/editor_folding.cpp
+++ b/editor/editor_folding.cpp
@@ -93,7 +93,7 @@ void EditorFolding::_fill_folds(const Node *p_root, const Node *p_node, Array &p
if (!p_node->get_owner()) {
return; //not owned, bye
}
- if (p_node->get_owner() != p_root && !p_root->is_editable_instance(p_node)) {
+ if (p_node->get_owner() != p_root && !p_root->is_editable_instance(p_node->get_owner())) {
return;
}
}
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index ae51faf3b9..c7c00e0496 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -455,6 +455,7 @@ void EditorNode::_update_from_settings() {
RS::get_singleton()->decals_set_filter(RS::DecalFilter(int(GLOBAL_GET("rendering/textures/decals/filter"))));
RS::get_singleton()->light_projectors_set_filter(RS::LightProjectorFilter(int(GLOBAL_GET("rendering/textures/light_projectors/filter"))));
+ RS::get_singleton()->lightmaps_set_bicubic_filter(GLOBAL_GET("rendering/lightmapping/lightmap_gi/use_bicubic_filter"));
SceneTree *tree = get_tree();
tree->set_debug_collisions_color(GLOBAL_GET("debug/shapes/collision/shape_color"));
@@ -696,7 +697,10 @@ void EditorNode::_notification(int p_what) {
callable_mp(this, &EditorNode::_begin_first_scan).call_deferred();
- DisplayServer::get_singleton()->set_system_theme_change_callback(callable_mp(this, &EditorNode::_update_theme).bind(false));
+ last_dark_mode_state = DisplayServer::get_singleton()->is_dark_mode();
+ last_system_accent_color = DisplayServer::get_singleton()->get_accent_color();
+ last_system_base_color = DisplayServer::get_singleton()->get_base_color();
+ DisplayServer::get_singleton()->set_system_theme_change_callback(callable_mp(this, &EditorNode::_check_system_theme_changed));
/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
} break;
@@ -5378,7 +5382,7 @@ bool EditorNode::ensure_main_scene(bool p_from_native) {
if (main_scene.is_empty()) {
current_menu_option = -1;
- pick_main_scene->set_text(TTR("No main scene has ever been defined, select one?\nYou can change it later in \"Project Settings\" under the 'application' category."));
+ pick_main_scene->set_text(TTR("No main scene has ever been defined. Select one?\nYou can change it later in \"Project Settings\" under the 'application' category."));
pick_main_scene->popup_centered();
if (editor_data.get_edited_scene_root()) {
@@ -5393,14 +5397,14 @@ bool EditorNode::ensure_main_scene(bool p_from_native) {
if (!FileAccess::exists(main_scene)) {
current_menu_option = -1;
- pick_main_scene->set_text(vformat(TTR("Selected scene '%s' does not exist, select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene));
+ pick_main_scene->set_text(vformat(TTR("Selected scene '%s' does not exist. Select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene));
pick_main_scene->popup_centered();
return false;
}
if (ResourceLoader::get_resource_type(main_scene) != "PackedScene") {
current_menu_option = -1;
- pick_main_scene->set_text(vformat(TTR("Selected scene '%s' is not a scene file, select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene));
+ pick_main_scene->set_text(vformat(TTR("Selected scene '%s' is not a scene file. Select a valid one?\nYou can change it later in \"Project Settings\" under the 'application' category."), main_scene));
pick_main_scene->popup_centered();
return false;
}
diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp
index 4cd44e3020..8935b9ad8a 100644
--- a/editor/editor_resource_picker.cpp
+++ b/editor/editor_resource_picker.cpp
@@ -618,9 +618,9 @@ void EditorResourcePicker::_ensure_allowed_types() const {
const String base = allowed_types[i].strip_edges();
if (base == "BaseMaterial3D") {
allowed_types_with_convert.insert("Texture2D");
- } else if (base == "ShaderMaterial") {
+ } else if (ClassDB::is_parent_class("ShaderMaterial", base)) {
allowed_types_with_convert.insert("Shader");
- } else if (base == "Texture2D") {
+ } else if (ClassDB::is_parent_class("ImageTexture", base)) {
allowed_types_with_convert.insert("Image");
}
}
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index c3a939c007..b9d530353c 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -65,7 +65,7 @@ bool EditorSettings::_set(const StringName &p_name, const Variant &p_value) {
_THREAD_SAFE_METHOD_
bool changed = _set_only(p_name, p_value);
- if (changed) {
+ if (changed && initialized) {
changed_settings.insert(p_name);
emit_signal(SNAME("settings_changed"));
}
@@ -330,6 +330,10 @@ bool EditorSettings::has_default_value(const String &p_setting) const {
return props[p_setting].has_default_value;
}
+void EditorSettings::_set_initialized() {
+ initialized = true;
+}
+
void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
_THREAD_SAFE_METHOD_
// Sets up the editor setting with a default value and hint PropertyInfo.
@@ -1927,7 +1931,5 @@ EditorSettings::EditorSettings() {
last_order = 0;
_load_defaults();
-}
-
-EditorSettings::~EditorSettings() {
+ callable_mp(this, &EditorSettings::_set_initialized).call_deferred();
}
diff --git a/editor/editor_settings.h b/editor/editor_settings.h
index 62ac0c60f3..6338f9105c 100644
--- a/editor/editor_settings.h
+++ b/editor/editor_settings.h
@@ -98,6 +98,7 @@ private:
bool save_changed_setting = true;
bool optimize_save = true; //do not save stuff that came from config but was not set from engine
+ bool initialized = false;
bool _set(const StringName &p_name, const Variant &p_value);
bool _set_only(const StringName &p_name, const Variant &p_value);
@@ -108,6 +109,7 @@ private:
bool _property_can_revert(const StringName &p_name) const;
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
+ void _set_initialized();
void _load_defaults(Ref<ConfigFile> p_extra_config = Ref<ConfigFile>());
void _load_godot2_text_editor_theme();
void _load_default_visual_shader_editor_theme();
@@ -196,7 +198,6 @@ public:
#endif
EditorSettings();
- ~EditorSettings();
};
//not a macro any longer
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index b780d42119..a9de8e3bc5 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -685,7 +685,15 @@ void FileSystemDock::_tree_multi_selected(Object *p_item, int p_column, bool p_s
}
Vector<String> FileSystemDock::get_selected_paths() const {
- return _tree_get_selected(false);
+ if (display_mode == DISPLAY_MODE_TREE_ONLY) {
+ return _tree_get_selected(false);
+ } else {
+ Vector<String> selected = _file_list_get_selected();
+ if (selected.is_empty()) {
+ selected.push_back(get_current_directory());
+ }
+ return selected;
+ }
}
String FileSystemDock::get_current_path() const {
@@ -2050,6 +2058,15 @@ Vector<String> FileSystemDock::_tree_get_selected(bool remove_self_inclusion, bo
return selected_strings;
}
+Vector<String> FileSystemDock::_file_list_get_selected() const {
+ Vector<String> selected;
+
+ for (int idx : files->get_selected_items()) {
+ selected.push_back(files->get_item_metadata(idx));
+ }
+ return selected;
+}
+
Vector<String> FileSystemDock::_remove_self_included_paths(Vector<String> selected_strings) {
// Remove paths or files that are included into another.
if (selected_strings.size() > 1) {
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index 959ace8eba..2f54cb91db 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -359,6 +359,7 @@ private:
void _update_display_mode(bool p_force = false);
Vector<String> _tree_get_selected(bool remove_self_inclusion = true, bool p_include_unselected_cursor = false) const;
+ Vector<String> _file_list_get_selected() const;
bool _is_file_type_disabled_by_feature_profile(const StringName &p_class);
diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp
index c6cc0e97dd..87d8ddad09 100644
--- a/editor/gui/scene_tree_editor.cpp
+++ b/editor/gui/scene_tree_editor.cpp
@@ -1000,6 +1000,7 @@ void SceneTreeEditor::set_selected(Node *p_node, bool p_emit_selected) {
TreeItem *item = p_node ? _find(tree->get_root(), p_node->get_path()) : nullptr;
if (item) {
+ selected = p_node;
if (auto_expand_selected) {
// Make visible when it's collapsed.
TreeItem *node = item->get_parent();
@@ -1009,8 +1010,24 @@ void SceneTreeEditor::set_selected(Node *p_node, bool p_emit_selected) {
}
item->select(0);
item->set_as_cursor(0);
- selected = p_node;
tree->ensure_cursor_is_visible();
+ } else {
+ // Ensure the node is selected and visible for the user if the node
+ // is not collapsed.
+ bool collapsed = false;
+ TreeItem *node = item;
+ while (node && node != tree->get_root()) {
+ if (node->is_collapsed()) {
+ collapsed = true;
+ break;
+ }
+ node = node->get_parent();
+ }
+ if (!collapsed) {
+ item->select(0);
+ item->set_as_cursor(0);
+ tree->ensure_cursor_is_visible();
+ }
}
} else {
if (!p_node) {
diff --git a/editor/icons/AudioStreamPlayer.svg b/editor/icons/AudioStreamPlayer.svg
index a3c4ad8e35..561e1c3bb0 100644
--- a/editor/icons/AudioStreamPlayer.svg
+++ b/editor/icons/AudioStreamPlayer.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path fill="#e0e0e0" stroke="#e0e0e0" stroke-linejoin="round" stroke-width=".176" d="M1.382.335.777.858H.204v.673h.564l.614.531Z"/><path fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-width=".176" d="M1.718.572a1.06 1.06 0 0 1 0 1.256M1.947.343c.402.5.402 1.213 0 1.714"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path fill="#e0e0e0" d="M1.252.15a.1.1 0 0 0-.082.03L.6.75H.318C.225.75.15.817.15.9v.6c0 .083.075.15.168.15H.6l.57.57c.066.067.18.02.18-.074V.256A.106.106 0 0 0 1.252.15" paint-order="markers stroke fill"/><path fill="none" stroke="#e0e0e0" stroke-linecap="round" stroke-linejoin="round" stroke-width=".165" d="M1.575.675c.45.525 0 1.05 0 1.05m.3-1.35c.675.825 0 1.65 0 1.65" paint-order="markers stroke fill"/></svg> \ No newline at end of file
diff --git a/editor/icons/AudioStreamPlayer2D.svg b/editor/icons/AudioStreamPlayer2D.svg
index fa60e30238..7f557b2cfa 100644
--- a/editor/icons/AudioStreamPlayer2D.svg
+++ b/editor/icons/AudioStreamPlayer2D.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path fill="#8da5f3" stroke="#8da5f3" stroke-linejoin="round" stroke-width=".176" d="M1.382.335.777.858H.204v.673h.564l.614.531Z"/><path fill="none" stroke="#8da5f3" stroke-linecap="round" stroke-width=".176" d="M1.718.572a1.06 1.06 0 0 1 0 1.256M1.947.343c.402.5.402 1.213 0 1.714"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path fill="#8da5f3" d="M1.252.15a.1.1 0 0 0-.082.03L.6.75H.318C.225.75.15.817.15.9v.6c0 .083.075.15.168.15H.6l.57.57c.066.067.18.02.18-.074V.256A.106.106 0 0 0 1.252.15" paint-order="markers stroke fill"/><path fill="none" stroke="#8da5f3" stroke-linecap="round" stroke-linejoin="round" stroke-width=".165" d="M1.575.675c.45.525 0 1.05 0 1.05m.3-1.35c.675.825 0 1.65 0 1.65" paint-order="markers stroke fill"/></svg> \ No newline at end of file
diff --git a/editor/icons/AudioStreamPlayer3D.svg b/editor/icons/AudioStreamPlayer3D.svg
index f6be929e61..5f07d68eec 100644
--- a/editor/icons/AudioStreamPlayer3D.svg
+++ b/editor/icons/AudioStreamPlayer3D.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path fill="#fc7f7f" stroke="#fc7f7f" stroke-linejoin="round" stroke-width=".176" d="M1.382.335.777.858H.204v.673h.564l.614.531Z"/><path fill="none" stroke="#fc7f7f" stroke-linecap="round" stroke-width=".176" d="M1.718.572a1.06 1.06 0 0 1 0 1.256M1.947.343c.402.5.402 1.213 0 1.714"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16" version="1.0" viewBox="0 0 2.4 2.4"><path fill="#fc7f7f" d="M1.252.15a.1.1 0 0 0-.082.03L.6.75H.318C.225.75.15.817.15.9v.6c0 .083.075.15.168.15H.6l.57.57c.066.067.18.02.18-.074V.256A.106.106 0 0 0 1.252.15" paint-order="markers stroke fill"/><path fill="none" stroke="#fc7f7f" stroke-linecap="round" stroke-linejoin="round" stroke-width=".165" d="M1.575.675c.45.525 0 1.05 0 1.05m.3-1.35c.675.825 0 1.65 0 1.65" paint-order="markers stroke fill"/></svg> \ No newline at end of file
diff --git a/editor/plugins/gizmos/camera_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/camera_3d_gizmo_plugin.cpp
index 21ad8f021c..8d0222215c 100644
--- a/editor/plugins/gizmos/camera_3d_gizmo_plugin.cpp
+++ b/editor/plugins/gizmos/camera_3d_gizmo_plugin.cpp
@@ -113,10 +113,10 @@ void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id,
camera->set("fov", CLAMP(a * 2.0, 1, 179));
} else {
Camera3D::KeepAspect aspect = camera->get_keep_aspect_mode();
- Vector3 far = aspect == Camera3D::KeepAspect::KEEP_WIDTH ? Vector3(4096, 0, -1) : Vector3(0, 4096, -1);
+ Vector3 camera_far = aspect == Camera3D::KeepAspect::KEEP_WIDTH ? Vector3(4096, 0, -1) : Vector3(0, 4096, -1);
Vector3 ra, rb;
- Geometry3D::get_closest_points_between_segments(Vector3(0, 0, -1), far, s[0], s[1], ra, rb);
+ Geometry3D::get_closest_points_between_segments(Vector3(0, 0, -1), camera_far, s[0], s[1], ra, rb);
float d = aspect == Camera3D::KeepAspect::KEEP_WIDTH ? ra.x * 2 : ra.y * 2;
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp
index de56767929..67d5e44ce5 100644
--- a/editor/plugins/node_3d_editor_gizmos.cpp
+++ b/editor/plugins/node_3d_editor_gizmos.cpp
@@ -81,6 +81,8 @@ void EditorNode3DGizmo::redraw() {
gizmo_plugin->redraw(this);
}
+ _update_bvh();
+
if (Node3DEditor::get_singleton()->is_current_selected_gizmo(this)) {
Node3DEditor::get_singleton()->update_transform_gizmo();
}
@@ -244,6 +246,32 @@ void EditorNode3DGizmo::add_mesh(const Ref<Mesh> &p_mesh, const Ref<Material> &p
instances.push_back(ins);
}
+void EditorNode3DGizmo::_update_bvh() {
+ ERR_FAIL_NULL(spatial_node);
+
+ Transform3D transform = spatial_node->get_global_transform();
+
+ float effective_icon_size = selectable_icon_size > 0.0f ? selectable_icon_size : 0.0f;
+ Vector3 icon_size_vector3 = Vector3(effective_icon_size, effective_icon_size, effective_icon_size);
+ AABB aabb(spatial_node->get_position() - icon_size_vector3 * 100.0f, icon_size_vector3 * 200.0f);
+
+ for (const Vector3 &segment_end : collision_segments) {
+ aabb.expand_to(transform.xform(segment_end));
+ }
+
+ if (collision_mesh.is_valid()) {
+ for (const Face3 &face : collision_mesh->get_faces()) {
+ aabb.expand_to(transform.xform(face.vertex[0]));
+ aabb.expand_to(transform.xform(face.vertex[1]));
+ aabb.expand_to(transform.xform(face.vertex[2]));
+ }
+ }
+
+ Node3DEditor::get_singleton()->update_gizmo_bvh_node(
+ bvh_node_id,
+ aabb);
+}
+
void EditorNode3DGizmo::add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard, const Color &p_modulate) {
add_vertices(p_lines, p_material, Mesh::PRIMITIVE_LINES, p_billboard, p_modulate);
}
@@ -765,6 +793,10 @@ void EditorNode3DGizmo::create() {
instances.write[i].create_instance(spatial_node, hidden);
}
+ bvh_node_id = Node3DEditor::get_singleton()->insert_gizmo_bvh_node(
+ spatial_node,
+ AABB(spatial_node->get_position(), Vector3(0, 0, 0)));
+
transform();
}
@@ -774,6 +806,8 @@ void EditorNode3DGizmo::transform() {
for (int i = 0; i < instances.size(); i++) {
RS::get_singleton()->instance_set_transform(instances[i].instance, spatial_node->get_global_transform() * instances[i].xform);
}
+
+ _update_bvh();
}
void EditorNode3DGizmo::free() {
@@ -790,6 +824,9 @@ void EditorNode3DGizmo::free() {
clear();
+ Node3DEditor::get_singleton()->remove_gizmo_bvh_node(bvh_node_id);
+ bvh_node_id = DynamicBVH::ID();
+
valid = false;
}
diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h
index d7c368d5d0..c4b275032a 100644
--- a/editor/plugins/node_3d_editor_gizmos.h
+++ b/editor/plugins/node_3d_editor_gizmos.h
@@ -31,6 +31,7 @@
#ifndef NODE_3D_EDITOR_GIZMOS_H
#define NODE_3D_EDITOR_GIZMOS_H
+#include "core/math/dynamic_bvh.h"
#include "core/templates/hash_map.h"
#include "core/templates/local_vector.h"
#include "scene/3d/camera_3d.h"
@@ -72,8 +73,12 @@ class EditorNode3DGizmo : public Node3DGizmo {
Vector<Instance> instances;
Node3D *spatial_node = nullptr;
+ DynamicBVH::ID bvh_node_id;
+
void _set_node_3d(Node *p_node) { set_node_3d(Object::cast_to<Node3D>(p_node)); }
+ void _update_bvh();
+
protected:
static void _bind_methods();
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 22300fe851..f5b0a3b51e 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -800,7 +800,6 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) const {
RS::get_singleton()->sdfgi_set_debug_probe_select(pos, ray);
}
- Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, pos + ray * camera->get_far(), get_tree()->get_root()->get_world_3d()->get_scenario());
HashSet<Ref<EditorNode3DGizmo>> found_gizmos;
Node *edited_scene = get_tree()->get_edited_scene_root();
@@ -808,9 +807,9 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) const {
Node *item = nullptr;
float closest_dist = 1e20;
- for (int i = 0; i < instances.size(); i++) {
- Node3D *spat = Object::cast_to<Node3D>(ObjectDB::get_instance(instances[i]));
+ Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_ray_query(pos, pos + ray * camera->get_far());
+ for (Node3D *spat : nodes_with_gizmos) {
if (!spat) {
continue;
}
@@ -863,12 +862,11 @@ void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, Vector<_RayRe
Vector3 ray = get_ray(p_pos);
Vector3 pos = get_ray_pos(p_pos);
- Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, pos + ray * camera->get_far(), get_tree()->get_root()->get_world_3d()->get_scenario());
- HashSet<Node3D *> found_nodes;
+ Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_ray_query(pos, pos + ray * camera->get_far());
- for (int i = 0; i < instances.size(); i++) {
- Node3D *spat = Object::cast_to<Node3D>(ObjectDB::get_instance(instances[i]));
+ HashSet<Node3D *> found_nodes;
+ for (Node3D *spat : nodes_with_gizmos) {
if (!spat) {
continue;
}
@@ -1046,7 +1044,7 @@ void Node3DEditorViewport::_select_region() {
_clear_selected();
}
- Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_convex(frustum, get_tree()->get_root()->get_world_3d()->get_scenario());
+ Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_frustum_query(frustum);
HashSet<Node3D *> found_nodes;
Vector<Node *> selected;
@@ -1055,8 +1053,7 @@ void Node3DEditorViewport::_select_region() {
return;
}
- for (int i = 0; i < instances.size(); i++) {
- Node3D *sp = Object::cast_to<Node3D>(ObjectDB::get_instance(instances[i]));
+ for (Node3D *sp : nodes_with_gizmos) {
if (!sp || _is_node_locked(sp)) {
continue;
}
@@ -1918,12 +1915,17 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
if (after != EditorPlugin::AFTER_GUI_INPUT_CUSTOM) {
- //clicking is always deferred to either move or release
- clicked = _select_ray(b->get_position());
+ // Single item selection.
+ Vector<_RayResult> selection;
+ _find_items_at_pos(b->get_position(), selection, false);
+ if (!selection.is_empty()) {
+ clicked = selection[0].item->get_instance_id();
+ }
+
selection_in_progress = true;
if (clicked.is_null()) {
- //default to regionselect
+ // Default to region select.
cursor.region_select = true;
cursor.region_begin = b->get_position();
cursor.region_end = b->get_position();
@@ -3114,6 +3116,7 @@ void Node3DEditorViewport::_notification(int p_what) {
case NOTIFICATION_DRAG_END: {
// Clear preview material when dropped outside applicable object.
if (spatial_editor->get_preview_material().is_valid() && !is_drag_successful()) {
+ _reset_preview_material();
_remove_preview_material();
} else {
_remove_preview_node();
@@ -9230,6 +9233,49 @@ void Node3DEditor::remove_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) {
_update_gizmos_menu();
}
+DynamicBVH::ID Node3DEditor::insert_gizmo_bvh_node(Node3D *p_node, const AABB &p_aabb) {
+ return gizmo_bvh.insert(p_aabb, p_node);
+}
+
+void Node3DEditor::update_gizmo_bvh_node(DynamicBVH::ID p_id, const AABB &p_aabb) {
+ gizmo_bvh.update(p_id, p_aabb);
+ gizmo_bvh.optimize_incremental(1);
+}
+
+void Node3DEditor::remove_gizmo_bvh_node(DynamicBVH::ID p_id) {
+ gizmo_bvh.remove(p_id);
+}
+
+Vector<Node3D *> Node3DEditor::gizmo_bvh_ray_query(const Vector3 &p_ray_start, const Vector3 &p_ray_end) {
+ struct Result {
+ Vector<Node3D *> nodes;
+ bool operator()(void *p_data) {
+ nodes.append((Node3D *)p_data);
+ return false;
+ }
+ } result;
+
+ gizmo_bvh.ray_query(p_ray_start, p_ray_end, result);
+
+ return result.nodes;
+}
+
+Vector<Node3D *> Node3DEditor::gizmo_bvh_frustum_query(const Vector<Plane> &p_frustum) {
+ Vector<Vector3> points = Geometry3D::compute_convex_mesh_points(&p_frustum[0], p_frustum.size());
+
+ struct Result {
+ Vector<Node3D *> nodes;
+ bool operator()(void *p_data) {
+ nodes.append((Node3D *)p_data);
+ return false;
+ }
+ } result;
+
+ gizmo_bvh.convex_query(p_frustum.ptr(), p_frustum.size(), points.ptr(), points.size(), result);
+
+ return result.nodes;
+}
+
Node3DEditorPlugin::Node3DEditorPlugin() {
spatial_editor = memnew(Node3DEditor);
spatial_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index 5bd14748c0..9e7d46c5e8 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -31,6 +31,7 @@
#ifndef NODE_3D_EDITOR_PLUGIN_H
#define NODE_3D_EDITOR_PLUGIN_H
+#include "core/math/dynamic_bvh.h"
#include "editor/plugins/editor_plugin.h"
#include "editor/plugins/node_3d_editor_gizmos.h"
#include "editor/themes/editor_scale.h"
@@ -629,6 +630,8 @@ private:
int current_hover_gizmo_handle;
bool current_hover_gizmo_handle_secondary;
+ DynamicBVH gizmo_bvh;
+
real_t snap_translate_value;
real_t snap_rotate_value;
real_t snap_scale_value;
@@ -933,6 +936,12 @@ public:
void add_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin);
void remove_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin);
+ DynamicBVH::ID insert_gizmo_bvh_node(Node3D *p_node, const AABB &p_aabb);
+ void update_gizmo_bvh_node(DynamicBVH::ID p_id, const AABB &p_aabb);
+ void remove_gizmo_bvh_node(DynamicBVH::ID p_id);
+ Vector<Node3D *> gizmo_bvh_ray_query(const Vector3 &p_ray_start, const Vector3 &p_ray_end);
+ Vector<Node3D *> gizmo_bvh_frustum_query(const Vector<Plane> &p_frustum);
+
void edit(Node3D *p_spatial);
void clear();
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index b2669cf1af..d50f5e51d5 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -115,8 +115,8 @@ void VSGraphNode::_draw_port(int p_slot_index, Point2i p_pos, bool p_left, const
icon_offset = -port_icon->get_size() * 0.5;
// Draw "shadow"/outline in the connection rim color.
- draw_texture_rect(port_icon, Rect2(p_pos + icon_offset - Size2(2, 2), port_icon->get_size() + Size2(4, 4)), false, p_rim_color);
- draw_texture(port_icon, p_pos + icon_offset, p_color);
+ draw_texture_rect(port_icon, Rect2(p_pos + (icon_offset - Size2(2, 2)) * EDSCALE, (port_icon->get_size() + Size2(4, 4)) * EDSCALE), false, p_rim_color);
+ draw_texture_rect(port_icon, Rect2(p_pos + icon_offset * EDSCALE, port_icon->get_size() * EDSCALE), false, p_color);
}
void VSGraphNode::draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color) {
diff --git a/main/main.cpp b/main/main.cpp
index bdae1bb1b0..9ee88af60e 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -2512,7 +2512,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF_BASIC("xr/openxr/startup_alert", true);
// OpenXR project extensions settings.
- GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking", true);
+ GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking", false);
+ GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking_unobstructed_data_source", false); // XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT
+ GLOBAL_DEF_BASIC("xr/openxr/extensions/hand_tracking_controller_data_source", false); // XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT
GLOBAL_DEF_RST_BASIC("xr/openxr/extensions/hand_interaction_profile", false);
GLOBAL_DEF_BASIC("xr/openxr/extensions/eye_gaze_interaction", false);
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 296cda627a..8777651545 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -460,27 +460,31 @@ void CSGShape3D::_update_shape() {
_update_collision_faces();
}
-void CSGShape3D::_update_collision_faces() {
- if (use_collision && is_root_shape() && root_collision_shape.is_valid()) {
- CSGBrush *n = _get_brush();
- ERR_FAIL_NULL_MSG(n, "Cannot get CSGBrush.");
- Vector<Vector3> physics_faces;
- physics_faces.resize(n->faces.size() * 3);
- Vector3 *physicsw = physics_faces.ptrw();
-
- for (int i = 0; i < n->faces.size(); i++) {
- int order[3] = { 0, 1, 2 };
+Vector<Vector3> CSGShape3D::_get_brush_collision_faces() {
+ Vector<Vector3> collision_faces;
+ CSGBrush *n = _get_brush();
+ ERR_FAIL_NULL_V_MSG(n, collision_faces, "Cannot get CSGBrush.");
+ collision_faces.resize(n->faces.size() * 3);
+ Vector3 *collision_faces_ptrw = collision_faces.ptrw();
- if (n->faces[i].invert) {
- SWAP(order[1], order[2]);
- }
+ for (int i = 0; i < n->faces.size(); i++) {
+ int order[3] = { 0, 1, 2 };
- physicsw[i * 3 + 0] = n->faces[i].vertices[order[0]];
- physicsw[i * 3 + 1] = n->faces[i].vertices[order[1]];
- physicsw[i * 3 + 2] = n->faces[i].vertices[order[2]];
+ if (n->faces[i].invert) {
+ SWAP(order[1], order[2]);
}
- root_collision_shape->set_faces(physics_faces);
+ collision_faces_ptrw[i * 3 + 0] = n->faces[i].vertices[order[0]];
+ collision_faces_ptrw[i * 3 + 1] = n->faces[i].vertices[order[1]];
+ collision_faces_ptrw[i * 3 + 2] = n->faces[i].vertices[order[2]];
+ }
+
+ return collision_faces;
+}
+
+void CSGShape3D::_update_collision_faces() {
+ if (use_collision && is_root_shape() && root_collision_shape.is_valid()) {
+ root_collision_shape->set_faces(_get_brush_collision_faces());
if (_is_debug_collision_shape_visible()) {
_update_debug_collision_shape();
@@ -488,6 +492,26 @@ void CSGShape3D::_update_collision_faces() {
}
}
+Ref<ArrayMesh> CSGShape3D::bake_static_mesh() {
+ Ref<ArrayMesh> baked_mesh;
+ if (is_root_shape() && root_mesh.is_valid()) {
+ baked_mesh = root_mesh;
+ }
+ return baked_mesh;
+}
+
+Ref<ConcavePolygonShape3D> CSGShape3D::bake_collision_shape() {
+ Ref<ConcavePolygonShape3D> baked_collision_shape;
+ if (is_root_shape() && root_collision_shape.is_valid()) {
+ baked_collision_shape.instantiate();
+ baked_collision_shape->set_faces(root_collision_shape->get_faces());
+ } else if (is_root_shape()) {
+ baked_collision_shape.instantiate();
+ baked_collision_shape->set_faces(_get_brush_collision_faces());
+ }
+ return baked_collision_shape;
+}
+
bool CSGShape3D::_is_debug_collision_shape_visible() {
return is_inside_tree() && (get_tree()->is_debugging_collisions_hint() || Engine::get_singleton()->is_editor_hint());
}
@@ -704,6 +728,9 @@ void CSGShape3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_meshes"), &CSGShape3D::get_meshes);
+ ClassDB::bind_method(D_METHOD("bake_static_mesh"), &CSGShape3D::bake_static_mesh);
+ ClassDB::bind_method(D_METHOD("bake_collision_shape"), &CSGShape3D::bake_collision_shape);
+
ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m"), "set_snap", "get_snap");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "calculate_tangents"), "set_calculate_tangents", "is_calculating_tangents");
@@ -934,7 +961,8 @@ CSGBrush *CSGMesh3D::_build_brush() {
void CSGMesh3D::_mesh_changed() {
_make_dirty();
- update_gizmos();
+
+ callable_mp((Node3D *)this, &Node3D::update_gizmos).call_deferred();
}
void CSGMesh3D::set_material(const Ref<Material> &p_material) {
diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h
index bb7c8be431..8f23ae2f9e 100644
--- a/modules/csg/csg_shape.h
+++ b/modules/csg/csg_shape.h
@@ -113,6 +113,7 @@ private:
void _update_debug_collision_shape();
void _clear_debug_collision_shape();
void _on_transform_changed();
+ Vector<Vector3> _get_brush_collision_faces();
protected:
void _notification(int p_what);
@@ -161,6 +162,10 @@ public:
bool is_calculating_tangents() const;
bool is_root_shape() const;
+
+ Ref<ArrayMesh> bake_static_mesh();
+ Ref<ConcavePolygonShape3D> bake_collision_shape();
+
CSGShape3D();
~CSGShape3D();
};
diff --git a/modules/csg/doc_classes/CSGShape3D.xml b/modules/csg/doc_classes/CSGShape3D.xml
index f9017e47c7..ac62d8dd83 100644
--- a/modules/csg/doc_classes/CSGShape3D.xml
+++ b/modules/csg/doc_classes/CSGShape3D.xml
@@ -5,12 +5,29 @@
</brief_description>
<description>
This is the CSG base class that provides CSG operation support to the various CSG nodes in Godot.
- [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay.
+ [b]Performance:[/b] CSG nodes are only intended for prototyping as they have a significant CPU performance cost.
+ Consider baking final CSG operation results into static geometry that replaces the CSG nodes.
+ Individual CSG root node results can be baked to nodes with static resources with the editor menu that appears when a CSG root node is selected.
+ Individual CSG root nodes can also be baked to static resources with scripts by calling [method bake_static_mesh] for the visual mesh or [method bake_collision_shape] for the physics collision.
+ Entire scenes of CSG nodes can be baked to static geometry and exported with the editor gltf scene exporter.
</description>
<tutorials>
<link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link>
</tutorials>
<methods>
+ <method name="bake_collision_shape">
+ <return type="ConcavePolygonShape3D" />
+ <description>
+ Returns a baked physics [ConcavePolygonShape3D] of this node's CSG operation result. Returns an empty shape if the node is not a CSG root node or has no valid geometry.
+ [b]Performance:[/b] If the CSG operation results in a very detailed geometry with many faces physics performance will be very slow. Concave shapes should in general only be used for static level geometry and not with dynamic objects that are moving.
+ </description>
+ </method>
+ <method name="bake_static_mesh">
+ <return type="ArrayMesh" />
+ <description>
+ Returns a baked static [ArrayMesh] of this node's CSG operation result. Materials from involved CSG nodes are added as extra mesh surfaces. Returns an empty mesh if the node is not a CSG root node or has no valid geometry.
+ </description>
+ </method>
<method name="get_collision_layer_value" qualifiers="const">
<return type="bool" />
<param index="0" name="layer_number" type="int" />
diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp
index ea7b6d225e..72676f4a40 100644
--- a/modules/csg/editor/csg_gizmos.cpp
+++ b/modules/csg/editor/csg_gizmos.cpp
@@ -38,6 +38,135 @@
#include "editor/plugins/gizmos/gizmo_3d_helper.h"
#include "editor/plugins/node_3d_editor_plugin.h"
#include "scene/3d/camera_3d.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/3d/physics/collision_shape_3d.h"
+#include "scene/3d/physics/static_body_3d.h"
+#include "scene/gui/dialogs.h"
+#include "scene/gui/menu_button.h"
+
+void CSGShapeEditor::_node_removed(Node *p_node) {
+ if (p_node == node) {
+ node = nullptr;
+ options->hide();
+ }
+}
+
+void CSGShapeEditor::edit(CSGShape3D *p_csg_shape) {
+ node = p_csg_shape;
+ if (node) {
+ options->show();
+ } else {
+ options->hide();
+ }
+}
+
+void CSGShapeEditor::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+ options->set_icon(get_editor_theme_icon(SNAME("CSGCombiner3D")));
+ } break;
+ }
+}
+
+void CSGShapeEditor::_menu_option(int p_option) {
+ Array meshes = node->get_meshes();
+ if (meshes.is_empty()) {
+ err_dialog->set_text(TTR("CSG operation returned an empty array."));
+ err_dialog->popup_centered();
+ return;
+ }
+
+ switch (p_option) {
+ case MENU_OPTION_BAKE_MESH_INSTANCE: {
+ _create_baked_mesh_instance();
+ } break;
+ case MENU_OPTION_BAKE_COLLISION_SHAPE: {
+ _create_baked_collision_shape();
+ } break;
+ }
+}
+
+void CSGShapeEditor::_create_baked_mesh_instance() {
+ if (node == get_tree()->get_edited_scene_root()) {
+ err_dialog->set_text(TTR("Can not add a baked mesh as sibling for the scene root.\nMove the CSG root node below a parent node."));
+ err_dialog->popup_centered();
+ return;
+ }
+
+ Ref<ArrayMesh> mesh = node->bake_static_mesh();
+ if (mesh.is_null()) {
+ err_dialog->set_text(TTR("CSG operation returned an empty mesh."));
+ err_dialog->popup_centered();
+ return;
+ }
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Create baked CSGShape3D Mesh Instance"));
+
+ Node *owner = get_tree()->get_edited_scene_root();
+
+ MeshInstance3D *mi = memnew(MeshInstance3D);
+ mi->set_mesh(mesh);
+ mi->set_name("CSGBakedMeshInstance3D");
+ mi->set_transform(node->get_transform());
+ ur->add_do_method(node, "add_sibling", mi, true);
+ ur->add_do_method(mi, "set_owner", owner);
+ ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), mi);
+
+ ur->add_do_reference(mi);
+ ur->add_undo_method(node->get_parent(), "remove_child", mi);
+
+ ur->commit_action();
+}
+
+void CSGShapeEditor::_create_baked_collision_shape() {
+ if (node == get_tree()->get_edited_scene_root()) {
+ err_dialog->set_text(TTR("Can not add a baked collision shape as sibling for the scene root.\nMove the CSG root node below a parent node."));
+ err_dialog->popup_centered();
+ return;
+ }
+
+ Ref<Shape3D> shape = node->bake_collision_shape();
+ if (shape.is_null()) {
+ err_dialog->set_text(TTR("CSG operation returned an empty shape."));
+ err_dialog->popup_centered();
+ return;
+ }
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Create baked CSGShape3D Collision Shape"));
+
+ Node *owner = get_tree()->get_edited_scene_root();
+
+ CollisionShape3D *cshape = memnew(CollisionShape3D);
+ cshape->set_shape(shape);
+ cshape->set_name("CSGBakedCollisionShape3D");
+ cshape->set_transform(node->get_transform());
+ ur->add_do_method(node, "add_sibling", cshape, true);
+ ur->add_do_method(cshape, "set_owner", owner);
+ ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), cshape);
+
+ ur->add_do_reference(cshape);
+ ur->add_undo_method(node->get_parent(), "remove_child", cshape);
+
+ ur->commit_action();
+}
+
+CSGShapeEditor::CSGShapeEditor() {
+ options = memnew(MenuButton);
+ options->hide();
+ options->set_text(TTR("CSG"));
+ options->set_switch_on_hover(true);
+ Node3DEditor::get_singleton()->add_control_to_menu_panel(options);
+
+ options->get_popup()->add_item(TTR("Bake Mesh Instance"), MENU_OPTION_BAKE_MESH_INSTANCE);
+ options->get_popup()->add_item(TTR("Bake Collision Shape"), MENU_OPTION_BAKE_COLLISION_SHAPE);
+
+ options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CSGShapeEditor::_menu_option));
+
+ err_dialog = memnew(AcceptDialog);
+ add_child(err_dialog);
+}
///////////
@@ -393,9 +522,26 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
}
}
+void EditorPluginCSG::edit(Object *p_object) {
+ CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object);
+ if (csg_shape && csg_shape->is_root_shape()) {
+ csg_shape_editor->edit(csg_shape);
+ } else {
+ csg_shape_editor->edit(nullptr);
+ }
+}
+
+bool EditorPluginCSG::handles(Object *p_object) const {
+ CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object);
+ return csg_shape && csg_shape->is_root_shape();
+}
+
EditorPluginCSG::EditorPluginCSG() {
Ref<CSGShape3DGizmoPlugin> gizmo_plugin = Ref<CSGShape3DGizmoPlugin>(memnew(CSGShape3DGizmoPlugin));
Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
+
+ csg_shape_editor = memnew(CSGShapeEditor);
+ EditorNode::get_singleton()->get_main_screen_control()->add_child(csg_shape_editor);
}
#endif // TOOLS_ENABLED
diff --git a/modules/csg/editor/csg_gizmos.h b/modules/csg/editor/csg_gizmos.h
index de19b33e7d..c562fe9fe7 100644
--- a/modules/csg/editor/csg_gizmos.h
+++ b/modules/csg/editor/csg_gizmos.h
@@ -37,8 +37,11 @@
#include "editor/plugins/editor_plugin.h"
#include "editor/plugins/node_3d_editor_gizmos.h"
+#include "scene/gui/control.h"
+class AcceptDialog;
class Gizmo3DHelper;
+class MenuButton;
class CSGShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(CSGShape3DGizmoPlugin, EditorNode3DGizmoPlugin);
@@ -62,10 +65,43 @@ public:
~CSGShape3DGizmoPlugin();
};
+class CSGShapeEditor : public Control {
+ GDCLASS(CSGShapeEditor, Control);
+
+ enum Menu {
+ MENU_OPTION_BAKE_MESH_INSTANCE,
+ MENU_OPTION_BAKE_COLLISION_SHAPE,
+ };
+
+ CSGShape3D *node = nullptr;
+ MenuButton *options = nullptr;
+ AcceptDialog *err_dialog = nullptr;
+
+ void _menu_option(int p_option);
+
+ void _create_baked_mesh_instance();
+ void _create_baked_collision_shape();
+
+protected:
+ void _node_removed(Node *p_node);
+
+ void _notification(int p_what);
+
+public:
+ void edit(CSGShape3D *p_csg_shape);
+ CSGShapeEditor();
+};
+
class EditorPluginCSG : public EditorPlugin {
GDCLASS(EditorPluginCSG, EditorPlugin);
+ CSGShapeEditor *csg_shape_editor = nullptr;
+
public:
+ virtual String get_name() const override { return "CSGShape3D"; }
+ virtual void edit(Object *p_object) override;
+ virtual bool handles(Object *p_object) const override;
+
EditorPluginCSG();
};
diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp
index 4938d8bff5..a0578bbd9d 100644
--- a/modules/cvtt/image_compress_cvtt.cpp
+++ b/modules/cvtt/image_compress_cvtt.cpp
@@ -149,7 +149,7 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) {
int h = p_image->get_height();
bool is_ldr = (p_image->get_format() <= Image::FORMAT_RGBA8);
- bool is_hdr = (p_image->get_format() >= Image::FORMAT_RH) && (p_image->get_format() <= Image::FORMAT_RGBE9995);
+ bool is_hdr = (p_image->get_format() >= Image::FORMAT_RF) && (p_image->get_format() <= Image::FORMAT_RGBE9995);
if (!is_ldr && !is_hdr) {
return; // Not a usable source format
diff --git a/modules/gdscript/config.py b/modules/gdscript/config.py
index a7d5c406e9..ecd33a5dac 100644
--- a/modules/gdscript/config.py
+++ b/modules/gdscript/config.py
@@ -11,6 +11,7 @@ def get_doc_classes():
return [
"@GDScript",
"GDScript",
+ "GDScriptSyntaxHighlighter",
]
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 6e7ac0dec9..104fc15a8c 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -52,9 +52,9 @@
<description>
Returns a single character (as a [String]) of the given Unicode code point (which is compatible with ASCII code).
[codeblock]
- a = char(65) # a is "A"
- a = char(65 + 32) # a is "a"
- a = char(8364) # a is "€"
+ var upper = char(65) # upper is "A"
+ var lower = char(65 + 32) # lower is "a"
+ var euro = char(8364) # euro is "€"
[/codeblock]
</description>
</method>
@@ -150,10 +150,10 @@
<description>
Returns the length of the given Variant [param var]. The length can be the character count of a [String] or [StringName], the element count of any array type, or the size of a [Dictionary]. For every other Variant type, a run-time error is generated and execution is stopped.
[codeblock]
- a = [1, 2, 3, 4]
+ var a = [1, 2, 3, 4]
len(a) # Returns 4
- b = "Hello!"
+ var b = "Hello!"
len(b) # Returns 6
[/codeblock]
</description>
diff --git a/modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml b/modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml
new file mode 100644
index 0000000000..63a9222901
--- /dev/null
+++ b/modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="GDScriptSyntaxHighlighter" inherits="EditorSyntaxHighlighter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
+ <brief_description>
+ A GDScript syntax highlighter that can be used with [TextEdit] and [CodeEdit] nodes.
+ </brief_description>
+ <description>
+ [b]Note:[/b] This class can only be used for editor plugins because it relies on editor settings.
+ [codeblocks]
+ [gdscript]
+ var code_preview = TextEdit.new()
+ var highlighter = GDScriptSyntaxHighlighter.new()
+ code_preview.syntax_highlighter = highlighter
+ [/gdscript]
+ [csharp]
+ var codePreview = new TextEdit();
+ var highlighter = new GDScriptSyntaxHighlighter();
+ codePreview.SyntaxHighlighter = highlighter;
+ [/csharp]
+ [/codeblocks]
+ </description>
+ <tutorials>
+ </tutorials>
+</class>
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 54bb152f7f..ecef852b4b 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -245,8 +245,26 @@ void GDScriptParser::apply_pending_warnings() {
}
#endif
-void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) {
- if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
+void GDScriptParser::override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument) {
+ if (!for_completion) {
+ return;
+ }
+ if (completion_context.node != p_for_node) {
+ return;
+ }
+ CompletionContext context;
+ context.type = p_type;
+ context.current_class = current_class;
+ context.current_function = current_function;
+ context.current_suite = current_suite;
+ context.current_line = tokenizer->get_cursor_line();
+ context.current_argument = p_argument;
+ context.node = p_node;
+ completion_context = context;
+}
+
+void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument) {
+ if (!for_completion) {
return;
}
if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
@@ -264,8 +282,8 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node
completion_context = context;
}
-void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) {
- if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) {
+void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type) {
+ if (!for_completion) {
return;
}
if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) {
@@ -1620,7 +1638,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
advance();
// Arguments.
push_completion_call(annotation);
- make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true);
+ make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0);
int argument_index = 0;
do {
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
@@ -1628,7 +1646,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
break;
}
- make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true);
+ make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index);
set_last_completion_call_arg(argument_index++);
ExpressionNode *argument = parse_expression(false);
if (argument == nullptr) {
@@ -2569,8 +2587,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_
}
LiteralNode *literal = alloc_node<LiteralNode>();
- complete_extents(literal);
literal->value = previous.literal;
+ reset_extents(literal, p_previous_operand);
+ update_extents(literal);
+ make_completion_context(COMPLETION_NONE, literal, -1);
+ complete_extents(literal);
return literal;
}
@@ -3065,12 +3086,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *
const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand);
Variant::Type builtin_type = get_builtin_type(id->name);
if (builtin_type < Variant::VARIANT_MAX) {
- make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type, true);
+ make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type);
is_builtin = true;
}
}
if (!is_builtin) {
- make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1, true);
+ make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1);
}
}
@@ -3195,23 +3216,24 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
push_completion_call(call);
int argument_index = 0;
do {
- make_completion_context(ct, call, argument_index++, true);
+ make_completion_context(ct, call, argument_index);
if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
// Allow for trailing comma.
break;
}
- bool use_identifier_completion = current.cursor_place == GDScriptTokenizerText::CURSOR_END || current.cursor_place == GDScriptTokenizerText::CURSOR_MIDDLE;
ExpressionNode *argument = parse_expression(false);
if (argument == nullptr) {
push_error(R"(Expected expression as the function argument.)");
} else {
call->arguments.push_back(argument);
- if (argument->type == Node::IDENTIFIER && use_identifier_completion) {
- completion_context.type = COMPLETION_IDENTIFIER;
+ if (argument->type == Node::LITERAL) {
+ override_completion_context(argument, ct, call, argument_index);
}
}
+
ct = COMPLETION_CALL_ARGUMENTS;
+ argument_index++;
} while (match(GDScriptTokenizer::Token::COMMA));
pop_completion_call();
@@ -3224,7 +3246,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) {
// We want code completion after a DOLLAR even if the current code is invalid.
- make_completion_context(COMPLETION_GET_NODE, nullptr, -1, true);
+ make_completion_context(COMPLETION_GET_NODE, nullptr, -1);
if (!current.is_node_name() && !check(GDScriptTokenizer::Token::LITERAL) && !check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) {
push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous.get_name()));
@@ -3281,7 +3303,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
path_state = PATH_STATE_SLASH;
}
- make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++, true);
+ make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++);
if (match(GDScriptTokenizer::Token::LITERAL)) {
if (previous.literal.get_type() != Variant::STRING) {
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 60ee477656..2999fb11e4 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -1455,9 +1455,11 @@ private:
}
void apply_pending_warnings();
#endif
-
- void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false);
- void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = false);
+ void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1);
+ void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type);
+ // In some cases it might become necessary to alter the completion context after parsing a subexpression.
+ // For example to not override COMPLETE_CALL_ARGUMENTS with COMPLETION_NONE from string literals.
+ void override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument = -1);
void push_completion_call(Node *p_call);
void pop_completion_call();
void set_last_completion_call_arg(int p_argument);
diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp
index 59e387eece..055f8e4110 100644
--- a/modules/gdscript/register_types.cpp
+++ b/modules/gdscript/register_types.cpp
@@ -165,6 +165,13 @@ void initialize_gdscript_module(ModuleInitializationLevel p_level) {
gdscript_translation_parser_plugin.instantiate();
EditorTranslationParser::get_singleton()->add_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD);
+ } else if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
+ ClassDB::APIType prev_api = ClassDB::get_current_api();
+ ClassDB::set_current_api(ClassDB::API_EDITOR);
+
+ GDREGISTER_CLASS(GDScriptSyntaxHighlighter);
+
+ ClassDB::set_current_api(prev_api);
}
#endif // TOOLS_ENABLED
}
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg
new file mode 100644
index 0000000000..be9bd510e1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg
@@ -0,0 +1,5 @@
+[output]
+include=[
+ {"insert_text": "\"property_of_a\""},
+ {"insert_text": "\"name\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd
new file mode 100644
index 0000000000..a8e04a62a7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd
@@ -0,0 +1,8 @@
+extends Node
+
+const A = preload ("res://completion/class_a.notest.gd")
+
+func _ready() -> void:
+ var a := A.new()
+ var tween := get_tree().create_tween()
+ tween.tween_property(a, "➡")
diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers.cfg b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.cfg
index 871a404e3a..5f08f9c265 100644
--- a/modules/gdscript/tests/scripts/completion/common/identifiers.cfg
+++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.cfg
@@ -11,11 +11,15 @@ include=[
{"display": "func_of_a"},
{"display": "signal_of_a"},
- ; GDScript: self.gd
+ ; GDScript: identifiers.gd
{"display": "test_signal_1"},
{"display": "test_signal_2"},
{"display": "test_var_1"},
{"display": "test_var_2"},
{"display": "test_func_1"},
{"display": "test_func_2"},
+ {"display": "test_parameter_1"},
+ {"display": "test_parameter_2"},
+ {"display": "local_test_var_1"},
+ {"display": "local_test_var_2"},
]
diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd
new file mode 100644
index 0000000000..91488c25aa
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd
@@ -0,0 +1,18 @@
+extends "res://completion/class_a.notest.gd"
+
+signal test_signal_1(a)
+signal test_signal_2(a: int)
+
+var test_var_1
+var test_var_2: int
+
+func test_func_1(t):
+ pass
+
+func test_func_2(t: int) -> void:
+ pass
+
+func _init(test_parameter_1, test_parameter_2: String):
+ var local_test_var_1
+ var local_test_var_2: int
+ print(t➡)
diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg
new file mode 100644
index 0000000000..5f08f9c265
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg
@@ -0,0 +1,25 @@
+scene="res://completion/get_node/get_node.tscn"
+[output]
+include=[
+ ; Node
+ {"display": "add_child"},
+ {"display": "owner"},
+ {"display": "child_entered_tree"},
+
+ ; GDScript: class_a.notest.gd
+ {"display": "property_of_a"},
+ {"display": "func_of_a"},
+ {"display": "signal_of_a"},
+
+ ; GDScript: identifiers.gd
+ {"display": "test_signal_1"},
+ {"display": "test_signal_2"},
+ {"display": "test_var_1"},
+ {"display": "test_var_2"},
+ {"display": "test_func_1"},
+ {"display": "test_func_2"},
+ {"display": "test_parameter_1"},
+ {"display": "test_parameter_2"},
+ {"display": "local_test_var_1"},
+ {"display": "local_test_var_2"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers.gd b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.gd
index efbafbee8e..a2f5b7bc23 100644
--- a/modules/gdscript/tests/scripts/completion/common/identifiers.gd
+++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.gd
@@ -12,5 +12,7 @@ func test_func_1(t):
func test_func_2(t: int) -> void:
pass
-func _init():
+func _init(test_parameter_1, test_parameter_2: String):
+ var local_test_var_1
+ var local_test_var_2: int
t➡
diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg
new file mode 100644
index 0000000000..5f08f9c265
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg
@@ -0,0 +1,25 @@
+scene="res://completion/get_node/get_node.tscn"
+[output]
+include=[
+ ; Node
+ {"display": "add_child"},
+ {"display": "owner"},
+ {"display": "child_entered_tree"},
+
+ ; GDScript: class_a.notest.gd
+ {"display": "property_of_a"},
+ {"display": "func_of_a"},
+ {"display": "signal_of_a"},
+
+ ; GDScript: identifiers.gd
+ {"display": "test_signal_1"},
+ {"display": "test_signal_2"},
+ {"display": "test_var_1"},
+ {"display": "test_var_2"},
+ {"display": "test_func_1"},
+ {"display": "test_func_2"},
+ {"display": "test_parameter_1"},
+ {"display": "test_parameter_2"},
+ {"display": "local_test_var_1"},
+ {"display": "local_test_var_2"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd
new file mode 100644
index 0000000000..fed0b869c4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd
@@ -0,0 +1,22 @@
+# godotengine/godot#92226
+extends "res://completion/class_a.notest.gd"
+
+signal test_signal_1(a)
+signal test_signal_2(a: int)
+
+var test_var_1
+var test_var_2: int
+
+func test_func_1(t):
+ pass
+
+func test_func_2(t: int) -> void:
+ pass
+
+func _init(test_parameter_1, test_parameter_2: String):
+ var local_test_var_1
+ var local_test_var_2: int
+ print(t➡
+
+ if true:
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg
new file mode 100644
index 0000000000..462846c9b2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg
@@ -0,0 +1,26 @@
+scene="res://completion/get_node/get_node.tscn"
+[output]
+exclude=[
+ ; Node
+ {"display": "add_child"},
+ {"display": "owner"},
+ {"display": "child_entered_tree"},
+ {"display": "add_child"},
+
+ ; GDScript: class_a.notest.gd
+ {"display": "property_of_a"},
+ {"display": "func_of_a"},
+ {"display": "signal_of_a"},
+
+ ; GDScript: no_completion_in_string.gd
+ {"display": "test_signal_1"},
+ {"display": "test_signal_2"},
+ {"display": "test_var_1"},
+ {"display": "test_var_2"},
+ {"display": "test_func_1"},
+ {"display": "test_func_2"},
+ {"display": "test_parameter_1"},
+ {"display": "test_parameter_2"},
+ {"display": "local_test_var_1"},
+ {"display": "local_test_var_2"},
+]
diff --git a/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd
new file mode 100644
index 0000000000..da52af9fe3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd
@@ -0,0 +1,19 @@
+# godotengine/godot#62945
+extends "res://completion/class_a.notest.gd"
+
+signal test_signal_1(a)
+signal test_signal_2(a: int)
+
+var test_var_1
+var test_var_2: int
+
+func test_func_1(t):
+ pass
+
+func test_func_2(t: int) -> void:
+ pass
+
+func _init(test_parameter_1, test_parameter_2: String):
+ var local_test_var_1
+ var local_test_var_2: int
+ var a = "➡"
diff --git a/modules/gdscript/tests/scripts/completion/common/self.gd b/modules/gdscript/tests/scripts/completion/common/self.gd
index 9ad2fbea51..ed181af0c5 100644
--- a/modules/gdscript/tests/scripts/completion/common/self.gd
+++ b/modules/gdscript/tests/scripts/completion/common/self.gd
@@ -14,3 +14,4 @@ func test_func_2(t: int) -> void:
func _init():
self.➡
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/filter/organized_export.gd b/modules/gdscript/tests/scripts/completion/filter/organized_export.gd
index 189608904c..9fa9618cee 100644
--- a/modules/gdscript/tests/scripts/completion/filter/organized_export.gd
+++ b/modules/gdscript/tests/scripts/completion/filter/organized_export.gd
@@ -5,4 +5,5 @@ extends CPUParticles2D
@export_subgroup("Test Subgroup")
func _init():
- ➡
+ t➡
+ pass
diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.cfg b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.cfg
index ae7d34d87d..ae7d34d87d 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.cfg
+++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.cfg
diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.gd
index 7710c2d13b..7710c2d13b 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd
+++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.gd
diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.cfg
index 9c580b711d..9c580b711d 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.cfg
+++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.cfg
diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.gd
index 6b29bf5526..6b29bf5526 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd
+++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.gd
diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.cfg
index 446198dd35..446198dd35 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.cfg
+++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.cfg
diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.gd
index 7710c2d13b..7710c2d13b 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd
+++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.gd
diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.cfg b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.cfg
index ae7d34d87d..ae7d34d87d 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.cfg
+++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.cfg
diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.gd
index 97b288334e..97b288334e 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd
+++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.gd
diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.cfg
index 9c580b711d..9c580b711d 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.cfg
+++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.cfg
diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.gd
index 402fd1d275..402fd1d275 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd
+++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.gd
diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.cfg
index 446198dd35..446198dd35 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.cfg
+++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.cfg
diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.gd
index 97b288334e..97b288334e 100644
--- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd
+++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.gd
diff --git a/modules/gdscript/tests/scripts/completion/types/local/interfered.cfg b/modules/gdscript/tests/scripts/completion/types/local/infered.cfg
index 8b68d51a89..8b68d51a89 100644
--- a/modules/gdscript/tests/scripts/completion/types/local/interfered.cfg
+++ b/modules/gdscript/tests/scripts/completion/types/local/infered.cfg
diff --git a/modules/gdscript/tests/scripts/completion/types/local/interfered.gd b/modules/gdscript/tests/scripts/completion/types/local/infered.gd
index f003c366a4..f003c366a4 100644
--- a/modules/gdscript/tests/scripts/completion/types/local/interfered.gd
+++ b/modules/gdscript/tests/scripts/completion/types/local/infered.gd
diff --git a/modules/gdscript/tests/scripts/completion/types/member/interfered.cfg b/modules/gdscript/tests/scripts/completion/types/member/infered.cfg
index 8b68d51a89..8b68d51a89 100644
--- a/modules/gdscript/tests/scripts/completion/types/member/interfered.cfg
+++ b/modules/gdscript/tests/scripts/completion/types/member/infered.cfg
diff --git a/modules/gdscript/tests/scripts/completion/types/member/interfered.gd b/modules/gdscript/tests/scripts/completion/types/member/infered.gd
index 069abd7891..069abd7891 100644
--- a/modules/gdscript/tests/scripts/completion/types/member/interfered.gd
+++ b/modules/gdscript/tests/scripts/completion/types/member/infered.gd
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index ec3ea9bcae..0aab6e84b4 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -115,8 +115,15 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
List<String> *r_missing_deps, Error *r_err) {
String blender_path = EDITOR_GET("filesystem/import/blender/blender_path");
- if (blender_major_version == -1 || blender_minor_version == -1) {
- _get_blender_version(blender_path, blender_major_version, blender_minor_version, nullptr);
+ ERR_FAIL_COND_V_MSG(blender_path.is_empty(), nullptr, "Blender path is empty, check your Editor Settings.");
+ ERR_FAIL_COND_V_MSG(!FileAccess::exists(blender_path), nullptr, vformat("Invalid Blender path: %s, check your Editor Settings.", blender_path));
+
+ if (blender_major_version == -1 || blender_minor_version == -1 || last_tested_blender_path != blender_path) {
+ String error;
+ if (!_get_blender_version(blender_path, blender_major_version, blender_minor_version, &error)) {
+ ERR_FAIL_V_MSG(nullptr, error);
+ }
+ last_tested_blender_path = blender_path;
}
// Get global paths for source and sink.
@@ -227,6 +234,18 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
} else {
parameters_map["export_normals"] = false;
}
+
+ if (blender_major_version > 4 || (blender_major_version == 4 && blender_minor_version >= 1)) {
+ if (p_options.has(SNAME("blender/meshes/export_geometry_nodes_instances")) && p_options[SNAME("blender/meshes/export_geometry_nodes_instances")]) {
+ parameters_map["export_gn_mesh"] = true;
+ if (blender_major_version == 4 && blender_minor_version == 1) {
+ // There is a bug in Blender 4.1 where it can't export lights and geometry nodes at the same time, one must be disabled.
+ parameters_map["export_lights"] = false;
+ }
+ } else {
+ parameters_map["export_gn_mesh"] = false;
+ }
+ }
if (p_options.has(SNAME("blender/meshes/tangents")) && p_options[SNAME("blender/meshes/tangents")]) {
parameters_map["export_tangents"] = true;
} else {
@@ -350,6 +369,7 @@ void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, Li
ADD_OPTION_BOOL("blender/meshes/colors", false);
ADD_OPTION_BOOL("blender/meshes/uvs", true);
ADD_OPTION_BOOL("blender/meshes/normals", true);
+ ADD_OPTION_BOOL("blender/meshes/export_geometry_nodes_instances", false);
ADD_OPTION_BOOL("blender/meshes/tangents", true);
ADD_OPTION_ENUM("blender/meshes/skins", "None,4 Influences (Compatible),All Influences", BLEND_BONE_INFLUENCES_ALL);
ADD_OPTION_BOOL("blender/meshes/export_bones_deforming_mesh_only", false);
diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h
index 8a6c65a624..6adace9276 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.h
+++ b/modules/gltf/editor/editor_scene_importer_blend.h
@@ -45,6 +45,7 @@ class EditorSceneFormatImporterBlend : public EditorSceneFormatImporter {
int blender_major_version = -1;
int blender_minor_version = -1;
+ String last_tested_blender_path;
public:
enum {
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index d98b250538..cd25b93e6c 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -5658,6 +5658,15 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn
if (p_scene_root == nullptr) {
// If the root node argument is null, this is the root node.
p_scene_root = current_node;
+ // If multiple nodes were generated under the root node, ensure they have the owner set.
+ if (unlikely(current_node->get_child_count() > 0)) {
+ Array args;
+ args.append(p_scene_root);
+ for (int i = 0; i < current_node->get_child_count(); i++) {
+ Node *child = current_node->get_child(i);
+ child->propagate_call(StringName("set_owner"), args);
+ }
+ }
} else {
// Add the node we generated and set the owner to the scene root.
p_scene_parent->add_child(current_node, true);
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 36c8a40ed9..6d561c1566 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -148,7 +148,7 @@ void CSharpLanguage::finalize() {
finalizing = true;
- // Make sure all script binding gchandles are released before finalizing GDMono
+ // Make sure all script binding gchandles are released before finalizing GDMono.
for (KeyValue<Object *, CSharpScriptBinding> &E : script_bindings) {
CSharpScriptBinding &script_binding = E.value;
@@ -156,6 +156,10 @@ void CSharpLanguage::finalize() {
script_binding.gchandle.release();
script_binding.inited = false;
}
+
+ // Make sure we clear all the instance binding callbacks so they don't get called
+ // after finalizing the C# language.
+ script_binding.owner->free_instance_binding(this);
}
if (gdmono) {
@@ -1227,6 +1231,11 @@ void CSharpLanguage::_instance_binding_free_callback(void *, void *, void *p_bin
}
GDExtensionBool CSharpLanguage::_instance_binding_reference_callback(void *p_token, void *p_binding, GDExtensionBool p_reference) {
+ // Instance bindings callbacks can only be called if the C# language is available.
+ // Failing this assert usually means that we didn't clear the instance binding in some Object
+ // and the C# language has already been finalized.
+ DEV_ASSERT(CSharpLanguage::get_singleton() != nullptr);
+
CRASH_COND(!p_binding);
CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)p_binding)->get();
@@ -1662,7 +1671,7 @@ bool CSharpInstance::_reference_owner_unsafe() {
// but the managed instance is alive, the refcount will be 1 instead of 0.
// See: _unreference_owner_unsafe()
- // May not me referenced yet, so we must use init_ref() instead of reference()
+ // May not be referenced yet, so we must use init_ref() instead of reference()
if (static_cast<RefCounted *>(owner)->init_ref()) {
CSharpLanguage::get_singleton()->post_unsafe_reference(owner);
unsafe_referenced = true;
@@ -2351,8 +2360,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
if (!ok) {
// Important to clear this before destroying the script instance here
instance->script = Ref<CSharpScript>();
- instance->owner = nullptr;
p_owner->set_script_instance(nullptr);
+ instance->owner = nullptr;
return nullptr;
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index d3720dcb72..ede0600ac1 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -355,24 +355,23 @@ namespace GodotTools.Export
if (outputPaths.Count > 2)
{
// lipo the simulator binaries together
- // TODO: Move this to the native lipo implementation we have in the macos export plugin.
- var lipoArgs = new List<string>();
- lipoArgs.Add("-create");
- lipoArgs.AddRange(outputPaths.Skip(1).Select(x => Path.Combine(x, $"{GodotSharpDirs.ProjectAssemblyName}.dylib")));
- lipoArgs.Add("-output");
- lipoArgs.Add(Path.Combine(outputPaths[1], $"{GodotSharpDirs.ProjectAssemblyName}.dylib"));
- int lipoExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("lipo"), lipoArgs);
- if (lipoExitCode != 0)
- throw new InvalidOperationException($"Command 'lipo' exited with code: {lipoExitCode}.");
+ string outputPath = Path.Combine(outputPaths[1], $"{GodotSharpDirs.ProjectAssemblyName}.dylib");
+ string[] files = outputPaths
+ .Skip(1)
+ .Select(path => Path.Combine(path, $"{GodotSharpDirs.ProjectAssemblyName}.dylib"))
+ .ToArray();
+
+ if (!Internal.LipOCreateFile(outputPath, files))
+ {
+ throw new InvalidOperationException($"Failed to 'lipo' simulator binaries.");
+ }
outputPaths.RemoveRange(2, outputPaths.Count - 2);
}
- var xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig,
- $"{GodotSharpDirs.ProjectAssemblyName}_aot.xcframework");
- if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths,
- Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, xcFrameworkPath)))
+ string xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, $"{GodotSharpDirs.ProjectAssemblyName}_aot.xcframework");
+ if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths, xcFrameworkPath))
{
throw new InvalidOperationException("Failed to generate xcframework.");
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs b/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs
deleted file mode 100644
index 023f46b685..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-using System;
-using System.IO;
-
-namespace GodotTools.Export
-{
- public static class XcodeHelper
- {
- private static string? _XcodePath = null;
-
- public static string XcodePath
- {
- get
- {
- if (_XcodePath == null)
- {
- _XcodePath = FindXcode();
-
- if (_XcodePath == null)
- throw new FileNotFoundException("Could not find Xcode.");
- }
-
- return _XcodePath;
- }
- }
-
- private static string? FindSelectedXcode()
- {
- var outputWrapper = new Godot.Collections.Array();
-
- int exitCode = Godot.OS.Execute("xcode-select", new string[] { "--print-path" }, output: outputWrapper);
-
- if (exitCode == 0)
- {
- string output = (string)outputWrapper[0];
- return output.Trim();
- }
-
- Console.Error.WriteLine($"'xcode-select --print-path' exited with code: {exitCode}");
-
- return null;
- }
-
- public static string? FindXcode()
- {
- string? selectedXcode = FindSelectedXcode();
- if (selectedXcode != null)
- {
- if (Directory.Exists(Path.Combine(selectedXcode, "Contents", "Developer")))
- return selectedXcode;
-
- // The path already pointed to Contents/Developer
- var dirInfo = new DirectoryInfo(selectedXcode);
- if (dirInfo is not { Parent.Name: "Contents", Name: "Developer" })
- {
- Console.WriteLine(Path.GetDirectoryName(selectedXcode));
- Console.WriteLine(System.IO.Directory.GetParent(selectedXcode)?.Name);
- Console.Error.WriteLine("Unrecognized path for selected Xcode");
- }
- else
- {
- return System.IO.Path.GetFullPath($"{selectedXcode}/../..");
- }
- }
- else
- {
- Console.Error.WriteLine("Could not find the selected Xcode; trying with a hint path");
- }
-
- const string XcodeHintPath = "/Applications/Xcode.app";
-
- if (Directory.Exists(XcodeHintPath))
- {
- if (Directory.Exists(Path.Combine(XcodeHintPath, "Contents", "Developer")))
- return XcodeHintPath;
-
- Console.Error.WriteLine($"Found Xcode at '{XcodeHintPath}' but it's missing the 'Contents/Developer' sub-directory");
- }
-
- return null;
- }
-
- public static string FindXcodeTool(string toolName)
- {
- string XcodeDefaultToolchain = Path.Combine(XcodePath, "Contents", "Developer", "Toolchains", "XcodeDefault.xctoolchain");
-
- string path = Path.Combine(XcodeDefaultToolchain, "usr", "bin", toolName);
- if (File.Exists(path))
- return path;
-
- throw new FileNotFoundException($"Cannot find Xcode tool: {toolName}");
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
index 175bb78051..225ac4073b 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
@@ -35,6 +35,13 @@ namespace GodotTools.Internals
return godot_icall_Internal_IsMacOSAppBundleInstalled(bundleIdIn);
}
+ public static bool LipOCreateFile(string outputPath, string[] files)
+ {
+ using godot_string outputPathIn = Marshaling.ConvertStringToNative(outputPath);
+ using godot_packed_string_array filesIn = Marshaling.ConvertSystemArrayToNativePackedStringArray(files);
+ return godot_icall_Internal_LipOCreateFile(outputPathIn, filesIn);
+ }
+
public static bool GodotIs32Bits() => godot_icall_Internal_GodotIs32Bits();
public static bool GodotIsRealTDouble() => godot_icall_Internal_GodotIsRealTDouble();
@@ -121,6 +128,8 @@ namespace GodotTools.Internals
private static partial bool godot_icall_Internal_IsMacOSAppBundleInstalled(in godot_string bundleId);
+ private static partial bool godot_icall_Internal_LipOCreateFile(in godot_string outputPath, in godot_packed_string_array files);
+
private static partial bool godot_icall_Internal_GodotIs32Bits();
private static partial bool godot_icall_Internal_GodotIsRealTDouble();
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index 03d8b4eab6..7322a47630 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -44,6 +44,7 @@
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
+#include "editor/export/lipo.h"
#include "editor/gui/editor_run_bar.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/themes/editor_scale.h"
@@ -117,6 +118,13 @@ bool godot_icall_Internal_IsMacOSAppBundleInstalled(const godot_string *p_bundle
#endif
}
+bool godot_icall_Internal_LipOCreateFile(const godot_string *p_output_path, const godot_packed_array *p_files) {
+ String output_path = *reinterpret_cast<const String *>(p_output_path);
+ PackedStringArray files = *reinterpret_cast<const PackedStringArray *>(p_files);
+ LipO lip;
+ return lip.create_file(output_path, files);
+}
+
bool godot_icall_Internal_GodotIs32Bits() {
return sizeof(void *) == 4;
}
@@ -258,6 +266,7 @@ static const void *unmanaged_callbacks[]{
(void *)godot_icall_EditorProgress_Step,
(void *)godot_icall_Internal_FullExportTemplatesDir,
(void *)godot_icall_Internal_IsMacOSAppBundleInstalled,
+ (void *)godot_icall_Internal_LipOCreateFile,
(void *)godot_icall_Internal_GodotIs32Bits,
(void *)godot_icall_Internal_GodotIsRealTDouble,
(void *)godot_icall_Internal_GodotMainIteration,
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index 5cc2a8026e..901700067d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -241,11 +241,17 @@ namespace Godot.Bridge
if (outIconPath != null)
{
- var iconAttr = scriptType.GetCustomAttributes(inherit: false)
+ IconAttribute? iconAttr = scriptType.GetCustomAttributes(inherit: false)
.OfType<IconAttribute>()
.FirstOrDefault();
- *outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path);
+ if (!string.IsNullOrEmpty(iconAttr?.Path))
+ {
+ string iconPath = iconAttr.Path.IsAbsolutePath()
+ ? iconAttr.Path.SimplifyPath()
+ : scriptPathStr.GetBaseDir().PathJoin(iconAttr.Path).SimplifyPath();
+ *outIconPath = Marshaling.ConvertStringToNative(iconPath);
+ }
}
if (outBaseType != null)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
index a5fa89d3bf..f943a3049d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
@@ -47,7 +47,7 @@ namespace Godot
{
get
{
- real_t detSign = Mathf.Sign(BasisDeterminant());
+ real_t detSign = Mathf.Sign(Determinant());
return new Vector2(X.Length(), detSign * Y.Length());
}
}
@@ -59,7 +59,7 @@ namespace Godot
{
get
{
- real_t detSign = Mathf.Sign(BasisDeterminant());
+ real_t detSign = Mathf.Sign(Determinant());
return Mathf.Acos(X.Normalized().Dot(detSign * Y.Normalized())) - Mathf.Pi * 0.5f;
}
}
@@ -135,7 +135,7 @@ namespace Godot
/// <returns>The inverse transformation matrix.</returns>
public readonly Transform2D AffineInverse()
{
- real_t det = BasisDeterminant();
+ real_t det = Determinant();
if (det == 0)
throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted.");
@@ -157,15 +157,16 @@ namespace Godot
/// <summary>
/// Returns the determinant of the basis matrix. If the basis is
- /// uniformly scaled, its determinant is the square of the scale.
+ /// uniformly scaled, then its determinant equals the square of the
+ /// scale factor.
///
- /// A negative determinant means the Y scale is negative.
- /// A zero determinant means the basis isn't invertible,
- /// and is usually considered invalid.
+ /// A negative determinant means the basis was flipped, so one part of
+ /// the scale is negative. A zero determinant means the basis isn't
+ /// invertible, and is usually considered invalid.
/// </summary>
/// <returns>The determinant of the basis matrix.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private readonly real_t BasisDeterminant()
+ public readonly real_t Determinant()
{
return (X.X * Y.Y) - (X.Y * Y.X);
}
diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp
index 8c2fb42463..33b92f6266 100644
--- a/modules/navigation/2d/nav_mesh_generator_2d.cpp
+++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp
@@ -1042,10 +1042,32 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation
}
TPPLPartition tpart;
- if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) { //failed!
- ERR_PRINT("NavigationPolygon Convex partition failed. Unable to create a valid NavigationMesh from defined polygon outline paths.");
- p_navigation_mesh->clear();
- return;
+
+ NavigationPolygon::SamplePartitionType sample_partition_type = p_navigation_mesh->get_sample_partition_type();
+
+ switch (sample_partition_type) {
+ case NavigationPolygon::SamplePartitionType::SAMPLE_PARTITION_CONVEX_PARTITION:
+ if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) {
+ ERR_PRINT("NavigationPolygon polygon convex partition failed. Unable to create a valid navigation mesh polygon layout from provided source geometry.");
+ p_navigation_mesh->set_vertices(Vector<Vector2>());
+ p_navigation_mesh->clear_polygons();
+ return;
+ }
+ break;
+ case NavigationPolygon::SamplePartitionType::SAMPLE_PARTITION_TRIANGULATE:
+ if (tpart.Triangulate_EC(&tppl_in_polygon, &tppl_out_polygon) == 0) {
+ ERR_PRINT("NavigationPolygon polygon triangulation failed. Unable to create a valid navigation mesh polygon layout from provided source geometry.");
+ p_navigation_mesh->set_vertices(Vector<Vector2>());
+ p_navigation_mesh->clear_polygons();
+ return;
+ }
+ break;
+ default: {
+ ERR_PRINT("NavigationPolygon polygon partitioning failed. Unrecognized partition type.");
+ p_navigation_mesh->set_vertices(Vector<Vector2>());
+ p_navigation_mesh->clear_polygons();
+ return;
+ }
}
Vector<Vector2> new_vertices;
diff --git a/modules/navigation/3d/godot_navigation_server_3d.cpp b/modules/navigation/3d/godot_navigation_server_3d.cpp
index fc96506795..11a5de608b 100644
--- a/modules/navigation/3d/godot_navigation_server_3d.cpp
+++ b/modules/navigation/3d/godot_navigation_server_3d.cpp
@@ -509,22 +509,31 @@ void GodotNavigationServer3D::region_bake_navigation_mesh(Ref<NavigationMesh> p_
int GodotNavigationServer3D::region_get_connections_count(RID p_region) const {
NavRegion *region = region_owner.get_or_null(p_region);
ERR_FAIL_NULL_V(region, 0);
-
- return region->get_connections_count();
+ NavMap *map = region->get_map();
+ if (map) {
+ return map->get_region_connections_count(region);
+ }
+ return 0;
}
Vector3 GodotNavigationServer3D::region_get_connection_pathway_start(RID p_region, int p_connection_id) const {
NavRegion *region = region_owner.get_or_null(p_region);
ERR_FAIL_NULL_V(region, Vector3());
-
- return region->get_connection_pathway_start(p_connection_id);
+ NavMap *map = region->get_map();
+ if (map) {
+ return map->get_region_connection_pathway_start(region, p_connection_id);
+ }
+ return Vector3();
}
Vector3 GodotNavigationServer3D::region_get_connection_pathway_end(RID p_region, int p_connection_id) const {
NavRegion *region = region_owner.get_or_null(p_region);
ERR_FAIL_NULL_V(region, Vector3());
-
- return region->get_connection_pathway_end(p_connection_id);
+ NavMap *map = region->get_map();
+ if (map) {
+ return map->get_region_connection_pathway_end(region, p_connection_id);
+ }
+ return Vector3();
}
Vector3 GodotNavigationServer3D::region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const {
diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp
index 9796fa350d..0c91e8dea3 100644
--- a/modules/navigation/nav_map.cpp
+++ b/modules/navigation/nav_map.cpp
@@ -937,8 +937,9 @@ void NavMap::sync() {
_new_pm_edge_free_count = 0;
// Remove regions connections.
+ region_external_connections.clear();
for (NavRegion *region : regions) {
- region->get_connections().clear();
+ region_external_connections[region] = LocalVector<gd::Edge::Connection>();
}
// Resize the polygon count.
@@ -1072,7 +1073,7 @@ void NavMap::sync() {
free_edge.polygon->edges[free_edge.edge].connections.push_back(new_connection);
// Add the connection to the region_connection map.
- ((NavRegion *)free_edge.polygon->owner)->get_connections().push_back(new_connection);
+ region_external_connections[(NavRegion *)free_edge.polygon->owner].push_back(new_connection);
_new_pm_edge_connection_count += 1;
}
}
@@ -1428,6 +1429,40 @@ void NavMap::_update_merge_rasterizer_cell_dimensions() {
merge_rasterizer_cell_height = cell_height * merge_rasterizer_cell_scale;
}
+int NavMap::get_region_connections_count(NavRegion *p_region) const {
+ ERR_FAIL_NULL_V(p_region, 0);
+
+ HashMap<NavRegion *, LocalVector<gd::Edge::Connection>>::ConstIterator found_connections = region_external_connections.find(p_region);
+ if (found_connections) {
+ return found_connections->value.size();
+ }
+ return 0;
+}
+
+Vector3 NavMap::get_region_connection_pathway_start(NavRegion *p_region, int p_connection_id) const {
+ ERR_FAIL_NULL_V(p_region, Vector3());
+
+ HashMap<NavRegion *, LocalVector<gd::Edge::Connection>>::ConstIterator found_connections = region_external_connections.find(p_region);
+ if (found_connections) {
+ ERR_FAIL_INDEX_V(p_connection_id, int(found_connections->value.size()), Vector3());
+ return found_connections->value[p_connection_id].pathway_start;
+ }
+
+ return Vector3();
+}
+
+Vector3 NavMap::get_region_connection_pathway_end(NavRegion *p_region, int p_connection_id) const {
+ ERR_FAIL_NULL_V(p_region, Vector3());
+
+ HashMap<NavRegion *, LocalVector<gd::Edge::Connection>>::ConstIterator found_connections = region_external_connections.find(p_region);
+ if (found_connections) {
+ ERR_FAIL_INDEX_V(p_connection_id, int(found_connections->value.size()), Vector3());
+ return found_connections->value[p_connection_id].pathway_end;
+ }
+
+ return Vector3();
+}
+
NavMap::NavMap() {
avoidance_use_multiple_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_multiple_threads");
avoidance_use_high_priority_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_high_priority_threads");
diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h
index 3dd6db21dd..82e8854b7a 100644
--- a/modules/navigation/nav_map.h
+++ b/modules/navigation/nav_map.h
@@ -125,6 +125,8 @@ class NavMap : public NavRid {
int pm_edge_free_count = 0;
int pm_obstacle_count = 0;
+ HashMap<NavRegion *, LocalVector<gd::Edge::Connection>> region_external_connections;
+
public:
NavMap();
~NavMap();
@@ -219,6 +221,10 @@ public:
int get_pm_edge_free_count() const { return pm_edge_free_count; }
int get_pm_obstacle_count() const { return pm_obstacle_count; }
+ int get_region_connections_count(NavRegion *p_region) const;
+ Vector3 get_region_connection_pathway_start(NavRegion *p_region, int p_connection_id) const;
+ Vector3 get_region_connection_pathway_end(NavRegion *p_region, int p_connection_id) const;
+
private:
void compute_single_step(uint32_t index, NavAgent **agent);
diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp
index f30855d697..85510bd416 100644
--- a/modules/navigation/nav_region.cpp
+++ b/modules/navigation/nav_region.cpp
@@ -44,8 +44,6 @@ void NavRegion::set_map(NavMap *p_map) {
map = p_map;
polygons_dirty = true;
- connections.clear();
-
if (map) {
map->add_region(this);
}
@@ -105,25 +103,6 @@ void NavRegion::set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) {
polygons_dirty = true;
}
-int NavRegion::get_connections_count() const {
- if (!map) {
- return 0;
- }
- return connections.size();
-}
-
-Vector3 NavRegion::get_connection_pathway_start(int p_connection_id) const {
- ERR_FAIL_NULL_V(map, Vector3());
- ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3());
- return connections[p_connection_id].pathway_start;
-}
-
-Vector3 NavRegion::get_connection_pathway_end(int p_connection_id) const {
- ERR_FAIL_NULL_V(map, Vector3());
- ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3());
- return connections[p_connection_id].pathway_end;
-}
-
Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const {
if (!get_enabled()) {
return Vector3();
diff --git a/modules/navigation/nav_region.h b/modules/navigation/nav_region.h
index ebc082bd2f..662a32c47a 100644
--- a/modules/navigation/nav_region.h
+++ b/modules/navigation/nav_region.h
@@ -40,7 +40,6 @@
class NavRegion : public NavBase {
NavMap *map = nullptr;
Transform3D transform;
- Vector<gd::Edge::Connection> connections;
bool enabled = true;
bool use_edge_connections = true;
@@ -85,13 +84,6 @@ public:
void set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh);
- Vector<gd::Edge::Connection> &get_connections() {
- return connections;
- }
- int get_connections_count() const;
- Vector3 get_connection_pathway_start(int p_connection_id) const;
- Vector3 get_connection_pathway_end(int p_connection_id) const;
-
LocalVector<gd::Polygon> const &get_polygons() const {
return polygons;
}
diff --git a/modules/openxr/config.py b/modules/openxr/config.py
index 6c6b9fdb8b..559aa2acf6 100644
--- a/modules/openxr/config.py
+++ b/modules/openxr/config.py
@@ -22,6 +22,7 @@ def get_doc_classes():
"OpenXRInteractionProfileMetadata",
"OpenXRIPBinding",
"OpenXRHand",
+ "OpenXRVisibilityMask",
"OpenXRCompositionLayer",
"OpenXRCompositionLayerQuad",
"OpenXRCompositionLayerCylinder",
diff --git a/modules/openxr/doc_classes/OpenXRVisibilityMask.xml b/modules/openxr/doc_classes/OpenXRVisibilityMask.xml
new file mode 100644
index 0000000000..90ee889c7c
--- /dev/null
+++ b/modules/openxr/doc_classes/OpenXRVisibilityMask.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="OpenXRVisibilityMask" inherits="VisualInstance3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
+ <brief_description>
+ Draws a stereo correct visibility mask.
+ </brief_description>
+ <description>
+ The visibility mask allows us to black out the part of the render result that is invisible due to lens distortion.
+ As this is rendered first, it prevents fragments with expensive lighting calculations to be processed as they are discarded through z-checking.
+ </description>
+ <tutorials>
+ </tutorials>
+</class>
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
index d9a66aa827..6d6231f6fa 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
@@ -58,9 +58,14 @@ OpenXRHandTrackingExtension::~OpenXRHandTrackingExtension() {
HashMap<String, bool *> OpenXRHandTrackingExtension::get_requested_extensions() {
HashMap<String, bool *> request_extensions;
+ unobstructed_data_source = GLOBAL_GET("xr/openxr/extensions/hand_tracking_unobstructed_data_source");
+ controller_data_source = GLOBAL_GET("xr/openxr/extensions/hand_tracking_controller_data_source");
+
request_extensions[XR_EXT_HAND_TRACKING_EXTENSION_NAME] = &hand_tracking_ext;
request_extensions[XR_EXT_HAND_JOINTS_MOTION_RANGE_EXTENSION_NAME] = &hand_motion_range_ext;
- request_extensions[XR_EXT_HAND_TRACKING_DATA_SOURCE_EXTENSION_NAME] = &hand_tracking_source_ext;
+ if (unobstructed_data_source || controller_data_source) {
+ request_extensions[XR_EXT_HAND_TRACKING_DATA_SOURCE_EXTENSION_NAME] = &hand_tracking_source_ext;
+ }
return request_extensions;
}
@@ -141,10 +146,18 @@ void OpenXRHandTrackingExtension::on_process() {
void *next_pointer = nullptr;
// Originally not all XR runtimes supported hand tracking data sourced both from controllers and normal hand tracking.
- // With this extension we can indicate we accept input from both sources so hand tracking data is consistently provided
- // on runtimes that support this.
- XrHandTrackingDataSourceEXT data_sources[2] = { XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT, XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT };
- XrHandTrackingDataSourceInfoEXT data_source_info = { XR_TYPE_HAND_TRACKING_DATA_SOURCE_INFO_EXT, next_pointer, 2, data_sources };
+ // With this extension we can indicate we wish to accept input from either or both sources.
+ // This functionality is subject to the abilities of the XR runtime and requires the data source extension.
+ // Note: If the data source extension is not available, no guarantees can be made on what the XR runtime supports.
+ uint32_t data_source_count = 0;
+ XrHandTrackingDataSourceEXT data_sources[2];
+ if (unobstructed_data_source) {
+ data_sources[data_source_count++] = XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT;
+ }
+ if (controller_data_source) {
+ data_sources[data_source_count++] = XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT;
+ }
+ XrHandTrackingDataSourceInfoEXT data_source_info = { XR_TYPE_HAND_TRACKING_DATA_SOURCE_INFO_EXT, next_pointer, data_source_count, data_sources };
if (hand_tracking_source_ext) {
// If supported include this info
next_pointer = &data_source_info;
@@ -224,7 +237,9 @@ void OpenXRHandTrackingExtension::on_process() {
if (XR_FAILED(result)) {
// not successful? then we do nothing.
print_line("OpenXR: Failed to get tracking for hand", i, "[", OpenXRAPI::get_singleton()->get_error_string(result), "]");
+ godot_tracker->set_hand_tracking_source(XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN);
godot_tracker->set_has_tracking_data(false);
+ godot_tracker->invalidate_pose("default");
continue;
}
@@ -235,8 +250,6 @@ void OpenXRHandTrackingExtension::on_process() {
}
if (hand_trackers[i].locations.isActive) {
- godot_tracker->set_has_tracking_data(true);
-
// SKELETON_RIG_HUMANOID bone adjustment. This rotation performs:
// OpenXR Z+ -> Godot Humanoid Y- (Back along the bone)
// OpenXR Y+ -> Godot Humanoid Z- (Out the back of the hand)
@@ -245,7 +258,6 @@ void OpenXRHandTrackingExtension::on_process() {
for (int joint = 0; joint < XR_HAND_JOINT_COUNT_EXT; joint++) {
const XrHandJointLocationEXT &location = hand_trackers[i].joint_locations[joint];
const XrHandJointVelocityEXT &velocity = hand_trackers[i].joint_velocities[joint];
- const XrHandTrackingDataSourceStateEXT &data_source = hand_trackers[i].data_source;
const XrPosef &pose = location.pose;
Transform3D transform;
@@ -285,23 +297,35 @@ void OpenXRHandTrackingExtension::on_process() {
godot_tracker->set_hand_joint_radius((XRHandTracker::HandJoint)joint, location.radius);
if (joint == XR_HAND_JOINT_PALM_EXT) {
- XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN;
- if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) {
- source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED;
- } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) {
- source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER;
- }
-
- godot_tracker->set_hand_tracking_source(source);
if (location.locationFlags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) {
+ XrHandTrackingDataSourceStateEXT &data_source = hand_trackers[i].data_source;
+
+ XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN;
+ if (hand_tracking_source_ext) {
+ if (!data_source.isActive) {
+ source = XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED;
+ } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) {
+ source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED;
+ } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) {
+ source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER;
+ } else {
+ // Data source shouldn't be active, if new data sources are added to OpenXR we need to enable them.
+ WARN_PRINT_ONCE("Unknown active data source found!");
+ source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN;
+ }
+ }
+ godot_tracker->set_hand_tracking_source(source);
+ godot_tracker->set_has_tracking_data(true);
godot_tracker->set_pose("default", transform, linear_velocity, angular_velocity);
} else {
+ godot_tracker->set_hand_tracking_source(hand_tracking_source_ext ? XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED : XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN);
godot_tracker->set_has_tracking_data(false);
godot_tracker->invalidate_pose("default");
}
}
}
} else {
+ godot_tracker->set_hand_tracking_source(hand_tracking_source_ext ? XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED : XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN);
godot_tracker->set_has_tracking_data(false);
godot_tracker->invalidate_pose("default");
}
@@ -349,16 +373,17 @@ XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(HandTra
OpenXRHandTrackingExtension::HandTrackedSource OpenXRHandTrackingExtension::get_hand_tracking_source(HandTrackedHands p_hand) const {
ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, OPENXR_SOURCE_UNKNOWN);
- if (hand_tracking_source_ext && hand_trackers[p_hand].data_source.isActive) {
- switch (hand_trackers[p_hand].data_source.dataSource) {
- case XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT:
- return OPENXR_SOURCE_UNOBSTRUCTED;
-
- case XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT:
- return OPENXR_SOURCE_CONTROLLER;
-
- default:
- return OPENXR_SOURCE_UNKNOWN;
+ if (hand_tracking_source_ext) {
+ if (!hand_trackers[p_hand].data_source.isActive) {
+ return OPENXR_SOURCE_NOT_TRACKED;
+ } else if (hand_trackers[p_hand].data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) {
+ return OPENXR_SOURCE_UNOBSTRUCTED;
+ } else if (hand_trackers[p_hand].data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) {
+ return OPENXR_SOURCE_CONTROLLER;
+ } else {
+ // Data source shouldn't be active, if new data sources are added to OpenXR we need to enable them.
+ WARN_PRINT_ONCE("Unknown active data source found!");
+ return OPENXR_SOURCE_UNKNOWN;
}
}
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h
index f709bc05c2..2c34ff7f21 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.h
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h
@@ -48,6 +48,7 @@ public:
OPENXR_SOURCE_UNKNOWN,
OPENXR_SOURCE_UNOBSTRUCTED,
OPENXR_SOURCE_CONTROLLER,
+ OPENXR_SOURCE_NOT_TRACKED,
OPENXR_SOURCE_MAX
};
@@ -110,6 +111,8 @@ private:
bool hand_tracking_ext = false;
bool hand_motion_range_ext = false;
bool hand_tracking_source_ext = false;
+ bool unobstructed_data_source = false;
+ bool controller_data_source = false;
// functions
void cleanup_hand_tracking();
diff --git a/modules/openxr/extensions/openxr_mxink_extension.cpp b/modules/openxr/extensions/openxr_mxink_extension.cpp
new file mode 100644
index 0000000000..fe48583c27
--- /dev/null
+++ b/modules/openxr/extensions/openxr_mxink_extension.cpp
@@ -0,0 +1,83 @@
+/**************************************************************************/
+/* openxr_mxink_extension.cpp */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#include "openxr_mxink_extension.h"
+
+#include "../action_map/openxr_interaction_profile_metadata.h"
+
+// Not in base XR libs needs def
+#define XR_LOGITECH_MX_INK_STYLUS_INTERACTION_EXTENSION_NAME "XR_LOGITECH_mx_ink_stylus_interaction"
+
+HashMap<String, bool *> OpenXRMxInkExtension::get_requested_extensions() {
+ HashMap<String, bool *> request_extensions;
+
+ request_extensions[XR_LOGITECH_MX_INK_STYLUS_INTERACTION_EXTENSION_NAME] = &available;
+
+ return request_extensions;
+}
+
+bool OpenXRMxInkExtension::is_available() {
+ return available;
+}
+
+void OpenXRMxInkExtension::on_register_metadata() {
+ OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton();
+ ERR_FAIL_NULL(metadata);
+
+ // Logitech MX Ink Stylus
+ metadata->register_interaction_profile("Logitech MX Ink Stylus", "/interaction_profiles/logitech/mx_ink_stylus_logitech", XR_LOGITECH_MX_INK_STYLUS_INTERACTION_EXTENSION_NAME);
+
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip Force", "/user/hand/left", "/user/hand/left/input/tip_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Middle force", "/user/hand/left", "/user/hand/left/input/cluster_middle_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front click", "/user/hand/left", "/user/hand/left/input/cluster_front_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front double", "/user/hand/left", "/user/hand/left/input/cluster_front_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back click", "/user/hand/left", "/user/hand/left/input/cluster_back_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back double", "/user/hand/left", "/user/hand/left/input/cluster_back_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "System click", "/user/hand/left", "/user/hand/left/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Docked", "/user/hand/left", "/user/hand/left/input/dock_logitech/docked_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Aim pose", "/user/hand/left", "/user/hand/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip pose", "/user/hand/left", "/user/hand/left/input/tip_logitech/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip Force", "/user/hand/right", "/user/hand/right/input/tip_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Middle force", "/user/hand/right", "/user/hand/right/input/cluster_middle_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front click", "/user/hand/right", "/user/hand/right/input/cluster_front_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front double", "/user/hand/right", "/user/hand/right/input/cluster_front_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back click", "/user/hand/right", "/user/hand/right/input/cluster_back_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back double", "/user/hand/right", "/user/hand/right/input/cluster_back_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "System click", "/user/hand/right", "/user/hand/right/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Docked", "/user/hand/right", "/user/hand/right/input/dock_logitech/docked_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Grip pose", "/user/hand/right", "/user/hand/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Aim pose", "/user/hand/right", "/user/hand/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip pose", "/user/hand/right", "/user/hand/right/input/tip_logitech/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+ metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Haptic output", "/user/hand/right", "/user/hand/right/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC);
+}
diff --git a/modules/openxr/extensions/openxr_mxink_extension.h b/modules/openxr/extensions/openxr_mxink_extension.h
new file mode 100644
index 0000000000..fe0cf866aa
--- /dev/null
+++ b/modules/openxr/extensions/openxr_mxink_extension.h
@@ -0,0 +1,48 @@
+/**************************************************************************/
+/* openxr_mxink_extension.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 OPENXR_MXINK_EXTENSION_H
+#define OPENXR_MXINK_EXTENSION_H
+
+#include "openxr_extension_wrapper.h"
+
+class OpenXRMxInkExtension : public OpenXRExtensionWrapper {
+public:
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+
+ bool is_available();
+
+ virtual void on_register_metadata() override;
+
+private:
+ bool available = false;
+};
+
+#endif // OPENXR_MXINK_EXTENSION_H
diff --git a/modules/openxr/extensions/openxr_visibility_mask_extension.cpp b/modules/openxr/extensions/openxr_visibility_mask_extension.cpp
new file mode 100644
index 0000000000..3e193d7d50
--- /dev/null
+++ b/modules/openxr/extensions/openxr_visibility_mask_extension.cpp
@@ -0,0 +1,279 @@
+/**************************************************************************/
+/* openxr_visibility_mask_extension.cpp */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#include "openxr_visibility_mask_extension.h"
+
+#include "../openxr_api.h"
+#include "core/string/print_string.h"
+#include "core/variant/array.h"
+#include "core/variant/variant.h"
+#include "servers/rendering_server.h"
+
+static const char *VISIBILITY_MASK_SHADER_CODE =
+ "shader_type spatial;\n"
+ "render_mode unshaded, shadows_disabled, cull_disabled;\n"
+ "void vertex() {\n"
+ "\tif (int(VERTEX.z) == VIEW_INDEX) {\n"
+ "\t\tVERTEX.z = -1.0;\n"
+ "\t\tVERTEX += EYE_OFFSET;\n"
+ "\t\tPOSITION = PROJECTION_MATRIX * vec4(VERTEX, 1.0);\n"
+ "\t\tPOSITION.xy /= POSITION.w;\n"
+ "\t\tPOSITION.z = 1.0;\n"
+ "\t\tPOSITION.w = 1.0;\n"
+ "\t} else {\n"
+ "\t\tPOSITION = vec4(2.0, 2.0, 2.0, 1.0);\n"
+ "\t}\n"
+ "}\n"
+ "void fragment() {\n"
+ "\tALBEDO = vec3(0.0, 0.0, 0.0);\n"
+ "}\n";
+
+OpenXRVisibilityMaskExtension *OpenXRVisibilityMaskExtension::singleton = nullptr;
+
+OpenXRVisibilityMaskExtension *OpenXRVisibilityMaskExtension::get_singleton() {
+ return singleton;
+}
+
+OpenXRVisibilityMaskExtension::OpenXRVisibilityMaskExtension() {
+ singleton = this;
+}
+
+OpenXRVisibilityMaskExtension::~OpenXRVisibilityMaskExtension() {
+ singleton = nullptr;
+}
+
+HashMap<String, bool *> OpenXRVisibilityMaskExtension::get_requested_extensions() {
+ HashMap<String, bool *> request_extensions;
+
+ request_extensions[XR_KHR_VISIBILITY_MASK_EXTENSION_NAME] = &available;
+
+ return request_extensions;
+}
+
+void OpenXRVisibilityMaskExtension::on_instance_created(const XrInstance p_instance) {
+ if (available) {
+ EXT_INIT_XR_FUNC(xrGetVisibilityMaskKHR);
+ }
+}
+
+void OpenXRVisibilityMaskExtension::on_session_created(const XrSession p_instance) {
+ if (available) {
+ RS *rendering_server = RS::get_singleton();
+ ERR_FAIL_NULL(rendering_server);
+
+ OpenXRAPI *openxr_api = (OpenXRAPI *)OpenXRAPI::get_singleton();
+ ERR_FAIL_NULL(openxr_api);
+
+ // Create our shader.
+ shader = rendering_server->shader_create();
+ rendering_server->shader_set_code(shader, VISIBILITY_MASK_SHADER_CODE);
+
+ // Create our material.
+ material = rendering_server->material_create();
+ rendering_server->material_set_shader(material, shader);
+ rendering_server->material_set_render_priority(material, 99);
+
+ // Create our mesh.
+ mesh = rendering_server->mesh_create();
+
+ // Get our initial mesh data.
+ mesh_count = openxr_api->get_view_count(); // We need a mesh for each view.
+ for (uint32_t i = 0; i < mesh_count; i++) {
+ _update_mesh_data(i);
+ }
+
+ // And update our mesh
+ _update_mesh();
+ }
+}
+
+void OpenXRVisibilityMaskExtension::on_session_destroyed() {
+ RS *rendering_server = RS::get_singleton();
+ ERR_FAIL_NULL(rendering_server);
+
+ // Free our mesh.
+ if (mesh.is_valid()) {
+ rendering_server->free(mesh);
+ mesh = RID();
+ }
+
+ // Free our material.
+ if (material.is_valid()) {
+ rendering_server->free(material);
+ material = RID();
+ }
+
+ // Free our shader.
+ if (shader.is_valid()) {
+ rendering_server->free(shader);
+ shader = RID();
+ }
+
+ mesh_count = 0;
+}
+
+void OpenXRVisibilityMaskExtension::on_pre_render() {
+ // Update mesh data if its dirty.
+ // Here we call this from the rendering thread however as we're going through the rendering server this is safe.
+ _update_mesh();
+}
+
+bool OpenXRVisibilityMaskExtension::on_event_polled(const XrEventDataBuffer &event) {
+ if (event.type == XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR) {
+ XrEventDataVisibilityMaskChangedKHR *vismask_event = (XrEventDataVisibilityMaskChangedKHR *)&event;
+
+ print_verbose("OpenXR EVENT: Visibility mask changed for view " + String::num_uint64(vismask_event->viewIndex));
+
+ if (available) { // This event won't be called if this extension is not available but better safe than sorry.
+ _update_mesh_data(vismask_event->viewIndex);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool OpenXRVisibilityMaskExtension::is_available() {
+ return available;
+}
+
+RID OpenXRVisibilityMaskExtension::get_mesh() {
+ return mesh;
+}
+
+void OpenXRVisibilityMaskExtension::_update_mesh_data(uint32_t p_view) {
+ if (available) {
+ ERR_FAIL_UNSIGNED_INDEX(p_view, 4);
+
+ OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
+ ERR_FAIL_NULL(openxr_api);
+
+ XrSession session = openxr_api->get_session();
+ XrViewConfigurationType view_configuration_type = openxr_api->get_view_configuration();
+
+ // Figure out how much data we're getting.
+ XrVisibilityMaskKHR visibility_mask_data = {
+ XR_TYPE_VISIBILITY_MASK_KHR,
+ nullptr,
+ 0,
+ 0,
+ nullptr,
+ 0,
+ 0,
+ nullptr,
+ };
+
+ XrResult result = xrGetVisibilityMaskKHR(session, view_configuration_type, p_view, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &visibility_mask_data);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Unable to obtain visibility mask metrics [", openxr_api->get_error_string(result), "]");
+ return;
+ }
+
+ // Resize buffers
+ mesh_data[p_view].vertices.resize(visibility_mask_data.vertexCountOutput);
+ mesh_data[p_view].indices.resize(visibility_mask_data.indexCountOutput);
+
+ visibility_mask_data.vertexCapacityInput = visibility_mask_data.vertexCountOutput;
+ visibility_mask_data.vertices = mesh_data[p_view].vertices.ptrw();
+ visibility_mask_data.indexCapacityInput = visibility_mask_data.indexCountOutput;
+ visibility_mask_data.indices = mesh_data[p_view].indices.ptrw();
+
+ result = xrGetVisibilityMaskKHR(session, view_configuration_type, p_view, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &visibility_mask_data);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Unable to obtain visibility mask data [", openxr_api->get_error_string(result), "]");
+ return;
+ }
+
+ // Mark as dirty, we have updated mesh data.
+ is_dirty = true;
+ }
+}
+
+void OpenXRVisibilityMaskExtension::_update_mesh() {
+ if (available && is_dirty && mesh_count > 0) {
+ RS *rendering_server = RS::get_singleton();
+ ERR_FAIL_NULL(rendering_server);
+
+ OpenXRAPI *openxr_api = (OpenXRAPI *)OpenXRAPI::get_singleton();
+ ERR_FAIL_NULL(openxr_api);
+
+ // Combine all vertex and index buffers into one.
+ PackedVector3Array vertices;
+ PackedInt32Array indices;
+
+ uint64_t vertice_count = 0;
+ uint64_t index_count = 0;
+
+ for (uint32_t i = 0; i < mesh_count; i++) {
+ vertice_count += mesh_data[i].vertices.size();
+ index_count += mesh_data[i].indices.size();
+ }
+
+ vertices.resize(vertice_count);
+ indices.resize(index_count);
+ uint64_t offset = 0;
+
+ Vector3 *v_out = vertices.ptrw();
+ int32_t *i_out = indices.ptrw();
+
+ for (uint32_t i = 0; i < mesh_count; i++) {
+ const XrVector2f *v_in = mesh_data[i].vertices.ptr();
+ for (uint32_t j = 0; j < mesh_data[i].vertices.size(); j++) {
+ v_out->x = v_in->x;
+ v_out->y = v_in->y;
+ v_out->z = float(i); // We store our view in our Z component, our shader will filter the right faces out.
+ v_out++;
+ v_in++;
+ }
+ const uint32_t *i_in = mesh_data[i].indices.ptr();
+ for (uint32_t j = 0; j < mesh_data[i].indices.size(); j++) {
+ *i_out = offset + *i_in;
+ i_out++;
+ i_in++;
+ }
+
+ offset += mesh_data[i].vertices.size();
+ }
+
+ // Update our mesh.
+ Array arr;
+ arr.resize(RS::ARRAY_MAX);
+ arr[RS::ARRAY_VERTEX] = vertices;
+ arr[RS::ARRAY_INDEX] = indices;
+
+ rendering_server->mesh_clear(mesh);
+ rendering_server->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr);
+ rendering_server->mesh_surface_set_material(mesh, 0, material);
+
+ // Set no longer dirty.
+ is_dirty = false;
+ }
+}
diff --git a/modules/openxr/extensions/openxr_visibility_mask_extension.h b/modules/openxr/extensions/openxr_visibility_mask_extension.h
new file mode 100644
index 0000000000..ee80dd02ff
--- /dev/null
+++ b/modules/openxr/extensions/openxr_visibility_mask_extension.h
@@ -0,0 +1,95 @@
+/**************************************************************************/
+/* openxr_visibility_mask_extension.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 OPENXR_VISIBILITY_MASK_EXTENSION_H
+#define OPENXR_VISIBILITY_MASK_EXTENSION_H
+
+#include "../util.h"
+
+#include "core/templates/vector.h"
+#include "openxr_extension_wrapper.h"
+#include "scene/resources/mesh.h"
+
+// The OpenXR visibility mask extension provides a mesh for each eye that
+// can be used as a mask to determine which part of our rendered result
+// is actually visible to the user. Due to lens distortion the edges of
+// the rendered image are never used in the final result output on the HMD.
+//
+// Blacking out this are of the render result can remove a fair amount of
+// overhead in rendering part of the screen that is unused.
+//
+// https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_KHR_visibility_mask
+
+class OpenXRVisibilityMaskExtension : public OpenXRExtensionWrapper {
+public:
+ static OpenXRVisibilityMaskExtension *get_singleton();
+
+ OpenXRVisibilityMaskExtension();
+ virtual ~OpenXRVisibilityMaskExtension() override;
+
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+
+ virtual void on_instance_created(const XrInstance p_instance) override;
+
+ virtual void on_session_created(const XrSession p_instance) override;
+ virtual void on_session_destroyed() override;
+
+ virtual void on_pre_render() override;
+ virtual bool on_event_polled(const XrEventDataBuffer &event) override;
+
+ bool is_available();
+ RID get_mesh();
+
+private:
+ static OpenXRVisibilityMaskExtension *singleton;
+
+ bool available = false;
+ bool is_dirty = false;
+
+ RID shader;
+ RID material;
+ RID mesh;
+
+ struct MeshData {
+ Vector<XrVector2f> vertices;
+ Vector<uint32_t> indices;
+ };
+
+ uint32_t mesh_count = 0;
+ MeshData mesh_data[4];
+
+ void _update_mesh_data(uint32_t p_view);
+ void _update_mesh();
+
+ // OpenXR API call wrappers
+ EXT_PROTO_XRRESULT_FUNC5(xrGetVisibilityMaskKHR, (XrSession), session, (XrViewConfigurationType), viewConfigurationType, (uint32_t), viewIndex, (XrVisibilityMaskTypeKHR), visibilityMaskType, (XrVisibilityMaskKHR *), visibilityMask);
+};
+
+#endif // OPENXR_VISIBILITY_MASK_EXTENSION_H
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index c6fd20dac7..db5c04fdef 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -1459,6 +1459,10 @@ void OpenXRAPI::set_form_factor(XrFormFactor p_form_factor) {
form_factor = p_form_factor;
}
+uint32_t OpenXRAPI::get_view_count() {
+ return view_count;
+}
+
void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configuration) {
ERR_FAIL_COND(is_initialized());
@@ -1914,15 +1918,6 @@ bool OpenXRAPI::poll_events() {
// We probably didn't poll fast enough, just output warning
WARN_PRINT("OpenXR EVENT: " + itos(event->lostEventCount) + " event data lost!");
} break;
- case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: {
- // XrEventDataVisibilityMaskChangedKHR *event = (XrEventDataVisibilityMaskChangedKHR *)&runtimeEvent;
-
- // TODO implement this in the future, we should call xrGetVisibilityMaskKHR to obtain a mask,
- // this will allow us to prevent rendering the part of our view which is never displayed giving us
- // a decent performance improvement.
-
- print_verbose("OpenXR EVENT: STUB: visibility mask changed");
- } break;
case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: {
XrEventDataInstanceLossPending *event = (XrEventDataInstanceLossPending *)&runtimeEvent;
diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h
index 81e9deb7ab..88455379b9 100644
--- a/modules/openxr/openxr_api.h
+++ b/modules/openxr/openxr_api.h
@@ -434,6 +434,7 @@ public:
void set_form_factor(XrFormFactor p_form_factor);
XrFormFactor get_form_factor() const { return form_factor; }
+ uint32_t get_view_count();
void set_view_configuration(XrViewConfigurationType p_view_configuration);
XrViewConfigurationType get_view_configuration() const { return view_configuration; }
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index cce9c09361..73ac529537 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -36,7 +36,8 @@
#include "extensions/openxr_eye_gaze_interaction.h"
#include "extensions/openxr_hand_interaction_extension.h"
-#include "thirdparty/openxr/include/openxr/openxr.h"
+
+#include <openxr/openxr.h>
void OpenXRInterface::_bind_methods() {
// lifecycle signals
diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp
index 85514737f2..61c294eff5 100644
--- a/modules/openxr/register_types.cpp
+++ b/modules/openxr/register_types.cpp
@@ -44,6 +44,7 @@
#include "scene/openxr_composition_layer_equirect.h"
#include "scene/openxr_composition_layer_quad.h"
#include "scene/openxr_hand.h"
+#include "scene/openxr_visibility_mask.h"
#include "extensions/openxr_composition_layer_depth_extension.h"
#include "extensions/openxr_composition_layer_extension.h"
@@ -57,8 +58,10 @@
#include "extensions/openxr_local_floor_extension.h"
#include "extensions/openxr_meta_controller_extension.h"
#include "extensions/openxr_ml2_controller_extension.h"
+#include "extensions/openxr_mxink_extension.h"
#include "extensions/openxr_palm_pose_extension.h"
#include "extensions/openxr_pico_controller_extension.h"
+#include "extensions/openxr_visibility_mask_extension.h"
#include "extensions/openxr_wmr_controller_extension.h"
#ifdef TOOLS_ENABLED
@@ -126,6 +129,8 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
OpenXRAPI::register_extension_wrapper(memnew(OpenXRMetaControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXREyeGazeInteractionExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandInteractionExtension));
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXRMxInkExtension));
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXRVisibilityMaskExtension));
// register gated extensions
if (GLOBAL_GET("xr/openxr/extensions/hand_tracking")) {
@@ -179,6 +184,8 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(OpenXRHand);
+ GDREGISTER_CLASS(OpenXRVisibilityMask);
+
XRServer *xr_server = XRServer::get_singleton();
if (xr_server) {
openxr_interface.instantiate();
diff --git a/modules/openxr/scene/openxr_visibility_mask.cpp b/modules/openxr/scene/openxr_visibility_mask.cpp
new file mode 100644
index 0000000000..7214fac327
--- /dev/null
+++ b/modules/openxr/scene/openxr_visibility_mask.cpp
@@ -0,0 +1,106 @@
+/**************************************************************************/
+/* openxr_visibility_mask.cpp */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#include "openxr_visibility_mask.h"
+
+#include "../extensions/openxr_visibility_mask_extension.h"
+#include "../openxr_interface.h"
+#include "scene/3d/xr_nodes.h"
+
+void OpenXRVisibilityMask::_bind_methods() {
+}
+
+void OpenXRVisibilityMask::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ OpenXRVisibilityMaskExtension *vis_mask_ext = OpenXRVisibilityMaskExtension::get_singleton();
+ if (vis_mask_ext && vis_mask_ext->is_available()) {
+ set_base(vis_mask_ext->get_mesh());
+ }
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ set_base(RID());
+ } break;
+ }
+}
+
+void OpenXRVisibilityMask::_on_openxr_session_begun() {
+ if (is_inside_tree()) {
+ OpenXRVisibilityMaskExtension *vis_mask_ext = OpenXRVisibilityMaskExtension::get_singleton();
+ if (vis_mask_ext && vis_mask_ext->is_available()) {
+ set_base(vis_mask_ext->get_mesh());
+ }
+ }
+}
+
+void OpenXRVisibilityMask::_on_openxr_session_stopping() {
+ set_base(RID());
+}
+
+PackedStringArray OpenXRVisibilityMask::get_configuration_warnings() const {
+ PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
+
+ if (is_visible() && is_inside_tree()) {
+ XRCamera3D *camera = Object::cast_to<XRCamera3D>(get_parent());
+ if (camera == nullptr) {
+ warnings.push_back(RTR("OpenXR visibility mask must have an XRCamera3D node as their parent."));
+ }
+ }
+
+ return warnings;
+}
+
+AABB OpenXRVisibilityMask::get_aabb() const {
+ AABB ret;
+
+ // Make sure it's always visible, this is positioned through its shader.
+ ret.position = Vector3(-1000.0, -1000.0, -1000.0);
+ ret.size = Vector3(2000.0, 2000.0, 2000.0);
+
+ return ret;
+}
+
+OpenXRVisibilityMask::OpenXRVisibilityMask() {
+ Ref<OpenXRInterface> openxr_interface = XRServer::get_singleton()->find_interface("OpenXR");
+ if (openxr_interface.is_valid()) {
+ openxr_interface->connect("session_begun", callable_mp(this, &OpenXRVisibilityMask::_on_openxr_session_begun));
+ openxr_interface->connect("session_stopping", callable_mp(this, &OpenXRVisibilityMask::_on_openxr_session_stopping));
+ }
+
+ RS::get_singleton()->instance_geometry_set_cast_shadows_setting(get_instance(), RS::SHADOW_CASTING_SETTING_OFF);
+}
+
+OpenXRVisibilityMask::~OpenXRVisibilityMask() {
+ Ref<OpenXRInterface> openxr_interface = XRServer::get_singleton()->find_interface("OpenXR");
+ if (openxr_interface.is_valid()) {
+ openxr_interface->disconnect("session_begun", callable_mp(this, &OpenXRVisibilityMask::_on_openxr_session_begun));
+ openxr_interface->disconnect("session_stopping", callable_mp(this, &OpenXRVisibilityMask::_on_openxr_session_stopping));
+ }
+}
diff --git a/modules/openxr/scene/openxr_visibility_mask.h b/modules/openxr/scene/openxr_visibility_mask.h
new file mode 100644
index 0000000000..0acb30fc0a
--- /dev/null
+++ b/modules/openxr/scene/openxr_visibility_mask.h
@@ -0,0 +1,56 @@
+/**************************************************************************/
+/* openxr_visibility_mask.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 OPENXR_VISIBILITY_MASK_H
+#define OPENXR_VISIBILITY_MASK_H
+
+#include "scene/3d/visual_instance_3d.h"
+
+class OpenXRVisibilityMask : public VisualInstance3D {
+ GDCLASS(OpenXRVisibilityMask, VisualInstance3D);
+
+protected:
+ static void _bind_methods();
+
+ void _notification(int p_what);
+
+ void _on_openxr_session_begun();
+ void _on_openxr_session_stopping();
+
+public:
+ virtual PackedStringArray get_configuration_warnings() const override;
+
+ virtual AABB get_aabb() const override;
+
+ OpenXRVisibilityMask();
+ ~OpenXRVisibilityMask();
+};
+
+#endif // OPENXR_VISIBILITY_MASK_H
diff --git a/platform/web/serve.py b/platform/web/serve.py
index f0b0ec9622..4e1521449b 100755
--- a/platform/web/serve.py
+++ b/platform/web/serve.py
@@ -6,7 +6,7 @@ import os
import socket
import subprocess
import sys
-from http.server import HTTPServer, SimpleHTTPRequestHandler, test # type: ignore
+from http.server import HTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
@@ -38,12 +38,24 @@ def shell_open(url):
def serve(root, port, run_browser):
os.chdir(root)
+ address = ("", port)
+ httpd = DualStackServer(address, CORSRequestHandler)
+
+ url = f"http://127.0.0.1:{port}"
if run_browser:
# Open the served page in the user's default browser.
- print("Opening the served URL in the default browser (use `--no-browser` or `-n` to disable this).")
- shell_open(f"http://127.0.0.1:{port}")
+ print(f"Opening the served URL in the default browser (use `--no-browser` or `-n` to disable this): {url}")
+ shell_open(url)
+ else:
+ print(f"Serving at: {url}")
- test(CORSRequestHandler, DualStackServer, port=port)
+ try:
+ httpd.serve_forever()
+ except KeyboardInterrupt:
+ print("\nKeyboard interrupt received, stopping server.")
+ finally:
+ # Clean-up server
+ httpd.server_close()
if __name__ == "__main__":
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index f0fe56a9c8..635e8326e2 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -250,6 +250,14 @@ void DisplayServerWindows::tts_stop() {
tts->stop();
}
+Error DisplayServerWindows::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ return _file_dialog_with_options_show(p_title, p_current_directory, String(), p_filename, p_show_hidden, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);
+}
+
+Error DisplayServerWindows::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) {
+ return _file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, true);
+}
+
// Silence warning due to a COM API weirdness.
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
@@ -377,22 +385,85 @@ public:
#pragma GCC diagnostic pop
#endif
-Error DisplayServerWindows::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
- return _file_dialog_with_options_show(p_title, p_current_directory, String(), p_filename, p_show_hidden, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);
+LRESULT CALLBACK WndProcFileDialog(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ DisplayServerWindows *ds_win = static_cast<DisplayServerWindows *>(DisplayServer::get_singleton());
+ if (ds_win) {
+ return ds_win->WndProcFileDialog(hWnd, uMsg, wParam, lParam);
+ } else {
+ return DefWindowProcW(hWnd, uMsg, wParam, lParam);
+ }
}
-Error DisplayServerWindows::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) {
- return _file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, true);
+LRESULT DisplayServerWindows::WndProcFileDialog(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+ MutexLock lock(file_dialog_mutex);
+ if (file_dialog_wnd.has(hWnd)) {
+ if (file_dialog_wnd[hWnd]->close_requested.is_set()) {
+ IPropertyStore *prop_store;
+ HRESULT hr = SHGetPropertyStoreForWindow(hWnd, IID_IPropertyStore, (void **)&prop_store);
+ if (hr == S_OK) {
+ PROPVARIANT val;
+ PropVariantInit(&val);
+ prop_store->SetValue(PKEY_AppUserModel_ID, val);
+ prop_store->Release();
+ }
+ DestroyWindow(hWnd);
+ file_dialog_wnd.erase(hWnd);
+ }
+ }
+ return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
-Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) {
- _THREAD_SAFE_METHOD_
+void DisplayServerWindows::_thread_fd_monitor(void *p_ud) {
+ DisplayServerWindows *ds = static_cast<DisplayServerWindows *>(get_singleton());
+ FileDialogData *fd = (FileDialogData *)p_ud;
- ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+ if (fd->mode < 0 && fd->mode >= DisplayServer::FILE_DIALOG_MODE_SAVE_MAX) {
+ fd->finished.set();
+ return;
+ }
+ CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+ int64_t x = fd->wrect.position.x;
+ int64_t y = fd->wrect.position.y;
+ int64_t w = fd->wrect.size.x;
+ int64_t h = fd->wrect.size.y;
+
+ WNDCLASSW wc = {};
+ wc.lpfnWndProc = (WNDPROC)::WndProcFileDialog;
+ wc.hInstance = GetModuleHandle(nullptr);
+ wc.lpszClassName = L"Engine File Dialog";
+ RegisterClassW(&wc);
+
+ HWND hwnd_dialog = CreateWindowExW(WS_EX_APPWINDOW, L"Engine File Dialog", L"", WS_OVERLAPPEDWINDOW, x, y, w, h, nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
+ if (hwnd_dialog) {
+ {
+ MutexLock lock(ds->file_dialog_mutex);
+ ds->file_dialog_wnd[hwnd_dialog] = fd;
+ }
+
+ HICON mainwindow_icon = (HICON)SendMessage(fd->hwnd_owner, WM_GETICON, ICON_SMALL, 0);
+ if (mainwindow_icon) {
+ SendMessage(hwnd_dialog, WM_SETICON, ICON_SMALL, (LPARAM)mainwindow_icon);
+ }
+ mainwindow_icon = (HICON)SendMessage(fd->hwnd_owner, WM_GETICON, ICON_BIG, 0);
+ if (mainwindow_icon) {
+ SendMessage(hwnd_dialog, WM_SETICON, ICON_BIG, (LPARAM)mainwindow_icon);
+ }
+ IPropertyStore *prop_store;
+ HRESULT hr = SHGetPropertyStoreForWindow(hwnd_dialog, IID_IPropertyStore, (void **)&prop_store);
+ if (hr == S_OK) {
+ PROPVARIANT val;
+ InitPropVariantFromString((PCWSTR)fd->appid.utf16().get_data(), &val);
+ prop_store->SetValue(PKEY_AppUserModel_ID, val);
+ prop_store->Release();
+ }
+ }
+
+ SetCurrentProcessExplicitAppUserModelID((PCWSTR)fd->appid.utf16().get_data());
Vector<Char16String> filter_names;
Vector<Char16String> filter_exts;
- for (const String &E : p_filters) {
+ for (const String &E : fd->filters) {
Vector<String> tokens = E.split(";");
if (tokens.size() >= 1) {
String flt = tokens[0].strip_edges();
@@ -425,11 +496,9 @@ Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title
filters.push_back({ (LPCWSTR)filter_names[i].ptr(), (LPCWSTR)filter_exts[i].ptr() });
}
- WindowID prev_focus = last_focused_window;
-
HRESULT hr = S_OK;
IFileDialog *pfd = nullptr;
- if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) {
+ if (fd->mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
hr = CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC_SERVER, IID_IFileSaveDialog, (void **)&pfd);
} else {
hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_IFileOpenDialog, (void **)&pfd);
@@ -445,40 +514,32 @@ Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title
IFileDialogCustomize *pfdc = nullptr;
hr = pfd->QueryInterface(IID_PPV_ARGS(&pfdc));
- for (int i = 0; i < p_options.size(); i++) {
- const Dictionary &item = p_options[i];
+ for (int i = 0; i < fd->options.size(); i++) {
+ const Dictionary &item = fd->options[i];
if (!item.has("name") || !item.has("values") || !item.has("default")) {
continue;
}
- const String &name = item["name"];
- const Vector<String> &options = item["values"];
- int default_idx = item["default"];
-
- event_handler->add_option(pfdc, name, options, default_idx);
+ event_handler->add_option(pfdc, item["name"], item["values"], item["default_idx"]);
}
- event_handler->set_root(p_root);
+ event_handler->set_root(fd->root);
pfdc->Release();
DWORD flags;
pfd->GetOptions(&flags);
- if (p_mode == FILE_DIALOG_MODE_OPEN_FILES) {
+ if (fd->mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES) {
flags |= FOS_ALLOWMULTISELECT;
}
- if (p_mode == FILE_DIALOG_MODE_OPEN_DIR) {
+ if (fd->mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR) {
flags |= FOS_PICKFOLDERS;
}
- if (p_show_hidden) {
+ if (fd->show_hidden) {
flags |= FOS_FORCESHOWHIDDEN;
}
pfd->SetOptions(flags | FOS_FORCEFILESYSTEM);
- pfd->SetTitle((LPCWSTR)p_title.utf16().ptr());
+ pfd->SetTitle((LPCWSTR)fd->title.utf16().ptr());
- String dir = ProjectSettings::get_singleton()->globalize_path(p_current_directory);
- if (dir == ".") {
- dir = OS::get_singleton()->get_executable_path().get_base_dir();
- }
- dir = dir.replace("/", "\\");
+ String dir = fd->current_directory.replace("/", "\\");
IShellItem *shellitem = nullptr;
hr = SHCreateItemFromParsingName((LPCWSTR)dir.utf16().ptr(), nullptr, IID_IShellItem, (void **)&shellitem);
@@ -487,16 +548,11 @@ Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title
pfd->SetFolder(shellitem);
}
- pfd->SetFileName((LPCWSTR)p_filename.utf16().ptr());
+ pfd->SetFileName((LPCWSTR)fd->filename.utf16().ptr());
pfd->SetFileTypes(filters.size(), filters.ptr());
pfd->SetFileTypeIndex(0);
- WindowID window_id = _get_focused_window_or_popup();
- if (!windows.has(window_id)) {
- window_id = MAIN_WINDOW_ID;
- }
-
- hr = pfd->Show(windows[window_id].hWnd);
+ hr = pfd->Show(hwnd_dialog);
pfd->Unadvise(cookie);
Dictionary options = event_handler->get_selected();
@@ -513,7 +569,7 @@ Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title
if (SUCCEEDED(hr)) {
Vector<String> file_names;
- if (p_mode == FILE_DIALOG_MODE_OPEN_FILES) {
+ if (fd->mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES) {
IShellItemArray *results;
hr = static_cast<IFileOpenDialog *>(pfd)->GetResults(&results);
if (SUCCEEDED(hr)) {
@@ -546,73 +602,148 @@ Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title
result->Release();
}
}
- if (p_callback.is_valid()) {
- if (p_options_in_cb) {
+ if (fd->callback.is_valid()) {
+ if (fd->options_in_cb) {
Variant v_result = true;
Variant v_files = file_names;
Variant v_index = index;
Variant v_opt = options;
- Variant ret;
- Callable::CallError ce;
- const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+ const Variant *cb_args[4] = { &v_result, &v_files, &v_index, &v_opt };
- p_callback.callp(args, 4, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 4, ce)));
- }
+ fd->callback.call_deferredp(cb_args, 4);
} else {
Variant v_result = true;
Variant v_files = file_names;
Variant v_index = index;
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
+ const Variant *cb_args[3] = { &v_result, &v_files, &v_index };
- p_callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 3, ce)));
- }
+ fd->callback.call_deferredp(cb_args, 3);
}
}
} else {
- if (p_callback.is_valid()) {
- if (p_options_in_cb) {
+ if (fd->callback.is_valid()) {
+ if (fd->options_in_cb) {
Variant v_result = false;
Variant v_files = Vector<String>();
- Variant v_index = index;
- Variant v_opt = options;
- Variant ret;
- Callable::CallError ce;
- const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt };
+ Variant v_index = 0;
+ Variant v_opt = Dictionary();
+ const Variant *cb_args[4] = { &v_result, &v_files, &v_index, &v_opt };
- p_callback.callp(args, 4, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 4, ce)));
- }
+ fd->callback.call_deferredp(cb_args, 4);
} else {
Variant v_result = false;
Variant v_files = Vector<String>();
- Variant v_index = index;
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &v_result, &v_files, &v_index };
+ Variant v_index = 0;
+ const Variant *cb_args[3] = { &v_result, &v_files, &v_index };
- p_callback.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callback, args, 3, ce)));
- }
+ fd->callback.call_deferredp(cb_args, 3);
}
}
}
pfd->Release();
- if (prev_focus != INVALID_WINDOW_ID) {
- callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus);
+ } else {
+ if (fd->callback.is_valid()) {
+ if (fd->options_in_cb) {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = 0;
+ Variant v_opt = Dictionary();
+ const Variant *cb_args[4] = { &v_result, &v_files, &v_index, &v_opt };
+
+ fd->callback.call_deferredp(cb_args, 4);
+ } else {
+ Variant v_result = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = 0;
+ const Variant *cb_args[3] = { &v_result, &v_files, &v_index };
+
+ fd->callback.call_deferredp(cb_args, 3);
+ }
}
+ }
+ {
+ MutexLock lock(ds->file_dialog_mutex);
+ if (hwnd_dialog && ds->file_dialog_wnd.has(hwnd_dialog)) {
+ IPropertyStore *prop_store;
+ hr = SHGetPropertyStoreForWindow(hwnd_dialog, IID_IPropertyStore, (void **)&prop_store);
+ if (hr == S_OK) {
+ PROPVARIANT val;
+ PropVariantInit(&val);
+ prop_store->SetValue(PKEY_AppUserModel_ID, val);
+ prop_store->Release();
+ }
+ DestroyWindow(hwnd_dialog);
+ ds->file_dialog_wnd.erase(hwnd_dialog);
+ }
+ }
+ UnregisterClassW(L"Engine File Dialog", GetModuleHandle(nullptr));
+ CoUninitialize();
+
+ fd->finished.set();
- return OK;
+ if (fd->window_id != INVALID_WINDOW_ID) {
+ callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd->window_id);
+ }
+}
+
+Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+
+ WindowID window_id = _get_focused_window_or_popup();
+ if (!windows.has(window_id)) {
+ window_id = MAIN_WINDOW_ID;
+ }
+ String appname;
+ if (Engine::get_singleton()->is_editor_hint()) {
+ appname = "Godot.GodotEditor." + String(VERSION_BRANCH);
} else {
- return ERR_CANT_OPEN;
+ String name = GLOBAL_GET("application/config/name");
+ String version = GLOBAL_GET("application/config/version");
+ if (version.is_empty()) {
+ version = "0";
+ }
+ String clean_app_name = name.to_pascal_case();
+ for (int i = 0; i < clean_app_name.length(); i++) {
+ if (!is_ascii_alphanumeric_char(clean_app_name[i]) && clean_app_name[i] != '_' && clean_app_name[i] != '.') {
+ clean_app_name[i] = '_';
+ }
+ }
+ clean_app_name = clean_app_name.substr(0, 120 - version.length()).trim_suffix(".");
+ appname = "Godot." + clean_app_name + "." + version;
+ }
+
+ FileDialogData *fd = memnew(FileDialogData);
+ if (window_id != INVALID_WINDOW_ID) {
+ fd->hwnd_owner = windows[window_id].hWnd;
+ RECT crect;
+ GetWindowRect(fd->hwnd_owner, &crect);
+ fd->wrect = Rect2i(crect.left, crect.top, crect.right - crect.left, crect.bottom - crect.top);
+ } else {
+ fd->hwnd_owner = 0;
+ fd->wrect = Rect2i(CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT);
}
+ fd->appid = appname;
+ fd->title = p_title;
+ fd->current_directory = p_current_directory;
+ fd->root = p_root;
+ fd->filename = p_filename;
+ fd->show_hidden = p_show_hidden;
+ fd->mode = p_mode;
+ fd->window_id = window_id;
+ fd->filters = p_filters;
+ fd->options = p_options;
+ fd->callback = p_callback;
+ fd->options_in_cb = p_options_in_cb;
+ fd->finished.clear();
+ fd->close_requested.clear();
+
+ fd->listener_thread.start(DisplayServerWindows::_thread_fd_monitor, fd);
+
+ file_dialogs.push_back(fd);
+
+ return OK;
}
void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) {
@@ -3022,6 +3153,21 @@ void DisplayServerWindows::process_events() {
_process_key_events();
Input::get_singleton()->flush_buffered_events();
}
+
+ LocalVector<List<FileDialogData *>::Element *> to_remove;
+ for (List<FileDialogData *>::Element *E = file_dialogs.front(); E; E = E->next()) {
+ FileDialogData *fd = E->get();
+ if (fd->finished.is_set()) {
+ if (fd->listener_thread.is_started()) {
+ fd->listener_thread.wait_to_finish();
+ }
+ to_remove.push_back(E);
+ }
+ }
+ for (List<FileDialogData *>::Element *E : to_remove) {
+ memdelete(E->get());
+ E->erase();
+ }
}
void DisplayServerWindows::force_process_and_drop_events() {
@@ -5703,12 +5849,6 @@ Vector2i _get_device_ids(const String &p_device_name) {
return ids;
}
-typedef enum _SHC_PROCESS_DPI_AWARENESS {
- SHC_PROCESS_DPI_UNAWARE = 0,
- SHC_PROCESS_SYSTEM_DPI_AWARE = 1,
- SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2
-} SHC_PROCESS_DPI_AWARENESS;
-
bool DisplayServerWindows::is_dark_mode_supported() const {
return ux_theme_available;
}
@@ -6260,6 +6400,20 @@ void DisplayServerWindows::register_windows_driver() {
}
DisplayServerWindows::~DisplayServerWindows() {
+ LocalVector<List<FileDialogData *>::Element *> to_remove;
+ for (List<FileDialogData *>::Element *E = file_dialogs.front(); E; E = E->next()) {
+ FileDialogData *fd = E->get();
+ if (fd->listener_thread.is_started()) {
+ fd->close_requested.set();
+ fd->listener_thread.wait_to_finish();
+ }
+ to_remove.push_back(E);
+ }
+ for (List<FileDialogData *>::Element *E : to_remove) {
+ memdelete(E->get());
+ E->erase();
+ }
+
delete joypad;
touch_state.clear();
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 26328ba876..8a2f9f81a6 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -350,6 +350,12 @@ typedef struct {
ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
} ICONDIR, *LPICONDIR;
+typedef enum _SHC_PROCESS_DPI_AWARENESS {
+ SHC_PROCESS_DPI_UNAWARE = 0,
+ SHC_PROCESS_SYSTEM_DPI_AWARE = 1,
+ SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2,
+} SHC_PROCESS_DPI_AWARENESS;
+
class DisplayServerWindows : public DisplayServer {
// No need to register with GDCLASS, it's platform-specific and nothing is added.
@@ -544,6 +550,31 @@ class DisplayServerWindows : public DisplayServer {
IndicatorID indicator_id_counter = 0;
HashMap<IndicatorID, IndicatorData> indicators;
+ struct FileDialogData {
+ HWND hwnd_owner = 0;
+ Rect2i wrect;
+ String appid;
+ String title;
+ String current_directory;
+ String root;
+ String filename;
+ bool show_hidden = false;
+ DisplayServer::FileDialogMode mode = FileDialogMode::FILE_DIALOG_MODE_OPEN_ANY;
+ Vector<String> filters;
+ TypedArray<Dictionary> options;
+ WindowID window_id = DisplayServer::INVALID_WINDOW_ID;
+ Callable callback;
+ bool options_in_cb = false;
+ Thread listener_thread;
+ SafeFlag close_requested;
+ SafeFlag finished;
+ };
+ Mutex file_dialog_mutex;
+ List<FileDialogData *> file_dialogs;
+ HashMap<HWND, FileDialogData *> file_dialog_wnd;
+
+ static void _thread_fd_monitor(void *p_ud);
+
HashMap<int64_t, MouseButton> pointer_prev_button;
HashMap<int64_t, MouseButton> pointer_button;
HashMap<int64_t, LONG> pointer_down_time;
@@ -605,6 +636,7 @@ class DisplayServerWindows : public DisplayServer {
String _get_klid(HKL p_hkl) const;
public:
+ LRESULT WndProcFileDialog(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT MouseProc(int code, WPARAM wParam, LPARAM lParam);
diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp
index dfe321a435..023e9201fc 100644
--- a/scene/2d/parallax_layer.cpp
+++ b/scene/2d/parallax_layer.cpp
@@ -78,13 +78,7 @@ void ParallaxLayer::_update_mirroring() {
}
void ParallaxLayer::set_mirroring(const Size2 &p_mirroring) {
- mirroring = p_mirroring;
- if (mirroring.x < 0) {
- mirroring.x = 0;
- }
- if (mirroring.y < 0) {
- mirroring.y = 0;
- }
+ mirroring = p_mirroring.maxf(0);
_update_mirroring();
}
diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp
index 8aa50668eb..f9e8c831d1 100644
--- a/scene/2d/skeleton_2d.cpp
+++ b/scene/2d/skeleton_2d.cpp
@@ -39,19 +39,17 @@
#endif //TOOLS_ENABLED
bool Bone2D::_set(const StringName &p_path, const Variant &p_value) {
- String path = p_path;
-
- if (path.begins_with("auto_calculate_length_and_angle")) {
+ if (p_path == SNAME("auto_calculate_length_and_angle")) {
set_autocalculate_length_and_angle(p_value);
- } else if (path.begins_with("length")) {
+ } else if (p_path == SNAME("length")) {
set_length(p_value);
- } else if (path.begins_with("bone_angle")) {
+ } else if (p_path == SNAME("bone_angle")) {
set_bone_angle(Math::deg_to_rad(real_t(p_value)));
- } else if (path.begins_with("default_length")) {
+ } else if (p_path == SNAME("default_length")) {
set_length(p_value);
}
#ifdef TOOLS_ENABLED
- else if (path.begins_with("editor_settings/show_bone_gizmo")) {
+ else if (p_path == SNAME("editor_settings/show_bone_gizmo")) {
_editor_set_show_bone_gizmo(p_value);
}
#endif // TOOLS_ENABLED
@@ -63,19 +61,17 @@ bool Bone2D::_set(const StringName &p_path, const Variant &p_value) {
}
bool Bone2D::_get(const StringName &p_path, Variant &r_ret) const {
- String path = p_path;
-
- if (path.begins_with("auto_calculate_length_and_angle")) {
+ if (p_path == SNAME("auto_calculate_length_and_angle")) {
r_ret = get_autocalculate_length_and_angle();
- } else if (path.begins_with("length")) {
+ } else if (p_path == SNAME("length")) {
r_ret = get_length();
- } else if (path.begins_with("bone_angle")) {
+ } else if (p_path == SNAME("bone_angle")) {
r_ret = Math::rad_to_deg(get_bone_angle());
- } else if (path.begins_with("default_length")) {
+ } else if (p_path == SNAME("default_length")) {
r_ret = get_length();
}
#ifdef TOOLS_ENABLED
- else if (path.begins_with("editor_settings/show_bone_gizmo")) {
+ else if (p_path == SNAME("editor_settings/show_bone_gizmo")) {
r_ret = _editor_get_show_bone_gizmo();
}
#endif // TOOLS_ENABLED
@@ -330,9 +326,7 @@ bool Bone2D::_editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p
rel = (p_other_bone->get_global_position() - get_global_position());
rel = rel.rotated(-get_global_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation
} else {
- real_t angle_to_use = get_rotation() + bone_angle;
- rel = Vector2(cos(angle_to_use), sin(angle_to_use)) * (length * MIN(get_global_scale().x, get_global_scale().y));
- rel = rel.rotated(-get_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation
+ rel = Vector2(Math::cos(bone_angle), Math::sin(bone_angle)) * length * get_global_scale();
}
Vector2 relt = rel.rotated(Math_PI * 0.5).normalized() * bone_width;
@@ -518,23 +512,19 @@ Bone2D::~Bone2D() {
//////////////////////////////////////
bool Skeleton2D::_set(const StringName &p_path, const Variant &p_value) {
- String path = p_path;
-
- if (path.begins_with("modification_stack")) {
+ if (p_path == SNAME("modification_stack")) {
set_modification_stack(p_value);
return true;
}
- return true;
+ return false;
}
bool Skeleton2D::_get(const StringName &p_path, Variant &r_ret) const {
- String path = p_path;
-
- if (path.begins_with("modification_stack")) {
+ if (p_path == SNAME("modification_stack")) {
r_ret = get_modification_stack();
return true;
}
- return true;
+ return false;
}
void Skeleton2D::_get_property_list(List<PropertyInfo> *p_list) const {
diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp
index 4889512037..7fe1b7079a 100644
--- a/scene/3d/bone_attachment_3d.cpp
+++ b/scene/3d/bone_attachment_3d.cpp
@@ -33,7 +33,7 @@
void BoneAttachment3D::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "bone_name") {
- // Because it is a constant function, we cannot use the _get_skeleton_3d function.
+ // Because it is a constant function, we cannot use the get_skeleton function.
const Skeleton3D *parent = nullptr;
if (use_external_skeleton) {
if (external_skeleton_node_cache.is_valid()) {
@@ -134,7 +134,7 @@ void BoneAttachment3D::_update_external_skeleton_cache() {
}
void BoneAttachment3D::_check_bind() {
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
if (sk && !bound) {
if (bone_idx <= -1) {
@@ -148,7 +148,7 @@ void BoneAttachment3D::_check_bind() {
}
}
-Skeleton3D *BoneAttachment3D::_get_skeleton3d() {
+Skeleton3D *BoneAttachment3D::get_skeleton() {
if (use_external_skeleton) {
if (external_skeleton_node_cache.is_valid()) {
return Object::cast_to<Skeleton3D>(ObjectDB::get_instance(external_skeleton_node_cache));
@@ -166,7 +166,7 @@ Skeleton3D *BoneAttachment3D::_get_skeleton3d() {
void BoneAttachment3D::_check_unbind() {
if (bound) {
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
if (sk) {
sk->disconnect(SceneStringName(skeleton_updated), callable_mp(this, &BoneAttachment3D::on_skeleton_update));
@@ -181,7 +181,7 @@ void BoneAttachment3D::_transform_changed() {
}
if (override_pose && !overriding) {
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
ERR_FAIL_NULL_MSG(sk, "Cannot override pose: Skeleton not found!");
ERR_FAIL_INDEX_MSG(bone_idx, sk->get_bone_count(), "Cannot override pose: Bone index is out of range!");
@@ -200,7 +200,7 @@ void BoneAttachment3D::_transform_changed() {
void BoneAttachment3D::set_bone_name(const String &p_name) {
bone_name = p_name;
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
if (sk) {
set_bone_idx(sk->find_bone(bone_name));
}
@@ -217,7 +217,7 @@ void BoneAttachment3D::set_bone_idx(const int &p_idx) {
bone_idx = p_idx;
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
if (sk) {
if (bone_idx <= -1 || bone_idx >= sk->get_bone_count()) {
WARN_PRINT("Bone index out of range! Cannot connect BoneAttachment to node!");
@@ -247,7 +247,7 @@ void BoneAttachment3D::set_override_pose(bool p_override) {
set_notify_transform(override_pose);
set_process_internal(override_pose);
if (!override_pose && bone_idx >= 0) {
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
if (sk) {
sk->reset_bone_pose(bone_idx);
}
@@ -318,7 +318,7 @@ void BoneAttachment3D::on_skeleton_update() {
}
updating = true;
if (bone_idx >= 0) {
- Skeleton3D *sk = _get_skeleton3d();
+ Skeleton3D *sk = get_skeleton();
if (sk) {
if (!override_pose) {
if (use_external_skeleton) {
@@ -369,6 +369,8 @@ BoneAttachment3D::BoneAttachment3D() {
}
void BoneAttachment3D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_skeleton"), &BoneAttachment3D::get_skeleton);
+
ClassDB::bind_method(D_METHOD("set_bone_name", "bone_name"), &BoneAttachment3D::set_bone_name);
ClassDB::bind_method(D_METHOD("get_bone_name"), &BoneAttachment3D::get_bone_name);
diff --git a/scene/3d/bone_attachment_3d.h b/scene/3d/bone_attachment_3d.h
index e19180b0ea..5435c4ad0c 100644
--- a/scene/3d/bone_attachment_3d.h
+++ b/scene/3d/bone_attachment_3d.h
@@ -57,7 +57,6 @@ class BoneAttachment3D : public Node3D {
bool updating = false;
void _transform_changed();
void _update_external_skeleton_cache();
- Skeleton3D *_get_skeleton3d();
protected:
void _validate_property(PropertyInfo &p_property) const;
@@ -79,6 +78,8 @@ public:
virtual PackedStringArray get_configuration_warnings() const override;
+ Skeleton3D *get_skeleton();
+
void set_bone_name(const String &p_name);
String get_bone_name() const;
diff --git a/scene/3d/multimesh_instance_3d.cpp b/scene/3d/multimesh_instance_3d.cpp
index 55d6e49e6c..2eef1dbbf4 100644
--- a/scene/3d/multimesh_instance_3d.cpp
+++ b/scene/3d/multimesh_instance_3d.cpp
@@ -30,16 +30,35 @@
#include "multimesh_instance_3d.h"
+void MultiMeshInstance3D::_refresh_interpolated() {
+ if (is_inside_tree() && multimesh.is_valid()) {
+ bool interpolated = is_physics_interpolated_and_enabled();
+ multimesh->set_physics_interpolated(interpolated);
+ }
+}
+
+void MultiMeshInstance3D::_physics_interpolated_changed() {
+ VisualInstance3D::_physics_interpolated_changed();
+ _refresh_interpolated();
+}
+
void MultiMeshInstance3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_multimesh", "multimesh"), &MultiMeshInstance3D::set_multimesh);
ClassDB::bind_method(D_METHOD("get_multimesh"), &MultiMeshInstance3D::get_multimesh);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multimesh", PROPERTY_HINT_RESOURCE_TYPE, "MultiMesh"), "set_multimesh", "get_multimesh");
}
+void MultiMeshInstance3D::_notification(int p_what) {
+ if (p_what == NOTIFICATION_ENTER_TREE) {
+ _refresh_interpolated();
+ }
+}
+
void MultiMeshInstance3D::set_multimesh(const Ref<MultiMesh> &p_multimesh) {
multimesh = p_multimesh;
if (multimesh.is_valid()) {
set_base(multimesh->get_rid());
+ _refresh_interpolated();
} else {
set_base(RID());
}
diff --git a/scene/3d/multimesh_instance_3d.h b/scene/3d/multimesh_instance_3d.h
index 404f31d1e3..c9507b1047 100644
--- a/scene/3d/multimesh_instance_3d.h
+++ b/scene/3d/multimesh_instance_3d.h
@@ -39,9 +39,12 @@ class MultiMeshInstance3D : public GeometryInstance3D {
Ref<MultiMesh> multimesh;
+ void _refresh_interpolated();
+
protected:
+ virtual void _physics_interpolated_changed() override;
static void _bind_methods();
- // bind helpers
+ void _notification(int p_what);
public:
void set_multimesh(const Ref<MultiMesh> &p_multimesh);
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index 5a4e176e99..c6ece84cdd 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -69,13 +69,13 @@ SkinReference::~SkinReference() {
///////////////////////////////////////
bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
- String path = p_path;
-
#ifndef DISABLE_DEPRECATED
- if (path.begins_with("animate_physical_bones")) {
+ if (p_path == SNAME("animate_physical_bones")) {
set_animate_physical_bones(p_value);
+ return true;
}
#endif
+ String path = p_path;
if (!path.begins_with("bones/")) {
return false;
@@ -139,13 +139,13 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
}
bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
- String path = p_path;
-
#ifndef DISABLE_DEPRECATED
- if (path.begins_with("animate_physical_bones")) {
+ if (p_path == SNAME("animate_physical_bones")) {
r_ret = get_animate_physical_bones();
+ return true;
}
#endif
+ String path = p_path;
if (!path.begins_with("bones/")) {
return false;
diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp
index 3f4b962641..b71f9bc0c4 100644
--- a/scene/3d/xr_nodes.cpp
+++ b/scene/3d/xr_nodes.cpp
@@ -303,6 +303,8 @@ StringName XRNode3D::get_pose_name() const {
void XRNode3D::set_show_when_tracked(bool p_show) {
show_when_tracked = p_show;
+
+ _update_visibility();
}
bool XRNode3D::get_show_when_tracked() const {
@@ -361,6 +363,9 @@ void XRNode3D::_bind_tracker() {
if (pose.is_valid()) {
set_transform(pose->get_adjusted_transform());
_set_has_tracking_data(pose->get_has_tracking_data());
+ } else {
+ // Pose has been invalidated or was never set.
+ _set_has_tracking_data(false);
}
}
}
@@ -407,6 +412,10 @@ void XRNode3D::_pose_lost_tracking(const Ref<XRPose> &p_pose) {
}
void XRNode3D::_set_has_tracking_data(bool p_has_tracking_data) {
+ // Always update our visibility, we may have set our tracking data
+ // when conditions weren't right.
+ _update_visibility();
+
// Ignore if the has_tracking_data state isn't changing.
if (p_has_tracking_data == has_tracking_data) {
return;
@@ -415,10 +424,19 @@ void XRNode3D::_set_has_tracking_data(bool p_has_tracking_data) {
// Handle change of has_tracking_data.
has_tracking_data = p_has_tracking_data;
emit_signal(SNAME("tracking_changed"), has_tracking_data);
+}
+void XRNode3D::_update_visibility() {
// If configured, show or hide the node based on tracking data.
if (show_when_tracked) {
- set_visible(has_tracking_data);
+ // Only react to this if we have a primary interface.
+ XRServer *xr_server = XRServer::get_singleton();
+ if (xr_server != nullptr) {
+ Ref<XRInterface> xr_interface = xr_server->get_primary_interface();
+ if (xr_interface.is_valid()) {
+ set_visible(has_tracking_data);
+ }
+ }
}
}
diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h
index a42f6d470f..94c3923433 100644
--- a/scene/3d/xr_nodes.h
+++ b/scene/3d/xr_nodes.h
@@ -95,6 +95,8 @@ protected:
void _pose_lost_tracking(const Ref<XRPose> &p_pose);
void _set_has_tracking_data(bool p_has_tracking_data);
+ void _update_visibility();
+
public:
void _validate_property(PropertyInfo &p_property) const;
void set_tracker(const StringName &p_tracker_name);
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 00b9a3478a..412eb83515 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -765,7 +765,7 @@ void CodeEdit::_backspace_internal(int p_caret) {
continue;
}
- if (to_line > 0 && _is_line_hidden(to_line - 1)) {
+ if (to_line > 0 && to_column == 0 && _is_line_hidden(to_line - 1)) {
unfold_line(to_line - 1);
}
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 4f8d818a6c..3264733548 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -121,8 +121,9 @@ void MenuBar::_open_popup(int p_index, bool p_focus_item) {
}
Rect2 item_rect = _get_menu_item_rect(p_index);
- Point2 screen_pos = get_screen_position() + item_rect.position * get_viewport()->get_canvas_transform().get_scale();
- Size2 screen_size = item_rect.size * get_viewport()->get_canvas_transform().get_scale();
+ Size2 canvas_scale = get_canvas_transform().get_scale();
+ Point2 screen_pos = get_screen_position() + item_rect.position * canvas_scale;
+ Size2 screen_size = item_rect.size * canvas_scale;
active_menu = p_index;
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 5c46abc732..de6d49761b 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -2809,9 +2809,11 @@ Node *Node::duplicate(int p_flags) const {
ERR_THREAD_GUARD_V(nullptr);
Node *dupe = _duplicate(p_flags);
+ ERR_FAIL_NULL_V_MSG(dupe, nullptr, "Failed to duplicate node.");
+
_duplicate_properties(this, this, dupe, p_flags);
- if (dupe && (p_flags & DUPLICATE_SIGNALS)) {
+ if (p_flags & DUPLICATE_SIGNALS) {
_duplicate_signals(this, dupe);
}
@@ -2827,6 +2829,8 @@ Node *Node::duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap, con
int flags = DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_USE_INSTANTIATION | DUPLICATE_FROM_EDITOR;
Node *dupe = _duplicate(flags, &r_duplimap);
+ ERR_FAIL_NULL_V_MSG(dupe, nullptr, "Failed to duplicate node.");
+
_duplicate_properties(this, this, dupe, flags);
// This is used by SceneTreeDock's paste functionality. When pasting to foreign scene, resources are duplicated.
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index c85fda2371..0036247625 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -5070,6 +5070,7 @@ void SubViewport::_internal_set_size(const Size2i &p_size, bool p_force) {
if (c) {
c->update_minimum_size();
+ c->queue_redraw();
}
}
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index c2704a3848..19fb62df2e 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -1602,7 +1602,7 @@ Size2 Window::_get_contents_minimum_size() const {
}
}
- return max;
+ return max * content_scale_factor;
}
void Window::child_controls_changed() {
diff --git a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
index 686560829b..ee6fc61af3 100644
--- a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
+++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp
@@ -37,6 +37,7 @@ void NavigationMeshSourceGeometryData2D::clear() {
traversable_outlines.clear();
obstruction_outlines.clear();
_projected_obstructions.clear();
+ bounds_dirty = true;
}
bool NavigationMeshSourceGeometryData2D::has_data() {
@@ -47,16 +48,19 @@ bool NavigationMeshSourceGeometryData2D::has_data() {
void NavigationMeshSourceGeometryData2D::clear_projected_obstructions() {
RWLockWrite write_lock(geometry_rwlock);
_projected_obstructions.clear();
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData2D::_set_traversable_outlines(const Vector<Vector<Vector2>> &p_traversable_outlines) {
RWLockWrite write_lock(geometry_rwlock);
traversable_outlines = p_traversable_outlines;
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData2D::_set_obstruction_outlines(const Vector<Vector<Vector2>> &p_obstruction_outlines) {
RWLockWrite write_lock(geometry_rwlock);
obstruction_outlines = p_obstruction_outlines;
+ bounds_dirty = true;
}
const Vector<Vector<Vector2>> &NavigationMeshSourceGeometryData2D::_get_traversable_outlines() const {
@@ -73,6 +77,7 @@ void NavigationMeshSourceGeometryData2D::_add_traversable_outline(const Vector<V
if (p_shape_outline.size() > 1) {
RWLockWrite write_lock(geometry_rwlock);
traversable_outlines.push_back(p_shape_outline);
+ bounds_dirty = true;
}
}
@@ -80,6 +85,7 @@ void NavigationMeshSourceGeometryData2D::_add_obstruction_outline(const Vector<V
if (p_shape_outline.size() > 1) {
RWLockWrite write_lock(geometry_rwlock);
obstruction_outlines.push_back(p_shape_outline);
+ bounds_dirty = true;
}
}
@@ -89,6 +95,7 @@ void NavigationMeshSourceGeometryData2D::set_traversable_outlines(const TypedArr
for (int i = 0; i < p_traversable_outlines.size(); i++) {
traversable_outlines.write[i] = p_traversable_outlines[i];
}
+ bounds_dirty = true;
}
TypedArray<Vector<Vector2>> NavigationMeshSourceGeometryData2D::get_traversable_outlines() const {
@@ -108,6 +115,7 @@ void NavigationMeshSourceGeometryData2D::set_obstruction_outlines(const TypedArr
for (int i = 0; i < p_obstruction_outlines.size(); i++) {
obstruction_outlines.write[i] = p_obstruction_outlines[i];
}
+ bounds_dirty = true;
}
TypedArray<Vector<Vector2>> NavigationMeshSourceGeometryData2D::get_obstruction_outlines() const {
@@ -128,6 +136,7 @@ void NavigationMeshSourceGeometryData2D::append_traversable_outlines(const Typed
for (int i = traversable_outlines_size; i < p_traversable_outlines.size(); i++) {
traversable_outlines.write[i] = p_traversable_outlines[i];
}
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData2D::append_obstruction_outlines(const TypedArray<Vector<Vector2>> &p_obstruction_outlines) {
@@ -137,6 +146,7 @@ void NavigationMeshSourceGeometryData2D::append_obstruction_outlines(const Typed
for (int i = obstruction_outlines_size; i < p_obstruction_outlines.size(); i++) {
obstruction_outlines.write[i] = p_obstruction_outlines[i];
}
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData2D::add_traversable_outline(const PackedVector2Array &p_shape_outline) {
@@ -148,6 +158,7 @@ void NavigationMeshSourceGeometryData2D::add_traversable_outline(const PackedVec
traversable_outline.write[i] = p_shape_outline[i];
}
traversable_outlines.push_back(traversable_outline);
+ bounds_dirty = true;
}
}
@@ -160,6 +171,7 @@ void NavigationMeshSourceGeometryData2D::add_obstruction_outline(const PackedVec
obstruction_outline.write[i] = p_shape_outline[i];
}
obstruction_outlines.push_back(obstruction_outline);
+ bounds_dirty = true;
}
}
@@ -176,6 +188,7 @@ void NavigationMeshSourceGeometryData2D::merge(const Ref<NavigationMeshSourceGeo
traversable_outlines.append_array(other_traversable_outlines);
obstruction_outlines.append_array(other_obstruction_outlines);
_projected_obstructions.append_array(other_projected_obstructions);
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData2D::add_projected_obstruction(const Vector<Vector2> &p_vertices, bool p_carve) {
@@ -195,6 +208,7 @@ void NavigationMeshSourceGeometryData2D::add_projected_obstruction(const Vector<
RWLockWrite write_lock(geometry_rwlock);
_projected_obstructions.push_back(projected_obstruction);
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData2D::set_projected_obstructions(const Array &p_array) {
@@ -217,6 +231,7 @@ void NavigationMeshSourceGeometryData2D::set_projected_obstructions(const Array
RWLockWrite write_lock(geometry_rwlock);
_projected_obstructions.push_back(projected_obstruction);
+ bounds_dirty = true;
}
}
@@ -266,6 +281,7 @@ void NavigationMeshSourceGeometryData2D::set_data(const Vector<Vector<Vector2>>
traversable_outlines = p_traversable_outlines;
obstruction_outlines = p_obstruction_outlines;
_projected_obstructions = p_projected_obstructions;
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData2D::get_data(Vector<Vector<Vector2>> &r_traversable_outlines, Vector<Vector<Vector2>> &r_obstruction_outlines, Vector<ProjectedObstruction> &r_projected_obstructions) {
@@ -275,6 +291,58 @@ void NavigationMeshSourceGeometryData2D::get_data(Vector<Vector<Vector2>> &r_tra
r_projected_obstructions = _projected_obstructions;
}
+Rect2 NavigationMeshSourceGeometryData2D::get_bounds() {
+ geometry_rwlock.read_lock();
+
+ if (bounds_dirty) {
+ geometry_rwlock.read_unlock();
+ RWLockWrite write_lock(geometry_rwlock);
+
+ bounds_dirty = false;
+ bounds = Rect2();
+ bool first_vertex = true;
+
+ for (const Vector<Vector2> &traversable_outline : traversable_outlines) {
+ for (const Vector2 &traversable_point : traversable_outline) {
+ if (first_vertex) {
+ first_vertex = false;
+ bounds.position = traversable_point;
+ } else {
+ bounds.expand_to(traversable_point);
+ }
+ }
+ }
+
+ for (const Vector<Vector2> &obstruction_outline : obstruction_outlines) {
+ for (const Vector2 &obstruction_point : obstruction_outline) {
+ if (first_vertex) {
+ first_vertex = false;
+ bounds.position = obstruction_point;
+ } else {
+ bounds.expand_to(obstruction_point);
+ }
+ }
+ }
+
+ for (const ProjectedObstruction &projected_obstruction : _projected_obstructions) {
+ for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) {
+ const Vector2 vertex = Vector2(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]);
+ if (first_vertex) {
+ first_vertex = false;
+ bounds.position = vertex;
+ } else {
+ bounds.expand_to(vertex);
+ }
+ }
+ }
+ } else {
+ geometry_rwlock.read_unlock();
+ }
+
+ RWLockRead read_lock(geometry_rwlock);
+ return bounds;
+}
+
void NavigationMeshSourceGeometryData2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &NavigationMeshSourceGeometryData2D::clear);
ClassDB::bind_method(D_METHOD("has_data"), &NavigationMeshSourceGeometryData2D::has_data);
@@ -298,6 +366,8 @@ void NavigationMeshSourceGeometryData2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_projected_obstructions", "projected_obstructions"), &NavigationMeshSourceGeometryData2D::set_projected_obstructions);
ClassDB::bind_method(D_METHOD("get_projected_obstructions"), &NavigationMeshSourceGeometryData2D::get_projected_obstructions);
+ ClassDB::bind_method(D_METHOD("get_bounds"), &NavigationMeshSourceGeometryData2D::get_bounds);
+
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "traversable_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_traversable_outlines", "get_traversable_outlines");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "obstruction_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_obstruction_outlines", "get_obstruction_outlines");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "projected_obstructions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_projected_obstructions", "get_projected_obstructions");
diff --git a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h
index 01e97eee48..b29c106fb5 100644
--- a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h
+++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.h
@@ -42,6 +42,9 @@ class NavigationMeshSourceGeometryData2D : public Resource {
Vector<Vector<Vector2>> traversable_outlines;
Vector<Vector<Vector2>> obstruction_outlines;
+ Rect2 bounds;
+ bool bounds_dirty = true;
+
public:
struct ProjectedObstruction;
@@ -103,6 +106,8 @@ public:
void set_data(const Vector<Vector<Vector2>> &p_traversable_outlines, const Vector<Vector<Vector2>> &p_obstruction_outlines, Vector<ProjectedObstruction> &p_projected_obstructions);
void get_data(Vector<Vector<Vector2>> &r_traversable_outlines, Vector<Vector<Vector2>> &r_obstruction_outlines, Vector<ProjectedObstruction> &r_projected_obstructions);
+ Rect2 get_bounds();
+
NavigationMeshSourceGeometryData2D() {}
~NavigationMeshSourceGeometryData2D() { clear(); }
};
diff --git a/scene/resources/2d/navigation_polygon.cpp b/scene/resources/2d/navigation_polygon.cpp
index 8218197fd9..3dfa906e3b 100644
--- a/scene/resources/2d/navigation_polygon.cpp
+++ b/scene/resources/2d/navigation_polygon.cpp
@@ -436,6 +436,15 @@ real_t NavigationPolygon::get_border_size() const {
return border_size;
}
+void NavigationPolygon::set_sample_partition_type(SamplePartitionType p_value) {
+ ERR_FAIL_INDEX(p_value, SAMPLE_PARTITION_MAX);
+ partition_type = p_value;
+}
+
+NavigationPolygon::SamplePartitionType NavigationPolygon::get_sample_partition_type() const {
+ return partition_type;
+}
+
void NavigationPolygon::set_parsed_geometry_type(ParsedGeometryType p_geometry_type) {
ERR_FAIL_INDEX(p_geometry_type, PARSED_GEOMETRY_MAX);
parsed_geometry_type = p_geometry_type;
@@ -550,6 +559,9 @@ void NavigationPolygon::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_border_size", "border_size"), &NavigationPolygon::set_border_size);
ClassDB::bind_method(D_METHOD("get_border_size"), &NavigationPolygon::get_border_size);
+ ClassDB::bind_method(D_METHOD("set_sample_partition_type", "sample_partition_type"), &NavigationPolygon::set_sample_partition_type);
+ ClassDB::bind_method(D_METHOD("get_sample_partition_type"), &NavigationPolygon::get_sample_partition_type);
+
ClassDB::bind_method(D_METHOD("set_parsed_geometry_type", "geometry_type"), &NavigationPolygon::set_parsed_geometry_type);
ClassDB::bind_method(D_METHOD("get_parsed_geometry_type"), &NavigationPolygon::get_parsed_geometry_type);
@@ -579,6 +591,8 @@ void NavigationPolygon::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_outlines", "_get_outlines");
+ ADD_GROUP("Sampling", "sample_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "sample_partition_type", PROPERTY_HINT_ENUM, "Convex Partition,Triangulate"), "set_sample_partition_type", "get_sample_partition_type");
ADD_GROUP("Geometry", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "parsed_geometry_type", PROPERTY_HINT_ENUM, "Mesh Instances,Static Colliders,Meshes and Static Colliders"), "set_parsed_geometry_type", "get_parsed_geometry_type");
ADD_PROPERTY(PropertyInfo(Variant::INT, "parsed_collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_parsed_collision_mask", "get_parsed_collision_mask");
@@ -595,6 +609,10 @@ void NavigationPolygon::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::RECT2, "baking_rect"), "set_baking_rect", "get_baking_rect");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "baking_rect_offset"), "set_baking_rect_offset", "get_baking_rect_offset");
+ BIND_ENUM_CONSTANT(SAMPLE_PARTITION_CONVEX_PARTITION);
+ BIND_ENUM_CONSTANT(SAMPLE_PARTITION_TRIANGULATE);
+ BIND_ENUM_CONSTANT(SAMPLE_PARTITION_MAX);
+
BIND_ENUM_CONSTANT(PARSED_GEOMETRY_MESH_INSTANCES);
BIND_ENUM_CONSTANT(PARSED_GEOMETRY_STATIC_COLLIDERS);
BIND_ENUM_CONSTANT(PARSED_GEOMETRY_BOTH);
diff --git a/scene/resources/2d/navigation_polygon.h b/scene/resources/2d/navigation_polygon.h
index f43335b397..86bda47ace 100644
--- a/scene/resources/2d/navigation_polygon.h
+++ b/scene/resources/2d/navigation_polygon.h
@@ -71,6 +71,11 @@ public:
Rect2 _edit_get_rect() const;
bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const;
#endif
+ enum SamplePartitionType {
+ SAMPLE_PARTITION_CONVEX_PARTITION = 0,
+ SAMPLE_PARTITION_TRIANGULATE,
+ SAMPLE_PARTITION_MAX
+ };
enum ParsedGeometryType {
PARSED_GEOMETRY_MESH_INSTANCES = 0,
@@ -88,6 +93,7 @@ public:
real_t agent_radius = 10.0f;
+ SamplePartitionType partition_type = SAMPLE_PARTITION_CONVEX_PARTITION;
ParsedGeometryType parsed_geometry_type = PARSED_GEOMETRY_BOTH;
uint32_t parsed_collision_mask = 0xFFFFFFFF;
@@ -119,6 +125,9 @@ public:
Vector<int> get_polygon(int p_idx);
void clear_polygons();
+ void set_sample_partition_type(SamplePartitionType p_value);
+ SamplePartitionType get_sample_partition_type() const;
+
void set_parsed_geometry_type(ParsedGeometryType p_geometry_type);
ParsedGeometryType get_parsed_geometry_type() const;
@@ -162,6 +171,7 @@ public:
~NavigationPolygon() {}
};
+VARIANT_ENUM_CAST(NavigationPolygon::SamplePartitionType);
VARIANT_ENUM_CAST(NavigationPolygon::ParsedGeometryType);
VARIANT_ENUM_CAST(NavigationPolygon::SourceGeometryMode);
diff --git a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
index 4c9f381aba..c55e25fcae 100644
--- a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
+++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp
@@ -33,6 +33,7 @@
void NavigationMeshSourceGeometryData3D::set_vertices(const Vector<float> &p_vertices) {
RWLockWrite write_lock(geometry_rwlock);
vertices = p_vertices;
+ bounds_dirty = true;
}
const Vector<float> &NavigationMeshSourceGeometryData3D::get_vertices() const {
@@ -44,6 +45,7 @@ void NavigationMeshSourceGeometryData3D::set_indices(const Vector<int> &p_indice
ERR_FAIL_COND(vertices.size() < p_indices.size());
RWLockWrite write_lock(geometry_rwlock);
indices = p_indices;
+ bounds_dirty = true;
}
const Vector<int> &NavigationMeshSourceGeometryData3D::get_indices() const {
@@ -63,6 +65,7 @@ void NavigationMeshSourceGeometryData3D::append_arrays(const Vector<float> &p_ve
for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) {
indices.set(i, indices[i] + number_of_vertices_before_merge / 3);
}
+ bounds_dirty = true;
}
bool NavigationMeshSourceGeometryData3D::has_data() {
@@ -75,11 +78,13 @@ void NavigationMeshSourceGeometryData3D::clear() {
vertices.clear();
indices.clear();
_projected_obstructions.clear();
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::clear_projected_obstructions() {
RWLockWrite write_lock(geometry_rwlock);
_projected_obstructions.clear();
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::_add_vertex(const Vector3 &p_vec3) {
@@ -207,12 +212,14 @@ void NavigationMeshSourceGeometryData3D::add_mesh_array(const Array &p_mesh_arra
ERR_FAIL_COND(p_mesh_array.size() != Mesh::ARRAY_MAX);
RWLockWrite write_lock(geometry_rwlock);
_add_mesh_array(p_mesh_array, root_node_transform * p_xform);
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform) {
ERR_FAIL_COND(p_faces.size() % 3 != 0);
RWLockWrite write_lock(geometry_rwlock);
_add_faces(p_faces, root_node_transform * p_xform);
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry) {
@@ -236,6 +243,7 @@ void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeo
}
_projected_obstructions.append_array(other_projected_obstructions);
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::add_projected_obstruction(const Vector<Vector3> &p_vertices, float p_elevation, float p_height, bool p_carve) {
@@ -259,6 +267,7 @@ void NavigationMeshSourceGeometryData3D::add_projected_obstruction(const Vector<
RWLockWrite write_lock(geometry_rwlock);
_projected_obstructions.push_back(projected_obstruction);
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::set_projected_obstructions(const Array &p_array) {
@@ -285,6 +294,7 @@ void NavigationMeshSourceGeometryData3D::set_projected_obstructions(const Array
RWLockWrite write_lock(geometry_rwlock);
_projected_obstructions.push_back(projected_obstruction);
+ bounds_dirty = true;
}
}
@@ -336,6 +346,7 @@ void NavigationMeshSourceGeometryData3D::set_data(const Vector<float> &p_vertice
vertices = p_vertices;
indices = p_indices;
_projected_obstructions = p_projected_obstructions;
+ bounds_dirty = true;
}
void NavigationMeshSourceGeometryData3D::get_data(Vector<float> &r_vertices, Vector<int> &r_indices, Vector<ProjectedObstruction> &r_projected_obstructions) {
@@ -345,6 +356,45 @@ void NavigationMeshSourceGeometryData3D::get_data(Vector<float> &r_vertices, Vec
r_projected_obstructions = _projected_obstructions;
}
+AABB NavigationMeshSourceGeometryData3D::get_bounds() {
+ geometry_rwlock.read_lock();
+
+ if (bounds_dirty) {
+ geometry_rwlock.read_unlock();
+ RWLockWrite write_lock(geometry_rwlock);
+
+ bounds_dirty = false;
+ bounds = AABB();
+ bool first_vertex = true;
+
+ for (int i = 0; i < vertices.size() / 3; i++) {
+ const Vector3 vertex = Vector3(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]);
+ if (first_vertex) {
+ first_vertex = false;
+ bounds.position = vertex;
+ } else {
+ bounds.expand_to(vertex);
+ }
+ }
+ for (const ProjectedObstruction &projected_obstruction : _projected_obstructions) {
+ for (int i = 0; i < projected_obstruction.vertices.size() / 3; i++) {
+ const Vector3 vertex = Vector3(projected_obstruction.vertices[i * 3], projected_obstruction.vertices[i * 3 + 1], projected_obstruction.vertices[i * 3 + 2]);
+ if (first_vertex) {
+ first_vertex = false;
+ bounds.position = vertex;
+ } else {
+ bounds.expand_to(vertex);
+ }
+ }
+ }
+ } else {
+ geometry_rwlock.read_unlock();
+ }
+
+ RWLockRead read_lock(geometry_rwlock);
+ return bounds;
+}
+
void NavigationMeshSourceGeometryData3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationMeshSourceGeometryData3D::set_vertices);
ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationMeshSourceGeometryData3D::get_vertices);
@@ -367,6 +417,8 @@ void NavigationMeshSourceGeometryData3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_projected_obstructions", "projected_obstructions"), &NavigationMeshSourceGeometryData3D::set_projected_obstructions);
ClassDB::bind_method(D_METHOD("get_projected_obstructions"), &NavigationMeshSourceGeometryData3D::get_projected_obstructions);
+ ClassDB::bind_method(D_METHOD("get_bounds"), &NavigationMeshSourceGeometryData3D::get_bounds);
+
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_indices", "get_indices");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "projected_obstructions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_projected_obstructions", "get_projected_obstructions");
diff --git a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h
index a8e613a51d..d7e3c3071c 100644
--- a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h
+++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.h
@@ -41,6 +41,9 @@ class NavigationMeshSourceGeometryData3D : public Resource {
Vector<float> vertices;
Vector<int> indices;
+ AABB bounds;
+ bool bounds_dirty = true;
+
public:
struct ProjectedObstruction;
@@ -101,6 +104,8 @@ public:
void set_data(const Vector<float> &p_vertices, const Vector<int> &p_indices, Vector<ProjectedObstruction> &p_projected_obstructions);
void get_data(Vector<float> &r_vertices, Vector<int> &r_indices, Vector<ProjectedObstruction> &r_projected_obstructions);
+ AABB get_bounds();
+
NavigationMeshSourceGeometryData3D() {}
~NavigationMeshSourceGeometryData3D() { clear(); }
};
diff --git a/scene/resources/multimesh.cpp b/scene/resources/multimesh.cpp
index 8cddfb5840..bf3caa1edd 100644
--- a/scene/resources/multimesh.cpp
+++ b/scene/resources/multimesh.cpp
@@ -202,6 +202,10 @@ Vector<float> MultiMesh::get_buffer() const {
return RS::get_singleton()->multimesh_get_buffer(multimesh);
}
+void MultiMesh::set_buffer_interpolated(const Vector<float> &p_buffer_curr, const Vector<float> &p_buffer_prev) {
+ RS::get_singleton()->multimesh_set_buffer_interpolated(multimesh, p_buffer_curr, p_buffer_prev);
+}
+
void MultiMesh::set_mesh(const Ref<Mesh> &p_mesh) {
mesh = p_mesh;
if (!mesh.is_null()) {
@@ -236,6 +240,11 @@ int MultiMesh::get_visible_instance_count() const {
return visible_instance_count;
}
+void MultiMesh::set_physics_interpolation_quality(PhysicsInterpolationQuality p_quality) {
+ _physics_interpolation_quality = p_quality;
+ RenderingServer::get_singleton()->multimesh_set_physics_interpolation_quality(multimesh, (RS::MultimeshPhysicsInterpolationQuality)p_quality);
+}
+
void MultiMesh::set_instance_transform(int p_instance, const Transform3D &p_transform) {
RenderingServer::get_singleton()->multimesh_instance_set_transform(multimesh, p_instance, p_transform);
}
@@ -269,6 +278,14 @@ Color MultiMesh::get_instance_custom_data(int p_instance) const {
return RenderingServer::get_singleton()->multimesh_instance_get_custom_data(multimesh, p_instance);
}
+void MultiMesh::reset_instance_physics_interpolation(int p_instance) {
+ RenderingServer::get_singleton()->multimesh_instance_reset_physics_interpolation(multimesh, p_instance);
+}
+
+void MultiMesh::set_physics_interpolated(bool p_interpolated) {
+ RenderingServer::get_singleton()->multimesh_set_physics_interpolated(multimesh, p_interpolated);
+}
+
void MultiMesh::set_custom_aabb(const AABB &p_custom) {
custom_aabb = p_custom;
RS::get_singleton()->multimesh_set_custom_aabb(multimesh, custom_aabb);
@@ -328,6 +345,8 @@ void MultiMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_instance_count"), &MultiMesh::get_instance_count);
ClassDB::bind_method(D_METHOD("set_visible_instance_count", "count"), &MultiMesh::set_visible_instance_count);
ClassDB::bind_method(D_METHOD("get_visible_instance_count"), &MultiMesh::get_visible_instance_count);
+ ClassDB::bind_method(D_METHOD("set_physics_interpolation_quality", "quality"), &MultiMesh::set_physics_interpolation_quality);
+ ClassDB::bind_method(D_METHOD("get_physics_interpolation_quality"), &MultiMesh::get_physics_interpolation_quality);
ClassDB::bind_method(D_METHOD("set_instance_transform", "instance", "transform"), &MultiMesh::set_instance_transform);
ClassDB::bind_method(D_METHOD("set_instance_transform_2d", "instance", "transform"), &MultiMesh::set_instance_transform_2d);
ClassDB::bind_method(D_METHOD("get_instance_transform", "instance"), &MultiMesh::get_instance_transform);
@@ -336,6 +355,7 @@ void MultiMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_instance_color", "instance"), &MultiMesh::get_instance_color);
ClassDB::bind_method(D_METHOD("set_instance_custom_data", "instance", "custom_data"), &MultiMesh::set_instance_custom_data);
ClassDB::bind_method(D_METHOD("get_instance_custom_data", "instance"), &MultiMesh::get_instance_custom_data);
+ ClassDB::bind_method(D_METHOD("reset_instance_physics_interpolation", "instance"), &MultiMesh::reset_instance_physics_interpolation);
ClassDB::bind_method(D_METHOD("set_custom_aabb", "aabb"), &MultiMesh::set_custom_aabb);
ClassDB::bind_method(D_METHOD("get_custom_aabb"), &MultiMesh::get_custom_aabb);
ClassDB::bind_method(D_METHOD("get_aabb"), &MultiMesh::get_aabb);
@@ -343,6 +363,8 @@ void MultiMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_buffer"), &MultiMesh::get_buffer);
ClassDB::bind_method(D_METHOD("set_buffer", "buffer"), &MultiMesh::set_buffer);
+ ClassDB::bind_method(D_METHOD("set_buffer_interpolated", "buffer_curr", "buffer_prev"), &MultiMesh::set_buffer_interpolated);
+
ADD_PROPERTY(PropertyInfo(Variant::INT, "transform_format", PROPERTY_HINT_ENUM, "2D,3D"), "set_transform_format", "get_transform_format");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_colors"), "set_use_colors", "is_using_colors");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_custom_data"), "set_use_custom_data", "is_using_custom_data");
@@ -369,8 +391,14 @@ void MultiMesh::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "custom_data_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "_set_custom_data_array", "_get_custom_data_array");
#endif
+ ADD_GROUP("Physics Interpolation", "physics_interpolation");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_interpolation_quality", PROPERTY_HINT_ENUM, "Fast,High"), "set_physics_interpolation_quality", "get_physics_interpolation_quality");
+
BIND_ENUM_CONSTANT(TRANSFORM_2D);
BIND_ENUM_CONSTANT(TRANSFORM_3D);
+
+ BIND_ENUM_CONSTANT(INTERP_QUALITY_FAST);
+ BIND_ENUM_CONSTANT(INTERP_QUALITY_HIGH);
}
MultiMesh::MultiMesh() {
diff --git a/scene/resources/multimesh.h b/scene/resources/multimesh.h
index d7bcb13162..03505bb4d1 100644
--- a/scene/resources/multimesh.h
+++ b/scene/resources/multimesh.h
@@ -44,6 +44,11 @@ public:
TRANSFORM_3D = RS::MULTIMESH_TRANSFORM_3D
};
+ enum PhysicsInterpolationQuality {
+ INTERP_QUALITY_FAST,
+ INTERP_QUALITY_HIGH,
+ };
+
private:
Ref<Mesh> mesh;
RID multimesh;
@@ -53,6 +58,7 @@ private:
bool use_custom_data = false;
int instance_count = 0;
int visible_instance_count = -1;
+ PhysicsInterpolationQuality _physics_interpolation_quality = INTERP_QUALITY_FAST;
protected:
static void _bind_methods();
@@ -74,6 +80,8 @@ protected:
void set_buffer(const Vector<float> &p_buffer);
Vector<float> get_buffer() const;
+ void set_buffer_interpolated(const Vector<float> &p_buffer_curr, const Vector<float> &p_buffer_prev);
+
public:
void set_mesh(const Ref<Mesh> &p_mesh);
Ref<Mesh> get_mesh() const;
@@ -93,6 +101,9 @@ public:
void set_visible_instance_count(int p_count);
int get_visible_instance_count() const;
+ void set_physics_interpolation_quality(PhysicsInterpolationQuality p_quality);
+ PhysicsInterpolationQuality get_physics_interpolation_quality() const { return _physics_interpolation_quality; }
+
void set_instance_transform(int p_instance, const Transform3D &p_transform);
void set_instance_transform_2d(int p_instance, const Transform2D &p_transform);
Transform3D get_instance_transform(int p_instance) const;
@@ -104,6 +115,10 @@ public:
void set_instance_custom_data(int p_instance, const Color &p_custom_data);
Color get_instance_custom_data(int p_instance) const;
+ void reset_instance_physics_interpolation(int p_instance);
+
+ void set_physics_interpolated(bool p_interpolated);
+
void set_custom_aabb(const AABB &p_custom);
AABB get_custom_aabb() const;
@@ -116,5 +131,6 @@ public:
};
VARIANT_ENUM_CAST(MultiMesh::TransformFormat);
+VARIANT_ENUM_CAST(MultiMesh::PhysicsInterpolationQuality);
#endif // MULTIMESH_H
diff --git a/scene/resources/navigation_mesh.h b/scene/resources/navigation_mesh.h
index f695ba3d80..741cea0791 100644
--- a/scene/resources/navigation_mesh.h
+++ b/scene/resources/navigation_mesh.h
@@ -93,7 +93,7 @@ protected:
float detail_sample_max_error = 1.0f;
SamplePartitionType partition_type = SAMPLE_PARTITION_WATERSHED;
- ParsedGeometryType parsed_geometry_type = PARSED_GEOMETRY_MESH_INSTANCES;
+ ParsedGeometryType parsed_geometry_type = PARSED_GEOMETRY_BOTH;
uint32_t collision_mask = 0xFFFFFFFF;
SourceGeometryMode source_geometry_mode = SOURCE_GEOMETRY_ROOT_NODE_CHILDREN;
diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp
index 5f70a24fcd..5e148c9276 100644
--- a/scene/resources/visual_shader_nodes.cpp
+++ b/scene/resources/visual_shader_nodes.cpp
@@ -5368,6 +5368,20 @@ String VisualShaderNodeIntParameter::generate_global(Shader::Mode p_mode, Visual
code += _get_qual_str() + "uniform int " + get_parameter_name() + " : hint_range(" + itos(hint_range_min) + ", " + itos(hint_range_max) + ")";
} else if (hint == HINT_RANGE_STEP) {
code += _get_qual_str() + "uniform int " + get_parameter_name() + " : hint_range(" + itos(hint_range_min) + ", " + itos(hint_range_max) + ", " + itos(hint_range_step) + ")";
+ } else if (hint == HINT_ENUM) {
+ code += _get_qual_str() + "uniform int " + get_parameter_name() + " : hint_enum(";
+
+ bool first = true;
+ for (const String &_name : hint_enum_names) {
+ if (first) {
+ first = false;
+ } else {
+ code += ", ";
+ }
+ code += "\"" + _name.c_escape() + "\"";
+ }
+
+ code += ")";
} else {
code += _get_qual_str() + "uniform int " + get_parameter_name();
}
@@ -5439,6 +5453,18 @@ int VisualShaderNodeIntParameter::get_step() const {
return hint_range_step;
}
+void VisualShaderNodeIntParameter::set_enum_names(const PackedStringArray &p_names) {
+ if (hint_enum_names == p_names) {
+ return;
+ }
+ hint_enum_names = p_names;
+ emit_changed();
+}
+
+PackedStringArray VisualShaderNodeIntParameter::get_enum_names() const {
+ return hint_enum_names;
+}
+
void VisualShaderNodeIntParameter::set_default_value_enabled(bool p_default_value_enabled) {
if (default_value_enabled == p_default_value_enabled) {
return;
@@ -5476,22 +5502,27 @@ void VisualShaderNodeIntParameter::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_step", "value"), &VisualShaderNodeIntParameter::set_step);
ClassDB::bind_method(D_METHOD("get_step"), &VisualShaderNodeIntParameter::get_step);
+ ClassDB::bind_method(D_METHOD("set_enum_names", "names"), &VisualShaderNodeIntParameter::set_enum_names);
+ ClassDB::bind_method(D_METHOD("get_enum_names"), &VisualShaderNodeIntParameter::get_enum_names);
+
ClassDB::bind_method(D_METHOD("set_default_value_enabled", "enabled"), &VisualShaderNodeIntParameter::set_default_value_enabled);
ClassDB::bind_method(D_METHOD("is_default_value_enabled"), &VisualShaderNodeIntParameter::is_default_value_enabled);
ClassDB::bind_method(D_METHOD("set_default_value", "value"), &VisualShaderNodeIntParameter::set_default_value);
ClassDB::bind_method(D_METHOD("get_default_value"), &VisualShaderNodeIntParameter::get_default_value);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,Range + Step"), "set_hint", "get_hint");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,Range + Step,Enum"), "set_hint", "get_hint");
ADD_PROPERTY(PropertyInfo(Variant::INT, "min"), "set_min", "get_min");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max"), "set_max", "get_max");
ADD_PROPERTY(PropertyInfo(Variant::INT, "step"), "set_step", "get_step");
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "enum_names"), "set_enum_names", "get_enum_names");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "default_value_enabled"), "set_default_value_enabled", "is_default_value_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "default_value"), "set_default_value", "get_default_value");
BIND_ENUM_CONSTANT(HINT_NONE);
BIND_ENUM_CONSTANT(HINT_RANGE);
BIND_ENUM_CONSTANT(HINT_RANGE_STEP);
+ BIND_ENUM_CONSTANT(HINT_ENUM);
BIND_ENUM_CONSTANT(HINT_MAX);
}
@@ -5513,6 +5544,9 @@ Vector<StringName> VisualShaderNodeIntParameter::get_editable_properties() const
if (hint == HINT_RANGE_STEP) {
props.push_back("step");
}
+ if (hint == HINT_ENUM) {
+ props.push_back("enum_names");
+ }
props.push_back("default_value_enabled");
if (default_value_enabled) {
props.push_back("default_value");
diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h
index 7a37ffa0e0..279599ef9c 100644
--- a/scene/resources/visual_shader_nodes.h
+++ b/scene/resources/visual_shader_nodes.h
@@ -2115,6 +2115,7 @@ public:
HINT_NONE,
HINT_RANGE,
HINT_RANGE_STEP,
+ HINT_ENUM,
HINT_MAX,
};
@@ -2123,6 +2124,7 @@ private:
int hint_range_min = 0;
int hint_range_max = 100;
int hint_range_step = 1;
+ PackedStringArray hint_enum_names;
bool default_value_enabled = false;
int default_value = 0;
@@ -2158,6 +2160,9 @@ public:
void set_step(int p_value);
int get_step() const;
+ void set_enum_names(const PackedStringArray &p_names);
+ PackedStringArray get_enum_names() const;
+
void set_default_value_enabled(bool p_enabled);
bool is_default_value_enabled() const;
diff --git a/servers/movie_writer/movie_writer.cpp b/servers/movie_writer/movie_writer.cpp
index 9df05ba94a..aebed4b432 100644
--- a/servers/movie_writer/movie_writer.cpp
+++ b/servers/movie_writer/movie_writer.cpp
@@ -185,6 +185,10 @@ void MovieWriter::add_frame() {
RID main_vp_rid = RenderingServer::get_singleton()->viewport_find_from_screen_attachment(DisplayServer::MAIN_WINDOW_ID);
RID main_vp_texture = RenderingServer::get_singleton()->viewport_get_texture(main_vp_rid);
Ref<Image> vp_tex = RenderingServer::get_singleton()->texture_2d_get(main_vp_texture);
+ if (RenderingServer::get_singleton()->viewport_is_using_hdr_2d(main_vp_rid)) {
+ vp_tex->convert(Image::FORMAT_RGBA8);
+ vp_tex->linear_to_srgb();
+ }
RenderingServer::get_singleton()->viewport_set_measure_render_time(main_vp_rid, true);
cpu_time += RenderingServer::get_singleton()->viewport_get_measured_render_time_cpu(main_vp_rid);
diff --git a/servers/rendering/dummy/rasterizer_scene_dummy.h b/servers/rendering/dummy/rasterizer_scene_dummy.h
index 083493003f..a699a58b1f 100644
--- a/servers/rendering/dummy/rasterizer_scene_dummy.h
+++ b/servers/rendering/dummy/rasterizer_scene_dummy.h
@@ -186,6 +186,7 @@ public:
virtual void decals_set_filter(RS::DecalFilter p_filter) override {}
virtual void light_projectors_set_filter(RS::LightProjectorFilter p_filter) override {}
+ virtual void lightmaps_set_bicubic_filter(bool p_enable) override {}
RasterizerSceneDummy() {}
~RasterizerSceneDummy() {}
diff --git a/servers/rendering/dummy/storage/material_storage.cpp b/servers/rendering/dummy/storage/material_storage.cpp
index 64f6b55172..e8b553ca76 100644
--- a/servers/rendering/dummy/storage/material_storage.cpp
+++ b/servers/rendering/dummy/storage/material_storage.cpp
@@ -102,11 +102,7 @@ void MaterialStorage::get_shader_parameter_list(RID p_shader, List<PropertyInfo>
if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) {
continue;
}
- if (E.value.texture_order >= 0) {
- filtered_uniforms.push_back(Pair<StringName, int>(E.key, E.value.texture_order + 100000));
- } else {
- filtered_uniforms.push_back(Pair<StringName, int>(E.key, E.value.order));
- }
+ filtered_uniforms.push_back(Pair<StringName, int>(E.key, E.value.prop_order));
}
int uniform_count = filtered_uniforms.size();
sorter.sort(filtered_uniforms.ptr(), uniform_count);
diff --git a/servers/rendering/dummy/storage/mesh_storage.cpp b/servers/rendering/dummy/storage/mesh_storage.cpp
index 0b7ade1762..3f90c80826 100644
--- a/servers/rendering/dummy/storage/mesh_storage.cpp
+++ b/servers/rendering/dummy/storage/mesh_storage.cpp
@@ -64,22 +64,22 @@ void MeshStorage::mesh_clear(RID p_mesh) {
m->surfaces.clear();
}
-RID MeshStorage::multimesh_allocate() {
+RID MeshStorage::_multimesh_allocate() {
return multimesh_owner.allocate_rid();
}
-void MeshStorage::multimesh_initialize(RID p_rid) {
+void MeshStorage::_multimesh_initialize(RID p_rid) {
multimesh_owner.initialize_rid(p_rid, DummyMultiMesh());
}
-void MeshStorage::multimesh_free(RID p_rid) {
+void MeshStorage::_multimesh_free(RID p_rid) {
DummyMultiMesh *multimesh = multimesh_owner.get_or_null(p_rid);
ERR_FAIL_NULL(multimesh);
multimesh_owner.free(p_rid);
}
-void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
+void MeshStorage::_multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
DummyMultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
multimesh->buffer.resize(p_buffer.size());
@@ -87,7 +87,7 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b
memcpy(cache_data, p_buffer.ptr(), p_buffer.size() * sizeof(float));
}
-Vector<float> MeshStorage::multimesh_get_buffer(RID p_multimesh) const {
+Vector<float> MeshStorage::_multimesh_get_buffer(RID p_multimesh) const {
DummyMultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Vector<float>());
diff --git a/servers/rendering/dummy/storage/mesh_storage.h b/servers/rendering/dummy/storage/mesh_storage.h
index d98b2e2ee7..ec19562147 100644
--- a/servers/rendering/dummy/storage/mesh_storage.h
+++ b/servers/rendering/dummy/storage/mesh_storage.h
@@ -146,34 +146,36 @@ public:
bool owns_multimesh(RID p_rid) { return multimesh_owner.owns(p_rid); }
- virtual RID multimesh_allocate() override;
- virtual void multimesh_initialize(RID p_rid) override;
- virtual void multimesh_free(RID p_rid) override;
-
- virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override {}
- virtual int multimesh_get_instance_count(RID p_multimesh) const override { return 0; }
-
- virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh) override {}
- virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) override {}
- virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) override {}
- virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override {}
- virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override {}
-
- virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override {}
- virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const override { return AABB(); }
-
- virtual RID multimesh_get_mesh(RID p_multimesh) const override { return RID(); }
- virtual AABB multimesh_get_aabb(RID p_multimesh) const override { return AABB(); }
-
- virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const override { return Transform3D(); }
- virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override { return Transform2D(); }
- virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const override { return Color(); }
- virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override { return Color(); }
- virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override;
- virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const override;
-
- virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) override {}
- virtual int multimesh_get_visible_instances(RID p_multimesh) const override { return 0; }
+ virtual RID _multimesh_allocate() override;
+ virtual void _multimesh_initialize(RID p_rid) override;
+ virtual void _multimesh_free(RID p_rid) override;
+
+ virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override {}
+ virtual int _multimesh_get_instance_count(RID p_multimesh) const override { return 0; }
+
+ virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) override {}
+ virtual void _multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) override {}
+ virtual void _multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) override {}
+ virtual void _multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override {}
+ virtual void _multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override {}
+
+ virtual void _multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override {}
+ virtual AABB _multimesh_get_custom_aabb(RID p_multimesh) const override { return AABB(); }
+
+ virtual RID _multimesh_get_mesh(RID p_multimesh) const override { return RID(); }
+ virtual AABB _multimesh_get_aabb(RID p_multimesh) const override { return AABB(); }
+
+ virtual Transform3D _multimesh_instance_get_transform(RID p_multimesh, int p_index) const override { return Transform3D(); }
+ virtual Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override { return Transform2D(); }
+ virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const override { return Color(); }
+ virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override { return Color(); }
+ virtual void _multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override;
+ virtual Vector<float> _multimesh_get_buffer(RID p_multimesh) const override;
+
+ virtual void _multimesh_set_visible_instances(RID p_multimesh, int p_visible) override {}
+ virtual int _multimesh_get_visible_instances(RID p_multimesh) const override { return 0; }
+
+ MultiMeshInterpolator *_multimesh_get_interpolator(RID p_multimesh) const override { return nullptr; }
/* SKELETON API */
diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp
index c4286dcc0c..0ec161d8cf 100644
--- a/servers/rendering/renderer_canvas_cull.cpp
+++ b/servers/rendering/renderer_canvas_cull.cpp
@@ -51,7 +51,7 @@ void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas
memset(z_last_list, 0, z_range * sizeof(RendererCanvasRender::Item *));
for (int i = 0; i < p_child_item_count; i++) {
- _cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, true, p_canvas_cull_mask, p_child_items[i].mirror, 1);
+ _cull_canvas_item(p_child_items[i].item, p_transform, p_clip_rect, Color(1, 1, 1, 1), 0, z_list, z_last_list, nullptr, nullptr, true, p_canvas_cull_mask, Point2(), 1, nullptr);
}
RendererCanvasRender::Item *list = nullptr;
@@ -97,6 +97,7 @@ void _collect_ysort_children(RendererCanvasCull::Item *p_canvas_item, const Tran
if (!child_items[i]->repeat_source) {
child_items[i]->repeat_size = p_canvas_item->repeat_size;
child_items[i]->repeat_times = p_canvas_item->repeat_times;
+ child_items[i]->repeat_source_item = p_canvas_item->repeat_source_item;
}
// Y sorted canvas items are flattened into r_items. Calculate their absolute z index to use when rendering r_items.
@@ -229,10 +230,13 @@ void RendererCanvasCull::_attach_canvas_item_for_draw(RendererCanvasCull::Item *
ci->visibility_notifier->visible_in_frame = RSG::rasterizer->get_frame_number();
}
+ } else if (ci->repeat_source) {
+ // If repeat source does not draw itself it still needs transform updated as its child items' repeat offsets are relative to it.
+ ci->final_transform = p_transform;
}
}
-void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_allow_y_sort, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times) {
+void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_allow_y_sort, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item) {
Item *ci = p_canvas_item;
if (!ci->visible) {
@@ -268,19 +272,16 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
Point2 repeat_size = p_repeat_size;
int repeat_times = p_repeat_times;
+ RendererCanvasRender::Item *repeat_source_item = p_repeat_source_item;
if (ci->repeat_source) {
repeat_size = ci->repeat_size;
repeat_times = ci->repeat_times;
+ repeat_source_item = ci;
} else {
ci->repeat_size = repeat_size;
ci->repeat_times = repeat_times;
- }
-
- if (repeat_size.x || repeat_size.y) {
- Size2 scale = final_xform.get_scale();
- rect.size += repeat_size * repeat_times / scale;
- rect.position -= repeat_size / scale * (repeat_times / 2);
+ ci->repeat_source_item = repeat_source_item;
}
if (snapping_2d_transforms_to_pixel) {
@@ -291,6 +292,25 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
final_xform = parent_xform * final_xform;
Rect2 global_rect = final_xform.xform(rect);
+ if (repeat_source_item && (repeat_size.x || repeat_size.y)) {
+ // Top-left repeated rect.
+ Rect2 corner_rect = global_rect;
+ corner_rect.position -= repeat_source_item->final_transform.basis_xform((repeat_times / 2) * repeat_size);
+ global_rect = corner_rect;
+
+ // Plus top-right repeated rect.
+ Size2 size_x_offset = repeat_source_item->final_transform.basis_xform(repeat_times * Size2(repeat_size.x, 0));
+ corner_rect.position += size_x_offset;
+ global_rect = global_rect.merge(corner_rect);
+
+ // Plus bottom-right repeated rect.
+ corner_rect.position += repeat_source_item->final_transform.basis_xform(repeat_times * Size2(0, repeat_size.y));
+ global_rect = global_rect.merge(corner_rect);
+
+ // Plus bottom-left repeated rect.
+ corner_rect.position -= size_x_offset;
+ global_rect = global_rect.merge(corner_rect);
+ }
global_rect.position += p_clip_rect.position;
if (ci->use_parent_material && p_material_owner) {
@@ -357,7 +377,7 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
sorter.sort(child_items, child_item_count);
for (i = 0; i < child_item_count; i++) {
- _cull_canvas_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, p_canvas_cull_mask, child_items[i]->repeat_size, child_items[i]->repeat_times);
+ _cull_canvas_item(child_items[i], final_xform * child_items[i]->ysort_xform, p_clip_rect, modulate * child_items[i]->ysort_modulate, child_items[i]->ysort_parent_abs_z_index, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, (Item *)child_items[i]->material_owner, false, p_canvas_cull_mask, child_items[i]->repeat_size, child_items[i]->repeat_times, child_items[i]->repeat_source_item);
}
} else {
RendererCanvasRender::Item *canvas_group_from = nullptr;
@@ -381,14 +401,14 @@ void RendererCanvasCull::_cull_canvas_item(Item *p_canvas_item, const Transform2
if (!child_items[i]->behind && !use_canvas_group) {
continue;
}
- _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
+ _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item);
}
_attach_canvas_item_for_draw(ci, p_canvas_clip, r_z_list, r_z_last_list, final_xform, p_clip_rect, global_rect, modulate, p_z, p_material_owner, use_canvas_group, canvas_group_from);
for (int i = 0; i < child_item_count; i++) {
if (child_items[i]->behind || use_canvas_group) {
continue;
}
- _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times);
+ _cull_canvas_item(child_items[i], final_xform, p_clip_rect, modulate, p_z, r_z_list, r_z_last_list, (Item *)ci->final_clip_owner, p_material_owner, true, p_canvas_cull_mask, repeat_size, repeat_times, repeat_source_item);
}
}
}
@@ -431,14 +451,22 @@ void RendererCanvasCull::canvas_set_item_mirroring(RID p_canvas, RID p_item, con
int idx = canvas->find_item(canvas_item);
ERR_FAIL_COND(idx == -1);
- canvas->child_items.write[idx].mirror = p_mirroring;
+
+ bool is_repeat_source = (p_mirroring.x || p_mirroring.y);
+ canvas_item->repeat_source = is_repeat_source;
+ canvas_item->repeat_source_item = is_repeat_source ? canvas_item : nullptr;
+ canvas_item->repeat_size = p_mirroring;
+ canvas_item->repeat_times = 1;
}
void RendererCanvasCull::canvas_set_item_repeat(RID p_item, const Point2 &p_repeat_size, int p_repeat_times) {
+ ERR_FAIL_COND(p_repeat_times < 0);
Item *canvas_item = canvas_item_owner.get_or_null(p_item);
ERR_FAIL_NULL(canvas_item);
- canvas_item->repeat_source = true;
+ bool is_repeat_source = (p_repeat_size.x || p_repeat_size.y) && p_repeat_times;
+ canvas_item->repeat_source = is_repeat_source;
+ canvas_item->repeat_source_item = is_repeat_source ? canvas_item : nullptr;
canvas_item->repeat_size = p_repeat_size;
canvas_item->repeat_times = p_repeat_times;
}
diff --git a/servers/rendering/renderer_canvas_cull.h b/servers/rendering/renderer_canvas_cull.h
index 9f8cbea2e9..91c03054f7 100644
--- a/servers/rendering/renderer_canvas_cull.h
+++ b/servers/rendering/renderer_canvas_cull.h
@@ -126,7 +126,6 @@ public:
struct Canvas : public RendererViewport::CanvasBase {
HashSet<RID> viewports;
struct ChildItem {
- Point2 mirror;
Item *item = nullptr;
bool operator<(const ChildItem &p_item) const {
return item->index < p_item.item->index;
@@ -188,7 +187,7 @@ public:
private:
void _render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, uint32_t p_canvas_cull_mask, RenderingMethod::RenderInfo *r_render_info = nullptr);
- void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_allow_y_sort, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times);
+ void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_parent_xform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool p_allow_y_sort, uint32_t p_canvas_cull_mask, const Point2 &p_repeat_size, int p_repeat_times, RendererCanvasRender::Item *p_repeat_source_item);
static constexpr int z_range = RS::CANVAS_ITEM_Z_MAX - RS::CANVAS_ITEM_Z_MIN + 1;
diff --git a/servers/rendering/renderer_canvas_render.h b/servers/rendering/renderer_canvas_render.h
index cb8180f989..c57abee165 100644
--- a/servers/rendering/renderer_canvas_render.h
+++ b/servers/rendering/renderer_canvas_render.h
@@ -364,6 +364,7 @@ public:
bool repeat_source;
Point2 repeat_size;
int repeat_times = 1;
+ Item *repeat_source_item = nullptr;
Rect2 global_rect_cache;
diff --git a/servers/rendering/renderer_rd/effects/fsr2.cpp b/servers/rendering/renderer_rd/effects/fsr2.cpp
index 925352a7d1..551ea5dd97 100644
--- a/servers/rendering/renderer_rd/effects/fsr2.cpp
+++ b/servers/rendering/renderer_rd/effects/fsr2.cpp
@@ -800,9 +800,6 @@ FSR2Effect::~FSR2Effect() {
RD::get_singleton()->free(device.linear_clamp_sampler);
for (uint32_t i = 0; i < FFX_FSR2_PASS_COUNT; i++) {
- if (device.passes[i].pipeline.pipeline_rid.is_valid()) {
- RD::get_singleton()->free(device.passes[i].pipeline.pipeline_rid);
- }
device.passes[i].shader->version_free(device.passes[i].shader_version);
}
}
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index cb655f9b04..b97e38da4d 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -1103,9 +1103,17 @@ void RenderForwardClustered::_setup_lightmaps(const RenderDataRD *p_render_data,
RID lightmap = light_storage->lightmap_instance_get_lightmap(p_lightmaps[i]);
+ // Transform (for directional lightmaps).
Basis to_lm = light_storage->lightmap_instance_get_transform(p_lightmaps[i]).basis.inverse() * p_cam_transform.basis;
to_lm = to_lm.inverse().transposed(); //will transform normals
RendererRD::MaterialStorage::store_transform_3x3(to_lm, scene_state.lightmaps[i].normal_xform);
+
+ // Light texture size.
+ Vector2i lightmap_size = light_storage->lightmap_get_light_texture_size(lightmap);
+ scene_state.lightmaps[i].texture_size[0] = lightmap_size[0];
+ scene_state.lightmaps[i].texture_size[1] = lightmap_size[1];
+
+ // Exposure.
scene_state.lightmaps[i].exposure_normalization = 1.0;
if (p_render_data->camera_attributes.is_valid()) {
float baked_exposure = light_storage->lightmap_get_baked_exposure_normalization(lightmap);
@@ -4242,6 +4250,11 @@ void RenderForwardClustered::_update_shader_quality_settings() {
spec_constants.push_back(sc);
+ sc.constant_id = SPEC_CONSTANT_USE_LIGHTMAP_BICUBIC_FILTER;
+ sc.bool_value = lightmap_filter_bicubic_get();
+
+ spec_constants.push_back(sc);
+
scene_shader.set_default_specialization_constants(spec_constants);
base_uniforms_changed(); //also need this
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
index 0aa4a0667e..5d14653db6 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
@@ -73,6 +73,7 @@ class RenderForwardClustered : public RendererSceneRenderRD {
SPEC_CONSTANT_DECAL_FILTER = 10,
SPEC_CONSTANT_PROJECTOR_FILTER = 11,
SPEC_CONSTANT_USE_DEPTH_FOG = 12,
+ SPEC_CONSTANT_USE_LIGHTMAP_BICUBIC_FILTER = 13,
};
enum {
@@ -235,8 +236,9 @@ class RenderForwardClustered : public RendererSceneRenderRD {
struct LightmapData {
float normal_xform[12];
- float pad[3];
+ float texture_size[2];
float exposure_normalization;
+ float pad;
};
struct LightmapCaptureData {
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index c03dd96062..412fa643b1 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -563,9 +563,17 @@ void RenderForwardMobile::_setup_lightmaps(const RenderDataRD *p_render_data, co
RID lightmap = light_storage->lightmap_instance_get_lightmap(p_lightmaps[i]);
+ // Transform (for directional lightmaps).
Basis to_lm = light_storage->lightmap_instance_get_transform(p_lightmaps[i]).basis.inverse() * p_cam_transform.basis;
to_lm = to_lm.inverse().transposed(); //will transform normals
RendererRD::MaterialStorage::store_transform_3x3(to_lm, scene_state.lightmaps[i].normal_xform);
+
+ // Light texture size.
+ Vector2i lightmap_size = light_storage->lightmap_get_light_texture_size(lightmap);
+ scene_state.lightmaps[i].texture_size[0] = lightmap_size[0];
+ scene_state.lightmaps[i].texture_size[1] = lightmap_size[1];
+
+ // Exposure.
scene_state.lightmaps[i].exposure_normalization = 1.0;
if (p_render_data->camera_attributes.is_valid()) {
float baked_exposure = light_storage->lightmap_get_baked_exposure_normalization(lightmap);
@@ -2781,6 +2789,11 @@ void RenderForwardMobile::_update_shader_quality_settings() {
spec_constants.push_back(sc);
+ sc.constant_id = SPEC_CONSTANT_USE_LIGHTMAP_BICUBIC_FILTER;
+ sc.bool_value = lightmap_filter_bicubic_get();
+
+ spec_constants.push_back(sc);
+
scene_shader.set_default_specialization_constants(spec_constants);
base_uniforms_changed(); //also need this
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
index 34260bd701..fc60c770e8 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
@@ -82,7 +82,7 @@ private:
SPEC_CONSTANT_DISABLE_FOG = 14,
SPEC_CONSTANT_USE_DEPTH_FOG = 16,
SPEC_CONSTANT_IS_MULTIMESH = 17,
-
+ SPEC_CONSTANT_USE_LIGHTMAP_BICUBIC_FILTER = 18,
};
enum {
@@ -208,8 +208,9 @@ private:
struct LightmapData {
float normal_xform[12];
- float pad[3];
+ float texture_size[2];
float exposure_normalization;
+ float pad;
};
struct LightmapCaptureData {
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index 5c68fb82b1..5e4721dfb5 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -407,7 +407,7 @@ _FORCE_INLINE_ static uint32_t _indices_to_primitives(RS::PrimitiveType p_primit
return (p_indices - subtractor[p_primitive]) / divisor[p_primitive];
}
-void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RD::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, Light *p_lights, PipelineVariants *p_pipeline_variants, bool &r_sdf_used, const Point2 &p_offset, RenderingMethod::RenderInfo *r_render_info) {
+void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RD::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, Light *p_lights, PipelineVariants *p_pipeline_variants, bool &r_sdf_used, const Point2 &p_repeat_offset, RenderingMethod::RenderInfo *r_render_info) {
//create an empty push constant
RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton();
RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton();
@@ -425,11 +425,11 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend
}
PushConstant push_constant;
- Transform2D base_transform = p_canvas_transform_inverse * p_item->final_transform;
-
- if (p_offset.x || p_offset.y) {
- base_transform *= Transform2D(0, p_offset / p_item->xform_curr.get_scale()); // TODO: Interpolate or explain why not needed.
+ Transform2D base_transform = p_item->final_transform;
+ if (p_item->repeat_source_item && (p_repeat_offset.x || p_repeat_offset.y)) {
+ base_transform.columns[2] += p_item->repeat_source_item->final_transform.basis_xform(p_repeat_offset);
}
+ base_transform = p_canvas_transform_inverse * base_transform;
Transform2D draw_transform;
_update_transform_2d_to_mat2x3(base_transform, push_constant.world);
@@ -1250,18 +1250,17 @@ void RendererCanvasRenderRD::_render_items(RID p_to_render_target, int p_item_co
_render_item(draw_list, p_to_render_target, ci, fb_format, canvas_transform_inverse, current_clip, p_lights, pipeline_variants, r_sdf_used, Point2(), r_render_info);
} else {
Point2 start_pos = ci->repeat_size * -(ci->repeat_times / 2);
- Point2 end_pos = ci->repeat_size * ci->repeat_times + ci->repeat_size + start_pos;
- Point2 pos = start_pos;
-
- do {
- do {
- _render_item(draw_list, p_to_render_target, ci, fb_format, canvas_transform_inverse, current_clip, p_lights, pipeline_variants, r_sdf_used, pos, r_render_info);
- pos.y += ci->repeat_size.y;
- } while (pos.y < end_pos.y);
-
- pos.x += ci->repeat_size.x;
- pos.y = start_pos.y;
- } while (pos.x < end_pos.x);
+ Point2 offset;
+
+ int repeat_times_x = ci->repeat_size.x ? ci->repeat_times : 0;
+ int repeat_times_y = ci->repeat_size.y ? ci->repeat_times : 0;
+ for (int ry = 0; ry <= repeat_times_y; ry++) {
+ offset.y = start_pos.y + ry * ci->repeat_size.y;
+ for (int rx = 0; rx <= repeat_times_x; rx++) {
+ offset.x = start_pos.x + rx * ci->repeat_size.x;
+ _render_item(draw_list, p_to_render_target, ci, fb_format, canvas_transform_inverse, current_clip, p_lights, pipeline_variants, r_sdf_used, offset, r_render_info);
+ }
+ }
}
prev_material = material;
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
index a78ced271a..9deb4814c7 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h
@@ -423,7 +423,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender {
double debug_redraw_time = 1.0;
inline void _bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size, bool p_texture_is_data = false); //recursive, so regular inline used instead.
- void _render_item(RenderingDevice::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RenderingDevice::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, Light *p_lights, PipelineVariants *p_pipeline_variants, bool &r_sdf_used, const Point2 &p_offset, RenderingMethod::RenderInfo *r_render_info = nullptr);
+ void _render_item(RenderingDevice::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RenderingDevice::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, Light *p_lights, PipelineVariants *p_pipeline_variants, bool &r_sdf_used, const Point2 &p_repeat_offset, RenderingMethod::RenderInfo *r_render_info = nullptr);
void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr);
_FORCE_INLINE_ void _update_transform_2d_to_mat2x4(const Transform2D &p_transform, float *p_mat2x4);
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
index 0ebed49ee9..7d6d5018d0 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp
@@ -1040,6 +1040,14 @@ void RendererSceneRenderRD::light_projectors_set_filter(RenderingServer::LightPr
_update_shader_quality_settings();
}
+void RendererSceneRenderRD::lightmaps_set_bicubic_filter(bool p_enable) {
+ if (lightmap_filter_bicubic == p_enable) {
+ return;
+ }
+ lightmap_filter_bicubic = p_enable;
+ _update_shader_quality_settings();
+}
+
int RendererSceneRenderRD::get_roughness_layers() const {
return sky.roughness_layers;
}
@@ -1483,6 +1491,7 @@ void RendererSceneRenderRD::init() {
decals_set_filter(RS::DecalFilter(int(GLOBAL_GET("rendering/textures/decals/filter"))));
light_projectors_set_filter(RS::LightProjectorFilter(int(GLOBAL_GET("rendering/textures/light_projectors/filter"))));
+ lightmaps_set_bicubic_filter(GLOBAL_GET("rendering/lightmapping/lightmap_gi/use_bicubic_filter"));
cull_argument.set_page_pool(&cull_argument_pool);
diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.h b/servers/rendering/renderer_rd/renderer_scene_render_rd.h
index a8e8e638cd..022a4560f8 100644
--- a/servers/rendering/renderer_rd/renderer_scene_render_rd.h
+++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.h
@@ -133,6 +133,7 @@ private:
float *directional_soft_shadow_kernel = nullptr;
float *penumbra_shadow_kernel = nullptr;
float *soft_shadow_kernel = nullptr;
+ bool lightmap_filter_bicubic = false;
int directional_penumbra_shadow_samples = 0;
int directional_soft_shadow_samples = 0;
int penumbra_shadow_samples = 0;
@@ -262,6 +263,7 @@ public:
virtual void decals_set_filter(RS::DecalFilter p_filter) override;
virtual void light_projectors_set_filter(RS::LightProjectorFilter p_filter) override;
+ virtual void lightmaps_set_bicubic_filter(bool p_enable) override;
_FORCE_INLINE_ RS::ShadowQuality shadows_quality_get() const {
return shadows_quality;
@@ -292,6 +294,9 @@ public:
_FORCE_INLINE_ int directional_penumbra_shadow_samples_get() const {
return directional_penumbra_shadow_samples;
}
+ _FORCE_INLINE_ bool lightmap_filter_bicubic_get() const {
+ return lightmap_filter_bicubic;
+ }
_FORCE_INLINE_ int directional_soft_shadow_samples_get() const {
return directional_soft_shadow_samples;
}
diff --git a/servers/rendering/renderer_rd/shaders/environment/sky.glsl b/servers/rendering/renderer_rd/shaders/environment/sky.glsl
index 35457a2482..5aa3735494 100644
--- a/servers/rendering/renderer_rd/shaders/environment/sky.glsl
+++ b/servers/rendering/renderer_rd/shaders/environment/sky.glsl
@@ -255,10 +255,6 @@ void main() {
frag_color.rgb = color;
frag_color.a = alpha;
- // For mobile renderer we're multiplying by 0.5 as we're using a UNORM buffer.
- // For both mobile and clustered, we also bake in the exposure value for the environment and camera.
- frag_color.rgb = frag_color.rgb * params.luminance_multiplier;
-
#if !defined(DISABLE_FOG) && !defined(USE_CUBEMAP_PASS)
// Draw "fixed" fog before volumetric fog to ensure volumetric fog can appear in front of the sky.
@@ -278,6 +274,10 @@ void main() {
#endif // DISABLE_FOG
+ // For mobile renderer we're multiplying by 0.5 as we're using a UNORM buffer.
+ // For both mobile and clustered, we also bake in the exposure value for the environment and camera.
+ frag_color.rgb = frag_color.rgb * params.luminance_multiplier;
+
// Blending is disabled for Sky, so alpha doesn't blend.
// Alpha is used for subsurface scattering so make sure it doesn't get applied to Sky.
if (!AT_CUBEMAP_PASS && !AT_HALF_RES_PASS && !AT_QUARTER_RES_PASS) {
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 67ee84b01b..5706f670eb 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
@@ -657,6 +657,7 @@ layout(constant_id = 9) const uint sc_directional_penumbra_shadow_samples = 4;
layout(constant_id = 10) const bool sc_decal_use_mipmaps = true;
layout(constant_id = 11) const bool sc_projector_use_mipmaps = true;
layout(constant_id = 12) const bool sc_use_depth_fog = false;
+layout(constant_id = 13) const bool sc_use_lightmap_bicubic_filter = false;
// not used in clustered renderer but we share some code with the mobile renderer that requires this.
const float sc_luminance_multiplier = 1.0;
@@ -701,6 +702,67 @@ layout(location = 9) in float dp_clip;
layout(location = 10) in flat uint instance_index_interp;
+#ifdef USE_LIGHTMAP
+// 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));
+}
+
+vec4 textureArray_bicubic(texture2DArray tex, vec3 uv, vec2 texture_size) {
+ vec2 texel_size = vec2(1.0) / texture_size;
+
+ uv.xy = uv.xy * texture_size + vec2(0.5);
+
+ vec2 iuv = floor(uv.xy);
+ vec2 fuv = fract(uv.xy);
+
+ 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 * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p0, uv.z)) + g1x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p1, uv.z)))) +
+ (g1(fuv.y) * (g0x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p2, uv.z)) + g1x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p3, uv.z))));
+}
+#endif //USE_LIGHTMAP
+
#ifdef USE_MULTIVIEW
#ifdef has_VK_KHR_multiview
#define ViewIndex gl_ViewIndex
@@ -1030,6 +1092,13 @@ void fragment_shader(in SceneData scene_data) {
vec3 light_vertex = vertex;
#endif //LIGHT_VERTEX_USED
+ mat3 model_normal_matrix;
+ if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_NON_UNIFORM_SCALE)) {
+ model_normal_matrix = transpose(inverse(mat3(read_model_matrix)));
+ } else {
+ model_normal_matrix = mat3(read_model_matrix);
+ }
+
mat4 read_view_matrix = scene_data.view_matrix;
vec2 read_viewport_size = scene_data.viewport_size;
{
@@ -1310,6 +1379,8 @@ void fragment_shader(in SceneData scene_data) {
#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+#ifndef AMBIENT_LIGHT_DISABLED
+
if (scene_data.use_reflection_cubemap) {
#ifdef LIGHT_ANISOTROPY_USED
// https://google.github.io/filament/Filament.html#lighting/imagebasedlights/anisotropy
@@ -1397,14 +1468,15 @@ void fragment_shader(in SceneData scene_data) {
#endif //USE_RADIANCE_CUBEMAP_ARRAY
specular_light += clearcoat_light * horizon * horizon * Fc * scene_data.ambient_light_color_energy.a;
}
-#endif
+#endif // LIGHT_CLEARCOAT_USED
+#endif // !AMBIENT_LIGHT_DISABLED
#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
//radiance
/// GI ///
#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
-
+#ifndef AMBIENT_LIGHT_DISABLED
#ifdef USE_LIGHTMAP
//lightmap
@@ -1439,10 +1511,23 @@ void fragment_shader(in SceneData scene_data) {
if (uses_sh) {
uvw.z *= 4.0; //SH textures use 4 times more data
- vec3 lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
- vec3 lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
- vec3 lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
- vec3 lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+
+ vec3 lm_light_l0;
+ vec3 lm_light_l1n1;
+ vec3 lm_light_l1_0;
+ vec3 lm_light_l1p1;
+
+ if (sc_use_lightmap_bicubic_filter) {
+ lm_light_l0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 0.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1n1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1_0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1p1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb;
+ } else {
+ lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
+ lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
+ lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
+ lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+ }
vec3 n = normalize(lightmaps.data[ofs].normal_xform * normal);
float en = lightmaps.data[ofs].exposure_normalization;
@@ -1459,7 +1544,11 @@ void fragment_shader(in SceneData scene_data) {
}
} else {
- ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[ofs].exposure_normalization;
+ if (sc_use_lightmap_bicubic_filter) {
+ ambient_light += textureArray_bicubic(lightmap_textures[ofs], uvw, lightmaps.data[ofs].light_texture_size).rgb * lightmaps.data[ofs].exposure_normalization;
+ } else {
+ ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[ofs].exposure_normalization;
+ }
}
}
#else
@@ -1698,9 +1787,6 @@ void fragment_shader(in SceneData scene_data) {
//finalize ambient light here
{
-#if defined(AMBIENT_LIGHT_DISABLED)
- ambient_light = vec3(0.0, 0.0, 0.0);
-#else
ambient_light *= albedo.rgb;
ambient_light *= ao;
@@ -1713,15 +1799,14 @@ void fragment_shader(in SceneData scene_data) {
ambient_light *= 1.0 - ssil.a;
ambient_light += ssil.rgb * albedo.rgb;
}
-#endif // AMBIENT_LIGHT_DISABLED
}
-
+#endif // AMBIENT_LIGHT_DISABLED
// convert ao to direct light ao
ao = mix(1.0, ao, ao_light_affect);
//this saves some VGPRs
vec3 f0 = F0(metallic, specular, albedo);
-
+#ifndef AMBIENT_LIGHT_DISABLED
{
#if defined(DIFFUSE_TOON)
//simplify for toon, as
@@ -1743,6 +1828,7 @@ void fragment_shader(in SceneData scene_data) {
#endif
}
+#endif // !AMBIENT_LIGHT_DISABLED
#endif //GI !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
#if !defined(MODE_RENDER_DEPTH)
diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl
index 441cf3c80c..18322a0619 100644
--- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl
@@ -94,8 +94,9 @@ directional_lights;
struct Lightmap {
mat3 normal_xform;
- vec3 pad;
+ vec2 light_texture_size;
float exposure_normalization;
+ vec2 pad;
};
layout(set = 0, binding = 7, std140) restrict readonly buffer Lightmaps {
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 24568d7e94..4e1da64151 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
@@ -521,6 +521,7 @@ layout(constant_id = 9) const bool sc_disable_omni_lights = false;
layout(constant_id = 10) const bool sc_disable_spot_lights = false;
layout(constant_id = 11) const bool sc_disable_reflection_probes = false;
layout(constant_id = 12) const bool sc_disable_directional_lights = false;
+layout(constant_id = 18) const bool sc_use_lightmap_bicubic_filter = false;
#endif //!MODE_UNSHADED
@@ -567,6 +568,67 @@ layout(location = 9) highp in float dp_clip;
#endif
+#ifdef USE_LIGHTMAP
+// 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));
+}
+
+vec4 textureArray_bicubic(texture2DArray tex, vec3 uv, vec2 texture_size) {
+ vec2 texel_size = vec2(1.0) / texture_size;
+
+ uv.xy = uv.xy * texture_size + vec2(0.5);
+
+ vec2 iuv = floor(uv.xy);
+ vec2 fuv = fract(uv.xy);
+
+ 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 * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p0, uv.z)) + g1x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p1, uv.z)))) +
+ (g1(fuv.y) * (g0x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p2, uv.z)) + g1x * texture(sampler2DArray(tex, SAMPLER_LINEAR_CLAMP), vec3(p3, uv.z))));
+}
+#endif //USE_LIGHTMAP
+
#ifdef USE_MULTIVIEW
#ifdef has_VK_KHR_multiview
#define ViewIndex gl_ViewIndex
@@ -839,6 +901,13 @@ void main() {
vec3 light_vertex = vertex;
#endif //LIGHT_VERTEX_USED
+ mat3 model_normal_matrix;
+ if (bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_NON_UNIFORM_SCALE)) {
+ model_normal_matrix = transpose(inverse(mat3(read_model_matrix)));
+ } else {
+ model_normal_matrix = mat3(read_model_matrix);
+ }
+
mat4 read_view_matrix = scene_data.view_matrix;
vec2 read_viewport_size = scene_data.viewport_size;
@@ -1071,6 +1140,8 @@ void main() {
#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
+#ifndef AMBIENT_LIGHT_DISABLED
+
if (scene_data.use_reflection_cubemap) {
#ifdef LIGHT_ANISOTROPY_USED
// https://google.github.io/filament/Filament.html#lighting/imagebasedlights/anisotropy
@@ -1158,13 +1229,14 @@ void main() {
#endif //USE_RADIANCE_CUBEMAP_ARRAY
specular_light += clearcoat_light * horizon * horizon * Fc * scene_data.ambient_light_color_energy.a;
}
-#endif
+#endif // LIGHT_CLEARCOAT_USED
+#endif // !AMBIENT_LIGHT_DISABLED
#endif //!defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
//radiance
#if !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
-
+#ifndef AMBIENT_LIGHT_DISABLED
#ifdef USE_LIGHTMAP
//lightmap
@@ -1199,10 +1271,22 @@ void main() {
if (uses_sh) {
uvw.z *= 4.0; //SH textures use 4 times more data
- vec3 lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
- vec3 lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
- vec3 lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
- vec3 lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+ vec3 lm_light_l0;
+ vec3 lm_light_l1n1;
+ vec3 lm_light_l1_0;
+ vec3 lm_light_l1p1;
+
+ if (sc_use_lightmap_bicubic_filter) {
+ lm_light_l0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 0.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1n1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1_0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb;
+ lm_light_l1p1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb;
+ } else {
+ lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb;
+ lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb;
+ lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb;
+ lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb;
+ }
vec3 n = normalize(lightmaps.data[ofs].normal_xform * normal);
float exposure_normalization = lightmaps.data[ofs].exposure_normalization;
@@ -1219,7 +1303,11 @@ void main() {
}
} else {
- ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[ofs].exposure_normalization;
+ if (sc_use_lightmap_bicubic_filter) {
+ ambient_light += textureArray_bicubic(lightmap_textures[ofs], uvw, lightmaps.data[ofs].light_texture_size).rgb * lightmaps.data[ofs].exposure_normalization;
+ } else {
+ ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).rgb * lightmaps.data[ofs].exposure_normalization;
+ }
}
}
@@ -1274,14 +1362,11 @@ void main() {
} //Reflection probes
// finalize ambient light here
- {
-#if defined(AMBIENT_LIGHT_DISABLED)
- ambient_light = vec3(0.0, 0.0, 0.0);
-#else
- ambient_light *= albedo.rgb;
- ambient_light *= ao;
-#endif // AMBIENT_LIGHT_DISABLED
- }
+
+ ambient_light *= albedo.rgb;
+ ambient_light *= ao;
+
+#endif // !AMBIENT_LIGHT_DISABLED
// convert ao to direct light ao
ao = mix(1.0, ao, ao_light_affect);
@@ -1289,6 +1374,7 @@ void main() {
//this saves some VGPRs
vec3 f0 = F0(metallic, specular, albedo);
+#ifndef AMBIENT_LIGHT_DISABLED
{
#if defined(DIFFUSE_TOON)
//simplify for toon, as
@@ -1310,6 +1396,7 @@ void main() {
#endif
}
+#endif // !AMBIENT_LIGHT_DISABLED
#endif // !defined(MODE_RENDER_DEPTH) && !defined(MODE_UNSHADED)
#if !defined(MODE_RENDER_DEPTH)
diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl
index 7674e905e1..d971ff04c5 100644
--- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl
@@ -71,8 +71,9 @@ directional_lights;
struct Lightmap {
mediump mat3 normal_xform;
- vec3 pad;
+ vec2 light_texture_size;
float exposure_normalization;
+ float pad;
};
layout(set = 0, binding = 7, std140) restrict readonly buffer Lightmaps {
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
index 3d294ca8cb..c217c0fa9a 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp
@@ -1813,6 +1813,7 @@ void LightStorage::lightmap_set_textures(RID p_lightmap, RID p_light, bool p_use
}
t->lightmap_users.insert(p_lightmap);
+ lm->light_texture_size = Vector2i(t->width, t->height);
if (using_lightmap_array) {
if (lm->array_index < 0) {
diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h
index f152cc5dae..94ab219dc2 100644
--- a/servers/rendering/renderer_rd/storage_rd/light_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h
@@ -332,6 +332,7 @@ private:
bool interior = false;
AABB bounds = AABB(Vector3(), Vector3(1, 1, 1));
float baked_exposure = 1.0;
+ Vector2i light_texture_size;
int32_t array_index = -1; //unassigned
PackedVector3Array points;
PackedColorArray point_sh;
@@ -985,6 +986,10 @@ public:
const Lightmap *lm = lightmap_owner.get_or_null(p_lightmap);
return lm->uses_spherical_harmonics;
}
+ _FORCE_INLINE_ Vector2i lightmap_get_light_texture_size(RID p_lightmap) const {
+ const Lightmap *lm = lightmap_owner.get_or_null(p_lightmap);
+ return lm->light_texture_size;
+ }
_FORCE_INLINE_ uint64_t lightmap_array_get_version() const {
ERR_FAIL_COND_V(!using_lightmap_array, 0); //only for arrays
return lightmap_array_version;
diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
index 8b74ebfacd..63dc54e24c 100644
--- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp
@@ -580,11 +580,7 @@ void MaterialStorage::ShaderData::get_shader_uniform_list(List<PropertyInfo> *p_
if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) {
continue;
}
- if (E.value.texture_order >= 0) {
- filtered_uniforms.push_back(Pair<StringName, int>(E.key, E.value.texture_order + 100000));
- } else {
- filtered_uniforms.push_back(Pair<StringName, int>(E.key, E.value.order));
- }
+ filtered_uniforms.push_back(Pair<StringName, int>(E.key, E.value.prop_order));
}
int uniform_count = filtered_uniforms.size();
sorter.sort(filtered_uniforms.ptr(), uniform_count);
@@ -634,7 +630,7 @@ bool MaterialStorage::ShaderData::is_parameter_texture(const StringName &p_param
return false;
}
- return uniforms[p_param].texture_order >= 0;
+ return uniforms[p_param].is_texture();
}
///////////////////////////////////////////////////////////////////////////
@@ -645,7 +641,7 @@ void MaterialStorage::MaterialData::update_uniform_buffer(const HashMap<StringNa
bool uses_global_buffer = false;
for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : p_uniforms) {
- if (E.value.order < 0) {
+ if (E.value.is_texture()) {
continue; // texture, does not go here
}
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
index c5454e748a..539bdcbbd0 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
@@ -1056,8 +1056,9 @@ void MeshStorage::update_mesh_instances() {
mi->surfaces[i].previous_buffer = mi->surfaces[i].current_buffer;
- if (uses_motion_vectors && (frame - mi->surfaces[i].last_change) == 1) {
- // Previous buffer's data can only be one frame old to be able to use motion vectors.
+ if (uses_motion_vectors && mi->surfaces[i].last_change && (frame - mi->surfaces[i].last_change) <= 2) {
+ // Use a 2-frame tolerance so that stepped skeletal animations have correct motion vectors
+ // (stepped animation is common for distant NPCs).
uint32_t new_buffer_index = mi->surfaces[i].current_buffer ^ 1;
if (mi->surfaces[i].uniform_set[new_buffer_index].is_null()) {
@@ -1380,14 +1381,16 @@ void MeshStorage::_mesh_surface_generate_version_for_input_mask(Mesh::Surface::V
////////////////// MULTIMESH
-RID MeshStorage::multimesh_allocate() {
+RID MeshStorage::_multimesh_allocate() {
return multimesh_owner.allocate_rid();
}
-void MeshStorage::multimesh_initialize(RID p_rid) {
+void MeshStorage::_multimesh_initialize(RID p_rid) {
multimesh_owner.initialize_rid(p_rid, MultiMesh());
}
-void MeshStorage::multimesh_free(RID p_rid) {
+void MeshStorage::_multimesh_free(RID p_rid) {
+ // Remove from interpolator.
+ _interpolation_data.notify_free_multimesh(p_rid);
_update_dirty_multimeshes();
multimesh_allocate_data(p_rid, 0, RS::MULTIMESH_TRANSFORM_2D);
MultiMesh *multimesh = multimesh_owner.get_or_null(p_rid);
@@ -1395,7 +1398,7 @@ void MeshStorage::multimesh_free(RID p_rid) {
multimesh_owner.free(p_rid);
}
-void MeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
+void MeshStorage::_multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
@@ -1505,13 +1508,13 @@ bool MeshStorage::_multimesh_uses_motion_vectors_offsets(RID p_multimesh) {
return _multimesh_uses_motion_vectors(multimesh);
}
-int MeshStorage::multimesh_get_instance_count(RID p_multimesh) const {
+int MeshStorage::_multimesh_get_instance_count(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, 0);
return multimesh->instances;
}
-void MeshStorage::multimesh_set_mesh(RID p_multimesh, RID p_mesh) {
+void MeshStorage::_multimesh_set_mesh(RID p_multimesh, RID p_mesh) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
if (multimesh->mesh == p_mesh) {
@@ -1701,7 +1704,7 @@ void MeshStorage::_multimesh_re_create_aabb(MultiMesh *multimesh, const float *p
multimesh->aabb = aabb;
}
-void MeshStorage::multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) {
+void MeshStorage::_multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1738,7 +1741,7 @@ void MeshStorage::multimesh_instance_set_transform(RID p_multimesh, int p_index,
_multimesh_mark_dirty(multimesh, p_index, true);
}
-void MeshStorage::multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) {
+void MeshStorage::_multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1765,7 +1768,7 @@ void MeshStorage::multimesh_instance_set_transform_2d(RID p_multimesh, int p_ind
_multimesh_mark_dirty(multimesh, p_index, true);
}
-void MeshStorage::multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) {
+void MeshStorage::_multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1788,7 +1791,7 @@ void MeshStorage::multimesh_instance_set_color(RID p_multimesh, int p_index, con
_multimesh_mark_dirty(multimesh, p_index, false);
}
-void MeshStorage::multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) {
+void MeshStorage::_multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_INDEX(p_index, multimesh->instances);
@@ -1811,7 +1814,7 @@ void MeshStorage::multimesh_instance_set_custom_data(RID p_multimesh, int p_inde
_multimesh_mark_dirty(multimesh, p_index, false);
}
-RID MeshStorage::multimesh_get_mesh(RID p_multimesh) const {
+RID MeshStorage::_multimesh_get_mesh(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, RID());
@@ -1825,7 +1828,7 @@ Dependency *MeshStorage::multimesh_get_dependency(RID p_multimesh) const {
return &multimesh->dependency;
}
-Transform3D MeshStorage::multimesh_instance_get_transform(RID p_multimesh, int p_index) const {
+Transform3D MeshStorage::_multimesh_instance_get_transform(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Transform3D());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Transform3D());
@@ -1856,7 +1859,7 @@ Transform3D MeshStorage::multimesh_instance_get_transform(RID p_multimesh, int p
return t;
}
-Transform2D MeshStorage::multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const {
+Transform2D MeshStorage::_multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Transform2D());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Transform2D());
@@ -1881,7 +1884,7 @@ Transform2D MeshStorage::multimesh_instance_get_transform_2d(RID p_multimesh, in
return t;
}
-Color MeshStorage::multimesh_instance_get_color(RID p_multimesh, int p_index) const {
+Color MeshStorage::_multimesh_instance_get_color(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Color());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Color());
@@ -1904,7 +1907,7 @@ Color MeshStorage::multimesh_instance_get_color(RID p_multimesh, int p_index) co
return c;
}
-Color MeshStorage::multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const {
+Color MeshStorage::_multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Color());
ERR_FAIL_INDEX_V(p_index, multimesh->instances, Color());
@@ -1927,7 +1930,7 @@ Color MeshStorage::multimesh_instance_get_custom_data(RID p_multimesh, int p_ind
return c;
}
-void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
+void MeshStorage::_multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_COND(p_buffer.size() != (multimesh->instances * (int)multimesh->stride_cache));
@@ -1974,7 +1977,7 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b
}
}
-Vector<float> MeshStorage::multimesh_get_buffer(RID p_multimesh) const {
+Vector<float> MeshStorage::_multimesh_get_buffer(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, Vector<float>());
if (multimesh->buffer.is_null()) {
@@ -1996,7 +1999,7 @@ Vector<float> MeshStorage::multimesh_get_buffer(RID p_multimesh) const {
}
}
-void MeshStorage::multimesh_set_visible_instances(RID p_multimesh, int p_visible) {
+void MeshStorage::_multimesh_set_visible_instances(RID p_multimesh, int p_visible) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
ERR_FAIL_COND(p_visible < -1 || p_visible > multimesh->instances);
@@ -2018,26 +2021,26 @@ void MeshStorage::multimesh_set_visible_instances(RID p_multimesh, int p_visible
multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_MULTIMESH_VISIBLE_INSTANCES);
}
-int MeshStorage::multimesh_get_visible_instances(RID p_multimesh) const {
+int MeshStorage::_multimesh_get_visible_instances(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, 0);
return multimesh->visible_instances;
}
-void MeshStorage::multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) {
+void MeshStorage::_multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL(multimesh);
multimesh->custom_aabb = p_aabb;
multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_AABB);
}
-AABB MeshStorage::multimesh_get_custom_aabb(RID p_multimesh) const {
+AABB MeshStorage::_multimesh_get_custom_aabb(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, AABB());
return multimesh->custom_aabb;
}
-AABB MeshStorage::multimesh_get_aabb(RID p_multimesh) const {
+AABB MeshStorage::_multimesh_get_aabb(RID p_multimesh) const {
MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
ERR_FAIL_NULL_V(multimesh, AABB());
if (multimesh->custom_aabb != AABB()) {
@@ -2050,6 +2053,13 @@ AABB MeshStorage::multimesh_get_aabb(RID p_multimesh) const {
return multimesh->aabb;
}
+MeshStorage::MultiMeshInterpolator *MeshStorage::_multimesh_get_interpolator(RID p_multimesh) const {
+ MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh);
+ ERR_FAIL_NULL_V_MSG(multimesh, nullptr, "Multimesh not found: " + itos(p_multimesh.get_id()));
+
+ return &multimesh->interpolator;
+}
+
void MeshStorage::_update_dirty_multimeshes() {
while (multimesh_dirty_list) {
MultiMesh *multimesh = multimesh_dirty_list;
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
index 5491f637bc..4344db783d 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
@@ -244,6 +244,8 @@ private:
bool dirty = false;
MultiMesh *dirty_list = nullptr;
+ RendererMeshStorage::MultiMeshInterpolator interpolator;
+
Dependency dependency;
};
@@ -621,36 +623,38 @@ public:
bool owns_multimesh(RID p_rid) { return multimesh_owner.owns(p_rid); };
- virtual RID multimesh_allocate() override;
- virtual void multimesh_initialize(RID p_multimesh) override;
- virtual void multimesh_free(RID p_rid) override;
+ virtual RID _multimesh_allocate() override;
+ virtual void _multimesh_initialize(RID p_multimesh) override;
+ virtual void _multimesh_free(RID p_rid) override;
+
+ virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override;
+ virtual int _multimesh_get_instance_count(RID p_multimesh) const override;
- virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) override;
- virtual int multimesh_get_instance_count(RID p_multimesh) const override;
+ virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) override;
+ virtual void _multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) override;
+ virtual void _multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) override;
+ virtual void _multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override;
+ virtual void _multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override;
- virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh) override;
- virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) override;
- virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) override;
- virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) override;
- virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) override;
+ virtual RID _multimesh_get_mesh(RID p_multimesh) const override;
- virtual RID multimesh_get_mesh(RID p_multimesh) const override;
+ virtual Transform3D _multimesh_instance_get_transform(RID p_multimesh, int p_index) const override;
+ virtual Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override;
+ virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const override;
+ virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override;
- virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const override;
- virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const override;
- virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const override;
- virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const override;
+ virtual void _multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override;
+ virtual Vector<float> _multimesh_get_buffer(RID p_multimesh) const override;
- virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) override;
- virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const override;
+ virtual void _multimesh_set_visible_instances(RID p_multimesh, int p_visible) override;
+ virtual int _multimesh_get_visible_instances(RID p_multimesh) const override;
- virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) override;
- virtual int multimesh_get_visible_instances(RID p_multimesh) const override;
+ virtual void _multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override;
+ virtual AABB _multimesh_get_custom_aabb(RID p_multimesh) const override;
- virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) override;
- virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const override;
+ virtual AABB _multimesh_get_aabb(RID p_multimesh) const override;
- virtual AABB multimesh_get_aabb(RID p_multimesh) const override;
+ virtual MultiMeshInterpolator *_multimesh_get_interpolator(RID p_multimesh) const override;
void _update_dirty_multimeshes();
void _multimesh_get_motion_vectors_offsets(RID p_multimesh, uint32_t &r_current_offset, uint32_t &r_prev_offset);
diff --git a/servers/rendering/renderer_rd/storage_rd/render_data_rd.h b/servers/rendering/renderer_rd/storage_rd/render_data_rd.h
index 6ebb85442b..888527e1ef 100644
--- a/servers/rendering/renderer_rd/storage_rd/render_data_rd.h
+++ b/servers/rendering/renderer_rd/storage_rd/render_data_rd.h
@@ -73,6 +73,8 @@ public:
uint32_t directional_light_count = 0;
bool directional_light_soft_shadows = false;
+ bool lightmap_bicubic_filter = false;
+
RenderingMethod::RenderInfo *render_info = nullptr;
/* Viewport data */
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index 06753c3fb7..1d25dec633 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -4392,7 +4392,8 @@ void RendererSceneCull::set_scene_render(RendererSceneRender *p_scene_render) {
/* INTERPOLATION API */
void RendererSceneCull::update_interpolation_tick(bool p_process) {
- // TODO (MultiMesh): Update interpolation in storage.
+ // MultiMesh: Update interpolation in storage.
+ RSG::mesh_storage->update_interpolation_tick(p_process);
// INSTANCES
@@ -4450,7 +4451,8 @@ void RendererSceneCull::update_interpolation_tick(bool p_process) {
}
void RendererSceneCull::update_interpolation_frame(bool p_process) {
- // TODO (MultiMesh): Update interpolation in storage.
+ // MultiMesh: Update interpolation in storage.
+ RSG::mesh_storage->update_interpolation_frame(p_process);
if (p_process) {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h
index 53f1f2d246..972f66d325 100644
--- a/servers/rendering/renderer_scene_cull.h
+++ b/servers/rendering/renderer_scene_cull.h
@@ -1418,6 +1418,7 @@ public:
PASS1(decals_set_filter, RS::DecalFilter)
PASS1(light_projectors_set_filter, RS::LightProjectorFilter)
+ PASS1(lightmaps_set_bicubic_filter, bool)
virtual void update();
diff --git a/servers/rendering/renderer_scene_render.h b/servers/rendering/renderer_scene_render.h
index 719efa4df2..3446f5dd1b 100644
--- a/servers/rendering/renderer_scene_render.h
+++ b/servers/rendering/renderer_scene_render.h
@@ -338,6 +338,7 @@ public:
virtual void decals_set_filter(RS::DecalFilter p_filter) = 0;
virtual void light_projectors_set_filter(RS::LightProjectorFilter p_filter) = 0;
+ virtual void lightmaps_set_bicubic_filter(bool p_enable) = 0;
virtual void update() = 0;
virtual ~RendererSceneRender() {}
diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp
index 1049cb63cd..7e45eba1de 100644
--- a/servers/rendering/renderer_viewport.cpp
+++ b/servers/rendering/renderer_viewport.cpp
@@ -1271,6 +1271,13 @@ void RendererViewport::viewport_set_use_hdr_2d(RID p_viewport, bool p_use_hdr_2d
RSG::texture_storage->render_target_set_use_hdr(viewport->render_target, p_use_hdr_2d);
}
+bool RendererViewport::viewport_is_using_hdr_2d(RID p_viewport) const {
+ Viewport *viewport = viewport_owner.get_or_null(p_viewport);
+ ERR_FAIL_NULL_V(viewport, false);
+
+ return viewport->use_hdr_2d;
+}
+
void RendererViewport::viewport_set_screen_space_aa(RID p_viewport, RS::ViewportScreenSpaceAA p_mode) {
Viewport *viewport = viewport_owner.get_or_null(p_viewport);
ERR_FAIL_NULL(viewport);
diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h
index b36fc7f57f..bf97905f86 100644
--- a/servers/rendering/renderer_viewport.h
+++ b/servers/rendering/renderer_viewport.h
@@ -262,6 +262,8 @@ public:
void viewport_set_transparent_background(RID p_viewport, bool p_enabled);
void viewport_set_use_hdr_2d(RID p_viewport, bool p_use_hdr_2d);
+ bool viewport_is_using_hdr_2d(RID p_viewport) const;
+
void viewport_set_global_canvas_transform(RID p_viewport, const Transform2D &p_transform);
void viewport_set_canvas_stacking(RID p_viewport, RID p_canvas, int p_layer, int p_sublayer);
diff --git a/servers/rendering/rendering_method.h b/servers/rendering/rendering_method.h
index 57fbf97d8c..f6212faf08 100644
--- a/servers/rendering/rendering_method.h
+++ b/servers/rendering/rendering_method.h
@@ -349,6 +349,7 @@ public:
virtual void decals_set_filter(RS::DecalFilter p_filter) = 0;
virtual void light_projectors_set_filter(RS::LightProjectorFilter p_filter) = 0;
+ virtual void lightmaps_set_bicubic_filter(bool p_enable) = 0;
virtual bool free(RID p_rid) = 0;
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 6e195a8002..60fa546e16 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -344,6 +344,11 @@ public:
FUNC2(multimesh_set_buffer, RID, const Vector<float> &)
FUNC1RC(Vector<float>, multimesh_get_buffer, RID)
+ FUNC3(multimesh_set_buffer_interpolated, RID, const Vector<float> &, const Vector<float> &)
+ FUNC2(multimesh_set_physics_interpolated, RID, bool)
+ FUNC2(multimesh_set_physics_interpolation_quality, RID, MultimeshPhysicsInterpolationQuality)
+ FUNC2(multimesh_instance_reset_physics_interpolation, RID, int)
+
FUNC2(multimesh_set_visible_instances, RID, int)
FUNC1RC(int, multimesh_get_visible_instances, RID)
@@ -639,6 +644,7 @@ public:
FUNC3(viewport_set_canvas_transform, RID, RID, const Transform2D &)
FUNC2(viewport_set_transparent_background, RID, bool)
FUNC2(viewport_set_use_hdr_2d, RID, bool)
+ FUNC1RC(bool, viewport_is_using_hdr_2d, RID)
FUNC2(viewport_set_snap_2d_transforms_to_pixel, RID, bool)
FUNC2(viewport_set_snap_2d_vertices_to_pixel, RID, bool)
@@ -761,6 +767,7 @@ public:
FUNC1(directional_soft_shadow_filter_set_quality, ShadowQuality);
FUNC1(decals_set_filter, RS::DecalFilter);
FUNC1(light_projectors_set_filter, RS::LightProjectorFilter);
+ FUNC1(lightmaps_set_bicubic_filter, bool);
/* CAMERA ATTRIBUTES */
diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp
index 2542f2eed7..49e005ca96 100644
--- a/servers/rendering/shader_compiler.cpp
+++ b/servers/rendering/shader_compiler.cpp
@@ -922,7 +922,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
if (shader->uniforms.has(vnode->name)) {
//its a uniform!
const ShaderLanguage::ShaderNode::Uniform &u = shader->uniforms[vnode->name];
- if (u.texture_order >= 0) {
+ if (u.is_texture()) {
StringName name;
if (u.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE) {
name = "color_buffer";
@@ -1039,7 +1039,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
if (shader->uniforms.has(anode->name)) {
//its a uniform!
const ShaderLanguage::ShaderNode::Uniform &u = shader->uniforms[anode->name];
- if (u.texture_order >= 0) {
+ if (u.is_texture()) {
code = _mkid(anode->name); //texture, use as is
} else {
//a scalar or vector
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 66fcefe228..f8d00ba7ec 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -96,6 +96,7 @@ const char *ShaderLanguage::token_names[TK_MAX] = {
"FLOAT_CONSTANT",
"INT_CONSTANT",
"UINT_CONSTANT",
+ "STRING_CONSTANT",
"TYPE_VOID",
"TYPE_BOOL",
"TYPE_BVEC2",
@@ -212,6 +213,7 @@ const char *ShaderLanguage::token_names[TK_MAX] = {
"HINT_ANISOTROPY_TEXTURE",
"HINT_SOURCE_COLOR",
"HINT_RANGE",
+ "HINT_ENUM",
"HINT_INSTANCE_INDEX",
"HINT_SCREEN_TEXTURE",
"HINT_NORMAL_ROUGHNESS_TEXTURE",
@@ -365,6 +367,7 @@ const ShaderLanguage::KeyWord ShaderLanguage::keyword_list[] = {
{ TK_HINT_SOURCE_COLOR, "source_color", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_RANGE, "hint_range", CF_UNSPECIFIED, {}, {} },
+ { TK_HINT_ENUM, "hint_enum", CF_UNSPECIFIED, {}, {} },
{ TK_HINT_INSTANCE_INDEX, "instance_index", CF_UNSPECIFIED, {}, {} },
// sampler hints
@@ -512,7 +515,54 @@ ShaderLanguage::Token ShaderLanguage::_get_token() {
return _make_token(TK_OP_NOT);
} break;
- //case '"' //string - no strings in shader
+ case '"': {
+ String _content = "";
+ bool _previous_backslash = false;
+
+ while (true) {
+ bool _ended = false;
+ char32_t c = GETCHAR(0);
+ if (c == 0) {
+ return _make_token(TK_ERROR, "EOF reached before string termination.");
+ }
+ switch (c) {
+ case '"': {
+ if (_previous_backslash) {
+ _content += '"';
+ _previous_backslash = false;
+ } else {
+ _ended = true;
+ }
+ break;
+ }
+ case '\\': {
+ if (_previous_backslash) {
+ _content += '\\';
+ }
+ _previous_backslash = !_previous_backslash;
+ break;
+ }
+ case '\n': {
+ return _make_token(TK_ERROR, "Unexpected end of string.");
+ }
+ default: {
+ if (!_previous_backslash) {
+ _content += c;
+ } else {
+ return _make_token(TK_ERROR, "Only \\\" and \\\\ escape characters supported.");
+ }
+ break;
+ }
+ }
+
+ char_idx++;
+ if (_ended) {
+ break;
+ }
+ }
+
+ return _make_token(TK_STRING_CONSTANT, _content);
+ } break;
//case '\'' //string - no strings in shader
case '{':
return _make_token(TK_CURLY_BRACKET_OPEN);
@@ -1127,6 +1177,9 @@ String ShaderLanguage::get_uniform_hint_name(ShaderNode::Uniform::Hint p_hint) {
case ShaderNode::Uniform::HINT_RANGE: {
result = "hint_range";
} break;
+ case ShaderNode::Uniform::HINT_ENUM: {
+ result = "hint_enum";
+ } break;
case ShaderNode::Uniform::HINT_SOURCE_COLOR: {
result = "source_color";
} break;
@@ -4146,6 +4199,11 @@ PropertyInfo ShaderLanguage::uniform_to_property_info(const ShaderNode::Uniform
if (p_uniform.array_size > 0) {
pi.type = Variant::PACKED_INT32_ARRAY;
// TODO: Handle range and encoding for for unsigned values.
+ } else if (p_uniform.hint == ShaderLanguage::ShaderNode::Uniform::HINT_ENUM) {
+ pi.type = Variant::INT;
+ pi.hint = PROPERTY_HINT_ENUM;
+ String hint_string;
+ pi.hint_string = String(",").join(p_uniform.hint_enum_names);
} else {
pi.type = Variant::INT;
pi.hint = PROPERTY_HINT_RANGE;
@@ -8228,6 +8286,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
int texture_binding = 0;
int uniforms = 0;
int instance_index = 0;
+ int prop_index = 0;
#ifdef DEBUG_ENABLED
uint64_t uniform_buffer_size = 0;
uint64_t max_uniform_buffer_size = 0;
@@ -8782,6 +8841,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
++texture_binding;
}
uniform.order = -1;
+ uniform.prop_order = prop_index++;
} else {
if (uniform_scope == ShaderNode::Uniform::SCOPE_INSTANCE && (type == TYPE_MAT2 || type == TYPE_MAT3 || type == TYPE_MAT4)) {
_set_error(vformat(RTR("The '%s' qualifier is not supported for matrix types."), "SCOPE_INSTANCE"));
@@ -8790,6 +8850,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
uniform.texture_order = -1;
if (uniform_scope != ShaderNode::Uniform::SCOPE_INSTANCE) {
uniform.order = uniforms++;
+ uniform.prop_order = prop_index++;
#ifdef DEBUG_ENABLED
if (check_device_limit_warnings) {
if (uniform.array_size > 0) {
@@ -8985,6 +9046,40 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
new_hint = ShaderNode::Uniform::HINT_RANGE;
} break;
+ case TK_HINT_ENUM: {
+ if (type != TYPE_INT) {
+ _set_error(vformat(RTR("Enum hint is for '%s' only."), "int"));
+ return ERR_PARSE_ERROR;
+ }
+
+ tk = _get_token();
+ if (tk.type != TK_PARENTHESIS_OPEN) {
+ _set_expected_after_error("(", "hint_enum");
+ return ERR_PARSE_ERROR;
+ }
+
+ while (true) {
+ tk = _get_token();
+
+ if (tk.type != TK_STRING_CONSTANT) {
+ _set_error(RTR("Expected a string constant."));
+ return ERR_PARSE_ERROR;
+ }
+
+ uniform.hint_enum_names.push_back(tk.text);
+
+ tk = _get_token();
+
+ if (tk.type == TK_PARENTHESIS_CLOSE) {
+ break;
+ } else if (tk.type != TK_COMMA) {
+ _set_error(RTR("Expected ',' or ')' after string constant."));
+ return ERR_PARSE_ERROR;
+ }
+ }
+
+ new_hint = ShaderNode::Uniform::HINT_ENUM;
+ } break;
case TK_HINT_INSTANCE_INDEX: {
if (custom_instance_index != -1) {
_set_error(vformat(RTR("Can only specify '%s' once."), "instance_index"));
@@ -9079,7 +9174,9 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
default:
break;
}
- if (((new_filter != FILTER_DEFAULT || new_repeat != REPEAT_DEFAULT) || (new_hint != ShaderNode::Uniform::HINT_NONE && new_hint != ShaderNode::Uniform::HINT_SOURCE_COLOR && new_hint != ShaderNode::Uniform::HINT_RANGE)) && !is_sampler_type(type)) {
+
+ bool is_sampler_hint = new_hint != ShaderNode::Uniform::HINT_NONE && new_hint != ShaderNode::Uniform::HINT_SOURCE_COLOR && new_hint != ShaderNode::Uniform::HINT_RANGE && new_hint != ShaderNode::Uniform::HINT_ENUM;
+ if (((new_filter != FILTER_DEFAULT || new_repeat != REPEAT_DEFAULT) || is_sampler_hint) && !is_sampler_type(type)) {
_set_error(RTR("This hint is only for sampler types."));
return ERR_PARSE_ERROR;
}
@@ -10782,15 +10879,21 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
}
} else if ((completion_base == DataType::TYPE_INT || completion_base == DataType::TYPE_FLOAT) && !completion_base_array) {
if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) {
- ScriptLanguage::CodeCompletionOption option("hint_range", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+ Vector<String> options;
if (completion_base == DataType::TYPE_INT) {
- option.insert_text = "hint_range(0, 100, 1)";
+ options.push_back("hint_range(0, 100, 1)");
+ options.push_back("hint_enum(\"Zero\", \"One\", \"Two\")");
} else {
- option.insert_text = "hint_range(0.0, 1.0, 0.1)";
+ options.push_back("hint_range(0.0, 1.0, 0.1)");
}
- r_options->push_back(option);
+ for (const String &option_text : options) {
+ String hint_name = option_text.substr(0, option_text.find_char(char32_t('(')));
+ ScriptLanguage::CodeCompletionOption option(hint_name, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+ option.insert_text = option_text;
+ r_options->push_back(option);
+ }
}
} else if ((int(completion_base) > int(TYPE_MAT4) && int(completion_base) < int(TYPE_STRUCT))) {
Vector<String> options;
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index ad3b78546d..1b5df7e90f 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -59,6 +59,7 @@ public:
TK_FLOAT_CONSTANT,
TK_INT_CONSTANT,
TK_UINT_CONSTANT,
+ TK_STRING_CONSTANT,
TK_TYPE_VOID,
TK_TYPE_BOOL,
TK_TYPE_BVEC2,
@@ -175,6 +176,7 @@ public:
TK_HINT_ANISOTROPY_TEXTURE,
TK_HINT_SOURCE_COLOR,
TK_HINT_RANGE,
+ TK_HINT_ENUM,
TK_HINT_INSTANCE_INDEX,
TK_HINT_SCREEN_TEXTURE,
TK_HINT_NORMAL_ROUGHNESS_TEXTURE,
@@ -623,6 +625,7 @@ public:
enum Hint {
HINT_NONE,
HINT_RANGE,
+ HINT_ENUM,
HINT_SOURCE_COLOR,
HINT_NORMAL,
HINT_ROUGHNESS_NORMAL,
@@ -648,6 +651,7 @@ public:
};
int order = 0;
+ int prop_order = 0;
int texture_order = 0;
int texture_binding = 0;
DataType type = TYPE_VOID;
@@ -660,10 +664,16 @@ public:
TextureFilter filter = FILTER_DEFAULT;
TextureRepeat repeat = REPEAT_DEFAULT;
float hint_range[3];
+ PackedStringArray hint_enum_names;
int instance_index = 0;
String group;
String subgroup;
+ _FORCE_INLINE_ bool is_texture() const {
+ // Order is assigned to -1 for texture uniforms.
+ return order < 0;
+ }
+
Uniform() {
hint_range[0] = 0.0f;
hint_range[1] = 1.0f;
diff --git a/servers/rendering/storage/mesh_storage.cpp b/servers/rendering/storage/mesh_storage.cpp
new file mode 100644
index 0000000000..221ebaa277
--- /dev/null
+++ b/servers/rendering/storage/mesh_storage.cpp
@@ -0,0 +1,485 @@
+/**************************************************************************/
+/* mesh_storage.cpp */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#include "mesh_storage.h"
+
+#include "core/math/transform_interpolator.h"
+
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+#include "core/config/project_settings.h"
+#endif
+
+RID RendererMeshStorage::multimesh_allocate() {
+ return _multimesh_allocate();
+}
+
+void RendererMeshStorage::multimesh_initialize(RID p_rid) {
+ _multimesh_initialize(p_rid);
+}
+
+void RendererMeshStorage::multimesh_free(RID p_rid) {
+ _multimesh_free(p_rid);
+}
+
+void RendererMeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors, bool p_use_custom_data) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi) {
+ mmi->_transform_format = p_transform_format;
+ mmi->_use_colors = p_use_colors;
+ mmi->_use_custom_data = p_use_custom_data;
+ mmi->_num_instances = p_instances;
+
+ mmi->_vf_size_xform = p_transform_format == RS::MULTIMESH_TRANSFORM_2D ? 8 : 12;
+ mmi->_vf_size_color = p_use_colors ? 4 : 0;
+ mmi->_vf_size_data = p_use_custom_data ? 4 : 0;
+
+ mmi->_stride = mmi->_vf_size_xform + mmi->_vf_size_color + mmi->_vf_size_data;
+
+ int size_in_floats = p_instances * mmi->_stride;
+ mmi->_data_curr.resize_zeroed(size_in_floats);
+ mmi->_data_prev.resize_zeroed(size_in_floats);
+ mmi->_data_interpolated.resize_zeroed(size_in_floats);
+ }
+
+ _multimesh_allocate_data(p_multimesh, p_instances, p_transform_format, p_use_colors, p_use_custom_data);
+}
+
+int RendererMeshStorage::multimesh_get_instance_count(RID p_multimesh) const {
+ return _multimesh_get_instance_count(p_multimesh);
+}
+
+void RendererMeshStorage::multimesh_set_mesh(RID p_multimesh, RID p_mesh) {
+ _multimesh_set_mesh(p_multimesh, p_mesh);
+}
+
+void RendererMeshStorage::multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi && mmi->interpolated) {
+ ERR_FAIL_COND(p_index >= mmi->_num_instances);
+ ERR_FAIL_COND(mmi->_vf_size_xform != 12);
+
+ int start = p_index * mmi->_stride;
+ float *ptr = mmi->_data_curr.ptrw();
+ ptr += start;
+
+ const Transform3D &t = p_transform;
+ ptr[0] = t.basis.rows[0][0];
+ ptr[1] = t.basis.rows[0][1];
+ ptr[2] = t.basis.rows[0][2];
+ ptr[3] = t.origin.x;
+ ptr[4] = t.basis.rows[1][0];
+ ptr[5] = t.basis.rows[1][1];
+ ptr[6] = t.basis.rows[1][2];
+ ptr[7] = t.origin.y;
+ ptr[8] = t.basis.rows[2][0];
+ ptr[9] = t.basis.rows[2][1];
+ ptr[10] = t.basis.rows[2][2];
+ ptr[11] = t.origin.z;
+
+ _multimesh_add_to_interpolation_lists(p_multimesh, *mmi);
+
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+ if (!Engine::get_singleton()->is_in_physics_frame()) {
+ PHYSICS_INTERPOLATION_WARNING("MultiMesh interpolation is being triggered from outside physics process, this might lead to issues");
+ }
+#endif
+
+ return;
+ }
+
+ _multimesh_instance_set_transform(p_multimesh, p_index, p_transform);
+}
+
+void RendererMeshStorage::multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) {
+ _multimesh_instance_set_transform_2d(p_multimesh, p_index, p_transform);
+}
+
+void RendererMeshStorage::multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi && mmi->interpolated) {
+ ERR_FAIL_COND(p_index >= mmi->_num_instances);
+ ERR_FAIL_COND(mmi->_vf_size_color == 0);
+
+ int start = (p_index * mmi->_stride) + mmi->_vf_size_xform;
+ float *ptr = mmi->_data_curr.ptrw();
+ ptr += start;
+
+ if (mmi->_vf_size_color == 4) {
+ for (int n = 0; n < 4; n++) {
+ ptr[n] = p_color.components[n];
+ }
+ } else {
+#ifdef DEV_ENABLED
+ // The options are currently 4 or zero, but just in case this changes in future...
+ ERR_FAIL_COND(mmi->_vf_size_color != 0);
+#endif
+ }
+ _multimesh_add_to_interpolation_lists(p_multimesh, *mmi);
+ return;
+ }
+
+ _multimesh_instance_set_color(p_multimesh, p_index, p_color);
+}
+
+void RendererMeshStorage::multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi && mmi->interpolated) {
+ ERR_FAIL_COND(p_index >= mmi->_num_instances);
+ ERR_FAIL_COND(mmi->_vf_size_data == 0);
+
+ int start = (p_index * mmi->_stride) + mmi->_vf_size_xform + mmi->_vf_size_color;
+ float *ptr = mmi->_data_curr.ptrw();
+ ptr += start;
+
+ if (mmi->_vf_size_data == 4) {
+ for (int n = 0; n < 4; n++) {
+ ptr[n] = p_color.components[n];
+ }
+ } else {
+#ifdef DEV_ENABLED
+ // The options are currently 4 or zero, but just in case this changes in future...
+ ERR_FAIL_COND(mmi->_vf_size_data != 0);
+#endif
+ }
+ _multimesh_add_to_interpolation_lists(p_multimesh, *mmi);
+ return;
+ }
+
+ _multimesh_instance_set_custom_data(p_multimesh, p_index, p_color);
+}
+
+void RendererMeshStorage::multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) {
+ _multimesh_set_custom_aabb(p_multimesh, p_aabb);
+}
+
+AABB RendererMeshStorage::multimesh_get_custom_aabb(RID p_multimesh) const {
+ return _multimesh_get_custom_aabb(p_multimesh);
+}
+
+RID RendererMeshStorage::multimesh_get_mesh(RID p_multimesh) const {
+ return _multimesh_get_mesh(p_multimesh);
+}
+
+Transform3D RendererMeshStorage::multimesh_instance_get_transform(RID p_multimesh, int p_index) const {
+ return _multimesh_instance_get_transform(p_multimesh, p_index);
+}
+
+Transform2D RendererMeshStorage::multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const {
+ return _multimesh_instance_get_transform_2d(p_multimesh, p_index);
+}
+
+Color RendererMeshStorage::multimesh_instance_get_color(RID p_multimesh, int p_index) const {
+ return _multimesh_instance_get_color(p_multimesh, p_index);
+}
+
+Color RendererMeshStorage::multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const {
+ return _multimesh_instance_get_custom_data(p_multimesh, p_index);
+}
+
+void RendererMeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi && mmi->interpolated) {
+ ERR_FAIL_COND_MSG(p_buffer.size() != mmi->_data_curr.size(), vformat("Buffer should have %d elements, got %d instead.", mmi->_data_curr.size(), p_buffer.size()));
+
+ mmi->_data_curr = p_buffer;
+ _multimesh_add_to_interpolation_lists(p_multimesh, *mmi);
+
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+ if (!Engine::get_singleton()->is_in_physics_frame()) {
+ PHYSICS_INTERPOLATION_WARNING("MultiMesh interpolation is being triggered from outside physics process, this might lead to issues");
+ }
+#endif
+
+ return;
+ }
+
+ _multimesh_set_buffer(p_multimesh, p_buffer);
+}
+
+Vector<float> RendererMeshStorage::multimesh_get_buffer(RID p_multimesh) const {
+ return _multimesh_get_buffer(p_multimesh);
+}
+
+void RendererMeshStorage::multimesh_set_buffer_interpolated(RID p_multimesh, const Vector<float> &p_buffer, const Vector<float> &p_buffer_prev) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi) {
+ ERR_FAIL_COND_MSG(p_buffer.size() != mmi->_data_curr.size(), vformat("Buffer for current frame should have %d elements, got %d instead.", mmi->_data_curr.size(), p_buffer.size()));
+ ERR_FAIL_COND_MSG(p_buffer_prev.size() != mmi->_data_prev.size(), vformat("Buffer for previous frame should have %d elements, got %d instead.", mmi->_data_prev.size(), p_buffer_prev.size()));
+
+ // We are assuming that mmi->interpolated is the case. (Can possibly assert this?)
+ // Even if this flag hasn't been set - just calling this function suggests interpolation is desired.
+ mmi->_data_prev = p_buffer_prev;
+ mmi->_data_curr = p_buffer;
+ _multimesh_add_to_interpolation_lists(p_multimesh, *mmi);
+
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+ if (!Engine::get_singleton()->is_in_physics_frame()) {
+ PHYSICS_INTERPOLATION_WARNING("MultiMesh interpolation is being triggered from outside physics process, this might lead to issues");
+ }
+#endif
+ }
+}
+
+void RendererMeshStorage::multimesh_set_physics_interpolated(RID p_multimesh, bool p_interpolated) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi) {
+ mmi->interpolated = p_interpolated;
+ }
+}
+
+void RendererMeshStorage::multimesh_set_physics_interpolation_quality(RID p_multimesh, RS::MultimeshPhysicsInterpolationQuality p_quality) {
+ ERR_FAIL_COND((p_quality < 0) || (p_quality > 1));
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi) {
+ mmi->quality = (int)p_quality;
+ }
+}
+
+void RendererMeshStorage::multimesh_instance_reset_physics_interpolation(RID p_multimesh, int p_index) {
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(p_multimesh);
+ if (mmi) {
+ ERR_FAIL_INDEX(p_index, mmi->_num_instances);
+
+ float *w = mmi->_data_prev.ptrw();
+ const float *r = mmi->_data_curr.ptr();
+ int start = p_index * mmi->_stride;
+
+ for (int n = 0; n < mmi->_stride; n++) {
+ w[start + n] = r[start + n];
+ }
+ }
+}
+
+void RendererMeshStorage::multimesh_set_visible_instances(RID p_multimesh, int p_visible) {
+ return _multimesh_set_visible_instances(p_multimesh, p_visible);
+}
+
+int RendererMeshStorage::multimesh_get_visible_instances(RID p_multimesh) const {
+ return _multimesh_get_visible_instances(p_multimesh);
+}
+
+AABB RendererMeshStorage::multimesh_get_aabb(RID p_multimesh) const {
+ return _multimesh_get_aabb(p_multimesh);
+}
+
+void RendererMeshStorage::_multimesh_add_to_interpolation_lists(RID p_multimesh, MultiMeshInterpolator &r_mmi) {
+ if (!r_mmi.on_interpolate_update_list) {
+ r_mmi.on_interpolate_update_list = true;
+ _interpolation_data.multimesh_interpolate_update_list.push_back(p_multimesh);
+ }
+
+ if (!r_mmi.on_transform_update_list) {
+ r_mmi.on_transform_update_list = true;
+ _interpolation_data.multimesh_transform_update_list_curr->push_back(p_multimesh);
+ }
+}
+
+void RendererMeshStorage::InterpolationData::notify_free_multimesh(RID p_rid) {
+ // If the instance was on any of the lists, remove.
+ multimesh_interpolate_update_list.erase_multiple_unordered(p_rid);
+ multimesh_transform_update_lists[0].erase_multiple_unordered(p_rid);
+ multimesh_transform_update_lists[1].erase_multiple_unordered(p_rid);
+}
+
+void RendererMeshStorage::update_interpolation_tick(bool p_process) {
+ // Detect any that were on the previous transform list that are no longer active,
+ // we should remove them from the interpolate list.
+
+ for (unsigned int n = 0; n < _interpolation_data.multimesh_transform_update_list_prev->size(); n++) {
+ const RID &rid = (*_interpolation_data.multimesh_transform_update_list_prev)[n];
+
+ bool active = true;
+
+ // No longer active? (Either the instance deleted or no longer being transformed.)
+
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(rid);
+ if (mmi && !mmi->on_transform_update_list) {
+ active = false;
+ mmi->on_interpolate_update_list = false;
+
+ // Make sure the most recent transform is set...
+ mmi->_data_interpolated = mmi->_data_curr; // TODO: Copy data rather than use Packed = function?
+
+ // ... and that both prev and current are the same, just in case of any interpolations.
+ mmi->_data_prev = mmi->_data_curr;
+ }
+
+ if (!mmi) {
+ active = false;
+ }
+
+ if (!active) {
+ _interpolation_data.multimesh_interpolate_update_list.erase(rid);
+ }
+ }
+
+ if (p_process) {
+ for (unsigned int i = 0; i < _interpolation_data.multimesh_transform_update_list_curr->size(); i++) {
+ const RID &rid = (*_interpolation_data.multimesh_transform_update_list_curr)[i];
+
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(rid);
+ if (mmi) {
+ // Reset for next tick.
+ mmi->on_transform_update_list = false;
+ mmi->_data_prev = mmi->_data_curr;
+ }
+ }
+ }
+
+ // If any have left the transform list, remove from the interpolate list.
+
+ // We maintain a mirror list for the transform updates, so we can detect when an instance
+ // is no longer being transformed, and remove it from the interpolate list.
+ SWAP(_interpolation_data.multimesh_transform_update_list_curr, _interpolation_data.multimesh_transform_update_list_prev);
+
+ // Prepare for the next iteration.
+ _interpolation_data.multimesh_transform_update_list_curr->clear();
+}
+
+void RendererMeshStorage::update_interpolation_frame(bool p_process) {
+ if (p_process) {
+ // Only need 32 bits for interpolation, don't use real_t.
+ float f = Engine::get_singleton()->get_physics_interpolation_fraction();
+
+ for (unsigned int c = 0; c < _interpolation_data.multimesh_interpolate_update_list.size(); c++) {
+ const RID &rid = _interpolation_data.multimesh_interpolate_update_list[c];
+
+ // We could use the TransformInterpolator here to slerp transforms, but that might be too expensive,
+ // so just using a Basis lerp for now.
+ MultiMeshInterpolator *mmi = _multimesh_get_interpolator(rid);
+ if (mmi) {
+ // Make sure arrays are the correct size.
+ DEV_ASSERT(mmi->_data_prev.size() == mmi->_data_curr.size());
+
+ if (mmi->_data_interpolated.size() < mmi->_data_curr.size()) {
+ mmi->_data_interpolated.resize(mmi->_data_curr.size());
+ }
+ DEV_ASSERT(mmi->_data_interpolated.size() >= mmi->_data_curr.size());
+
+ DEV_ASSERT((mmi->_data_curr.size() % mmi->_stride) == 0);
+ int num = mmi->_data_curr.size() / mmi->_stride;
+
+ const float *pf_prev = mmi->_data_prev.ptr();
+ const float *pf_curr = mmi->_data_curr.ptr();
+ float *pf_int = mmi->_data_interpolated.ptrw();
+
+ bool use_lerp = mmi->quality == 0;
+
+ // Temporary transform (needed for swizzling).
+ Transform3D tp, tc, tr; // (transform prev, curr and result)
+
+ // Test for cache friendliness versus doing branchless.
+ for (int n = 0; n < num; n++) {
+ // Transform.
+ if (use_lerp) {
+ for (int i = 0; i < mmi->_vf_size_xform; i++) {
+ pf_int[i] = Math::lerp(pf_prev[i], pf_curr[i], f);
+ }
+ } else {
+ // Silly swizzling, this will slow things down.
+ // No idea why it is using this format...
+ // ... maybe due to the shader.
+ tp.basis.rows[0][0] = pf_prev[0];
+ tp.basis.rows[0][1] = pf_prev[1];
+ tp.basis.rows[0][2] = pf_prev[2];
+ tp.basis.rows[1][0] = pf_prev[4];
+ tp.basis.rows[1][1] = pf_prev[5];
+ tp.basis.rows[1][2] = pf_prev[6];
+ tp.basis.rows[2][0] = pf_prev[8];
+ tp.basis.rows[2][1] = pf_prev[9];
+ tp.basis.rows[2][2] = pf_prev[10];
+ tp.origin.x = pf_prev[3];
+ tp.origin.y = pf_prev[7];
+ tp.origin.z = pf_prev[11];
+
+ tc.basis.rows[0][0] = pf_curr[0];
+ tc.basis.rows[0][1] = pf_curr[1];
+ tc.basis.rows[0][2] = pf_curr[2];
+ tc.basis.rows[1][0] = pf_curr[4];
+ tc.basis.rows[1][1] = pf_curr[5];
+ tc.basis.rows[1][2] = pf_curr[6];
+ tc.basis.rows[2][0] = pf_curr[8];
+ tc.basis.rows[2][1] = pf_curr[9];
+ tc.basis.rows[2][2] = pf_curr[10];
+ tc.origin.x = pf_curr[3];
+ tc.origin.y = pf_curr[7];
+ tc.origin.z = pf_curr[11];
+
+ TransformInterpolator::interpolate_transform_3d(tp, tc, tr, f);
+
+ pf_int[0] = tr.basis.rows[0][0];
+ pf_int[1] = tr.basis.rows[0][1];
+ pf_int[2] = tr.basis.rows[0][2];
+ pf_int[4] = tr.basis.rows[1][0];
+ pf_int[5] = tr.basis.rows[1][1];
+ pf_int[6] = tr.basis.rows[1][2];
+ pf_int[8] = tr.basis.rows[2][0];
+ pf_int[9] = tr.basis.rows[2][1];
+ pf_int[10] = tr.basis.rows[2][2];
+ pf_int[3] = tr.origin.x;
+ pf_int[7] = tr.origin.y;
+ pf_int[11] = tr.origin.z;
+ }
+
+ pf_prev += mmi->_vf_size_xform;
+ pf_curr += mmi->_vf_size_xform;
+ pf_int += mmi->_vf_size_xform;
+
+ // Color.
+ if (mmi->_vf_size_color == 4) {
+ for (int i = 0; i < 4; i++) {
+ pf_int[i] = Math::lerp(pf_prev[i], pf_curr[i], f);
+ }
+
+ pf_prev += 4;
+ pf_curr += 4;
+ pf_int += 4;
+ }
+
+ // Custom data.
+ if (mmi->_vf_size_data == 4) {
+ for (int i = 0; i < 4; i++) {
+ pf_int[i] = Math::lerp(pf_prev[i], pf_curr[i], f);
+ }
+
+ pf_prev += 4;
+ pf_curr += 4;
+ pf_int += 4;
+ }
+ }
+
+ _multimesh_set_buffer(rid, mmi->_data_interpolated);
+
+ // TODO: Make sure AABBs are constantly up to date through the interpolation?
+ // NYI.
+ }
+ }
+ }
+}
diff --git a/servers/rendering/storage/mesh_storage.h b/servers/rendering/storage/mesh_storage.h
index 39fd4f393d..ecd2a967d0 100644
--- a/servers/rendering/storage/mesh_storage.h
+++ b/servers/rendering/storage/mesh_storage.h
@@ -89,39 +89,110 @@ public:
virtual void update_mesh_instances() = 0;
/* MULTIMESH API */
+ struct MultiMeshInterpolator {
+ RS::MultimeshTransformFormat _transform_format = RS::MULTIMESH_TRANSFORM_3D;
+ bool _use_colors = false;
+ bool _use_custom_data = false;
- virtual RID multimesh_allocate() = 0;
- virtual void multimesh_initialize(RID p_rid) = 0;
- virtual void multimesh_free(RID p_rid) = 0;
+ // The stride of the buffer in floats.
+ int _stride = 0;
- virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) = 0;
+ // Vertex format sizes in floats.
+ int _vf_size_xform = 0;
+ int _vf_size_color = 0;
+ int _vf_size_data = 0;
- virtual int multimesh_get_instance_count(RID p_multimesh) const = 0;
+ // Set by allocate, can be used to prevent indexing out of range.
+ int _num_instances = 0;
- virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh) = 0;
- virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) = 0;
- virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) = 0;
- virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) = 0;
- virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) = 0;
+ // Quality determines whether to use lerp or slerp etc.
+ int quality = 0;
+ bool interpolated = false;
+ bool on_interpolate_update_list = false;
+ bool on_transform_update_list = false;
- virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) = 0;
- virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const = 0;
+ Vector<float> _data_prev;
+ Vector<float> _data_curr;
+ Vector<float> _data_interpolated;
+ };
- virtual RID multimesh_get_mesh(RID p_multimesh) const = 0;
+ virtual RID multimesh_allocate();
+ virtual void multimesh_initialize(RID p_rid);
+ virtual void multimesh_free(RID p_rid);
- virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const = 0;
- virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const = 0;
- virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const = 0;
- virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const = 0;
+ virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false);
- virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) = 0;
- virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const = 0;
+ virtual int multimesh_get_instance_count(RID p_multimesh) const;
- virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) = 0;
- virtual int multimesh_get_visible_instances(RID p_multimesh) const = 0;
+ virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh);
+ virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform);
+ virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform);
+ virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color);
+ virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color);
- virtual AABB multimesh_get_aabb(RID p_multimesh) const = 0;
+ virtual void multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb);
+ virtual AABB multimesh_get_custom_aabb(RID p_multimesh) const;
+ virtual RID multimesh_get_mesh(RID p_multimesh) const;
+
+ virtual Transform3D multimesh_instance_get_transform(RID p_multimesh, int p_index) const;
+ virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const;
+ virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const;
+ virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const;
+
+ virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer);
+ virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const;
+
+ virtual void multimesh_set_buffer_interpolated(RID p_multimesh, const Vector<float> &p_buffer, const Vector<float> &p_buffer_prev);
+ virtual void multimesh_set_physics_interpolated(RID p_multimesh, bool p_interpolated);
+ virtual void multimesh_set_physics_interpolation_quality(RID p_multimesh, RS::MultimeshPhysicsInterpolationQuality p_quality);
+ virtual void multimesh_instance_reset_physics_interpolation(RID p_multimesh, int p_index);
+
+ virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible);
+ virtual int multimesh_get_visible_instances(RID p_multimesh) const;
+
+ virtual AABB multimesh_get_aabb(RID p_multimesh) const;
+
+ virtual RID _multimesh_allocate() = 0;
+ virtual void _multimesh_initialize(RID p_rid) = 0;
+ virtual void _multimesh_free(RID p_rid) = 0;
+
+ virtual void _multimesh_allocate_data(RID p_multimesh, int p_instances, RS::MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) = 0;
+
+ virtual int _multimesh_get_instance_count(RID p_multimesh) const = 0;
+
+ virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) = 0;
+ virtual void _multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform3D &p_transform) = 0;
+ virtual void _multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) = 0;
+ virtual void _multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) = 0;
+ virtual void _multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) = 0;
+
+ virtual void _multimesh_set_custom_aabb(RID p_multimesh, const AABB &p_aabb) = 0;
+ virtual AABB _multimesh_get_custom_aabb(RID p_multimesh) const = 0;
+
+ virtual RID _multimesh_get_mesh(RID p_multimesh) const = 0;
+
+ virtual Transform3D _multimesh_instance_get_transform(RID p_multimesh, int p_index) const = 0;
+ virtual Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const = 0;
+ virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const = 0;
+ virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const = 0;
+
+ virtual void _multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) = 0;
+ virtual Vector<float> _multimesh_get_buffer(RID p_multimesh) const = 0;
+
+ virtual void _multimesh_set_visible_instances(RID p_multimesh, int p_visible) = 0;
+ virtual int _multimesh_get_visible_instances(RID p_multimesh) const = 0;
+
+ virtual AABB _multimesh_get_aabb(RID p_multimesh) const = 0;
+
+ // Multimesh is responsible for allocating / destroying a MultiMeshInterpolator object.
+ // This allows shared functionality for interpolation across backends.
+ virtual MultiMeshInterpolator *_multimesh_get_interpolator(RID p_multimesh) const = 0;
+
+private:
+ void _multimesh_add_to_interpolation_lists(RID p_multimesh, MultiMeshInterpolator &r_mmi);
+
+public:
/* SKELETON API */
virtual RID skeleton_allocate() = 0;
@@ -137,6 +208,19 @@ public:
virtual void skeleton_set_base_transform_2d(RID p_skeleton, const Transform2D &p_base_transform) = 0;
virtual void skeleton_update_dependency(RID p_base, DependencyTracker *p_instance) = 0;
+
+ /* INTERPOLATION */
+
+ struct InterpolationData {
+ void notify_free_multimesh(RID p_rid);
+ LocalVector<RID> multimesh_interpolate_update_list;
+ LocalVector<RID> multimesh_transform_update_lists[2];
+ LocalVector<RID> *multimesh_transform_update_list_curr = &multimesh_transform_update_lists[0];
+ LocalVector<RID> *multimesh_transform_update_list_prev = &multimesh_transform_update_lists[1];
+ } _interpolation_data;
+
+ void update_interpolation_tick(bool p_process = true);
+ void update_interpolation_frame(bool p_process = true);
};
#endif // MESH_STORAGE_H
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 9fc67b04b1..0f0d15a52f 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -2439,8 +2439,15 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("multimesh_set_buffer", "multimesh", "buffer"), &RenderingServer::multimesh_set_buffer);
ClassDB::bind_method(D_METHOD("multimesh_get_buffer", "multimesh"), &RenderingServer::multimesh_get_buffer);
+ ClassDB::bind_method(D_METHOD("multimesh_set_buffer_interpolated", "multimesh", "buffer", "buffer_previous"), &RenderingServer::multimesh_set_buffer_interpolated);
+ ClassDB::bind_method(D_METHOD("multimesh_set_physics_interpolated", "multimesh", "interpolated"), &RenderingServer::multimesh_set_physics_interpolated);
+ ClassDB::bind_method(D_METHOD("multimesh_set_physics_interpolation_quality", "multimesh", "quality"), &RenderingServer::multimesh_set_physics_interpolation_quality);
+ ClassDB::bind_method(D_METHOD("multimesh_instance_reset_physics_interpolation", "multimesh", "index"), &RenderingServer::multimesh_instance_reset_physics_interpolation);
+
BIND_ENUM_CONSTANT(MULTIMESH_TRANSFORM_2D);
BIND_ENUM_CONSTANT(MULTIMESH_TRANSFORM_3D);
+ BIND_ENUM_CONSTANT(MULTIMESH_INTERP_QUALITY_FAST);
+ BIND_ENUM_CONSTANT(MULTIMESH_INTERP_QUALITY_HIGH);
/* SKELETON API */
@@ -2477,6 +2484,7 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("light_directional_set_sky_mode", "light", "mode"), &RenderingServer::light_directional_set_sky_mode);
ClassDB::bind_method(D_METHOD("light_projectors_set_filter", "filter"), &RenderingServer::light_projectors_set_filter);
+ ClassDB::bind_method(D_METHOD("lightmaps_set_bicubic_filter", "enable"), &RenderingServer::lightmaps_set_bicubic_filter);
BIND_ENUM_CONSTANT(LIGHT_PROJECTOR_FILTER_NEAREST);
BIND_ENUM_CONSTANT(LIGHT_PROJECTOR_FILTER_LINEAR);
@@ -3618,6 +3626,7 @@ void RenderingServer::init() {
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "rendering/lightmapping/probe_capture/update_speed", PROPERTY_HINT_RANGE, "0.001,256,0.001"), 15);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "rendering/lightmapping/primitive_meshes/texel_size", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 0.2);
+ GLOBAL_DEF("rendering/lightmapping/lightmap_gi/use_bicubic_filter", true);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/global_illumination/sdfgi/probe_ray_count", PROPERTY_HINT_ENUM, "8 (Fastest),16,32,64,96,128 (Slowest)"), 1);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/global_illumination/sdfgi/frames_to_converge", PROPERTY_HINT_ENUM, "5 (Less Latency but Lower Quality),10,15,20,25,30 (More Latency but Higher Quality)"), 5);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index 8450cb0198..d8b6651833 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -426,6 +426,11 @@ public:
MULTIMESH_TRANSFORM_3D,
};
+ enum MultimeshPhysicsInterpolationQuality {
+ MULTIMESH_INTERP_QUALITY_FAST,
+ MULTIMESH_INTERP_QUALITY_HIGH,
+ };
+
virtual void multimesh_allocate_data(RID p_multimesh, int p_instances, MultimeshTransformFormat p_transform_format, bool p_use_colors = false, bool p_use_custom_data = false) = 0;
virtual int multimesh_get_instance_count(RID p_multimesh) const = 0;
@@ -449,6 +454,12 @@ public:
virtual void multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_buffer) = 0;
virtual Vector<float> multimesh_get_buffer(RID p_multimesh) const = 0;
+ // Interpolation.
+ virtual void multimesh_set_buffer_interpolated(RID p_multimesh, const Vector<float> &p_buffer_curr, const Vector<float> &p_buffer_prev) = 0;
+ virtual void multimesh_set_physics_interpolated(RID p_multimesh, bool p_interpolated) = 0;
+ virtual void multimesh_set_physics_interpolation_quality(RID p_multimesh, MultimeshPhysicsInterpolationQuality p_quality) = 0;
+ virtual void multimesh_instance_reset_physics_interpolation(RID p_multimesh, int p_index) = 0;
+
virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) = 0;
virtual int multimesh_get_visible_instances(RID p_multimesh) const = 0;
@@ -687,6 +698,8 @@ public:
virtual void lightmap_set_probe_capture_update_speed(float p_speed) = 0;
+ virtual void lightmaps_set_bicubic_filter(bool p_enable) = 0;
+
/* PARTICLES API */
virtual RID particles_create() = 0;
@@ -924,6 +937,7 @@ public:
virtual void viewport_set_canvas_transform(RID p_viewport, RID p_canvas, const Transform2D &p_offset) = 0;
virtual void viewport_set_transparent_background(RID p_viewport, bool p_enabled) = 0;
virtual void viewport_set_use_hdr_2d(RID p_viewport, bool p_use_hdr) = 0;
+ virtual bool viewport_is_using_hdr_2d(RID p_viewport) const = 0;
virtual void viewport_set_snap_2d_transforms_to_pixel(RID p_viewport, bool p_enabled) = 0;
virtual void viewport_set_snap_2d_vertices_to_pixel(RID p_viewport, bool p_enabled) = 0;
@@ -1788,6 +1802,7 @@ VARIANT_ENUM_CAST(RenderingServer::ArrayCustomFormat);
VARIANT_ENUM_CAST(RenderingServer::PrimitiveType);
VARIANT_ENUM_CAST(RenderingServer::BlendShapeMode);
VARIANT_ENUM_CAST(RenderingServer::MultimeshTransformFormat);
+VARIANT_ENUM_CAST(RenderingServer::MultimeshPhysicsInterpolationQuality);
VARIANT_ENUM_CAST(RenderingServer::LightType);
VARIANT_ENUM_CAST(RenderingServer::LightParam);
VARIANT_ENUM_CAST(RenderingServer::LightBakeMode);
diff --git a/servers/xr/xr_hand_tracker.cpp b/servers/xr/xr_hand_tracker.cpp
index abfe2e9867..2f64737ed6 100644
--- a/servers/xr/xr_hand_tracker.cpp
+++ b/servers/xr/xr_hand_tracker.cpp
@@ -60,6 +60,7 @@ void XRHandTracker::_bind_methods() {
BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_UNKNOWN);
BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_UNOBSTRUCTED);
BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_CONTROLLER);
+ BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_NOT_TRACKED);
BIND_ENUM_CONSTANT(HAND_TRACKING_SOURCE_MAX);
BIND_ENUM_CONSTANT(HAND_JOINT_PALM);
diff --git a/servers/xr/xr_hand_tracker.h b/servers/xr/xr_hand_tracker.h
index c7c18a31f8..7837df3e0a 100644
--- a/servers/xr/xr_hand_tracker.h
+++ b/servers/xr/xr_hand_tracker.h
@@ -42,6 +42,7 @@ public:
HAND_TRACKING_SOURCE_UNKNOWN,
HAND_TRACKING_SOURCE_UNOBSTRUCTED,
HAND_TRACKING_SOURCE_CONTROLLER,
+ HAND_TRACKING_SOURCE_NOT_TRACKED,
HAND_TRACKING_SOURCE_MAX
};
diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h
index a166002cdd..9ec1b812df 100644
--- a/tests/scene/test_code_edit.h
+++ b/tests/scene/test_code_edit.h
@@ -4609,6 +4609,26 @@ TEST_CASE("[SceneTree][CodeEdit] text manipulation") {
CHECK(code_edit->get_text() == "line 1\nline 2\nline 3");
CHECK(code_edit->get_caret_line() == 0);
CHECK(code_edit->get_caret_column() == 0);
+
+ // Unfold previous folded line on backspace if the caret is at the first column.
+ code_edit->set_line_folding_enabled(true);
+ code_edit->set_text("line 1\n\tline 2\nline 3");
+ code_edit->set_caret_line(2);
+ code_edit->set_caret_column(0);
+ code_edit->fold_line(0);
+ code_edit->backspace();
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ code_edit->set_line_folding_enabled(false);
+
+ // Do not unfold previous line on backspace if the caret is not at the first column.
+ code_edit->set_line_folding_enabled(true);
+ code_edit->set_text("line 1\n\tline 2\nline 3");
+ code_edit->set_caret_line(2);
+ code_edit->set_caret_column(4);
+ code_edit->fold_line(0);
+ code_edit->backspace();
+ CHECK(code_edit->is_line_folded(0));
+ code_edit->set_line_folding_enabled(false);
}
SUBCASE("[TextEdit] cut") {
diff --git a/thirdparty/README.md b/thirdparty/README.md
index bc2964c392..f241ce56d1 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -894,7 +894,7 @@ number and run the script.
## ufbx
- Upstream: https://github.com/ufbx/ufbx
-- Version: 0.14.0 (80ff790ab36507b99ec7e4ef55b9cfb076ce821b, 2024)
+- Version: 0.14.3 (19bdb7e7ef02eb914d5e7211a3685f50ee6d27e3, 2024)
- License: MIT
Files extracted from upstream source:
diff --git a/thirdparty/ufbx/ufbx.c b/thirdparty/ufbx/ufbx.c
index 6d584369a7..2d13c78bd8 100644
--- a/thirdparty/ufbx/ufbx.c
+++ b/thirdparty/ufbx/ufbx.c
@@ -570,7 +570,7 @@ ufbx_static_assert(sizeof_f64, sizeof(double) == 8);
// -- Version
-#define UFBX_SOURCE_VERSION ufbx_pack_version(0, 14, 0)
+#define UFBX_SOURCE_VERSION ufbx_pack_version(0, 14, 3)
ufbx_abi_data_def const uint32_t ufbx_source_version = UFBX_SOURCE_VERSION;
ufbx_static_assert(source_header_version, UFBX_SOURCE_VERSION/1000u == UFBX_HEADER_VERSION/1000u);
@@ -7257,8 +7257,8 @@ typedef enum {
} ufbxi_parse_state;
typedef enum {
- UFBXI_ARRAY_FLAG_RESULT = 0x1, // < Alloacte the array from the result buffer
- UFBXI_ARRAY_FLAG_TMP_BUF = 0x2, // < Alloacte the array from the result buffer
+ UFBXI_ARRAY_FLAG_RESULT = 0x1, // < Allocate the array from the result buffer
+ UFBXI_ARRAY_FLAG_TMP_BUF = 0x2, // < Allocate the array from the result buffer
UFBXI_ARRAY_FLAG_PAD_BEGIN = 0x4, // < Pad the begin of the array with 4 zero elements to guard from invalid -1 index accesses
UFBXI_ARRAY_FLAG_ACCURATE_F32 = 0x8, // < Must be parsed as bit-accurate 32-bit floats
} ufbxi_array_flags;
@@ -21772,6 +21772,10 @@ ufbxi_noinline static ufbx_quat ufbxi_get_rotation(const ufbx_props *props, ufbx
ufbxi_mul_rotate_quat(&t, node->adjust_pre_rotation);
}
+ if (node->adjust_mirror_axis) {
+ ufbxi_mirror_rotation(&t.rotation, node->adjust_mirror_axis);
+ }
+
return t.rotation;
}
@@ -24710,7 +24714,7 @@ static ufbxi_noinline const ufbx_prop *ufbxi_next_prop_slow(ufbxi_prop_iter *ite
const ufbx_prop_override *over = iter->over;
if (prop == iter->prop_end && over == iter->over_end) return NULL;
- // We can use `UINT32_MAX` as a termianting key (aka prefix) as prop names must
+ // We can use `UINT32_MAX` as a terminating key (aka prefix) as prop names must
// be valid UTF-8 and the byte sequence "\xff\xff\xff\xff" is not valid.
uint32_t prop_key = prop != iter->prop_end ? prop->_internal_key : UINT32_MAX;
uint32_t over_key = over != iter->over_end ? over->_internal_key : UINT32_MAX;
diff --git a/thirdparty/ufbx/ufbx.h b/thirdparty/ufbx/ufbx.h
index 072569068a..8d856edaad 100644
--- a/thirdparty/ufbx/ufbx.h
+++ b/thirdparty/ufbx/ufbx.h
@@ -266,7 +266,7 @@ struct ufbx_converter { };
// `ufbx_source_version` contains the version of the corresponding source file.
// HINT: The version can be compared numerically to the result of `ufbx_pack_version()`,
// for example `#if UFBX_VERSION >= ufbx_pack_version(0, 12, 0)`.
-#define UFBX_HEADER_VERSION ufbx_pack_version(0, 14, 0)
+#define UFBX_HEADER_VERSION ufbx_pack_version(0, 14, 3)
#define UFBX_VERSION UFBX_HEADER_VERSION
// -- Basic types
@@ -2673,7 +2673,7 @@ UFBX_ENUM_TYPE(ufbx_texture_type, UFBX_TEXTURE_TYPE, UFBX_TEXTURE_SHADER);
// Blend modes to combine layered textures with, compatible with common blend
// mode definitions in many art programs. Simpler blend modes have equations
-// specified below where `src` is the layer to compososite over `dst`.
+// specified below where `src` is the layer to composite over `dst`.
// See eg. https://www.w3.org/TR/2013/WD-compositing-1-20131010/#blendingseparable
typedef enum ufbx_blend_mode UFBX_ENUM_REPR {
UFBX_BLEND_TRANSLUCENT, // < `src` effects result alpha
@@ -5286,6 +5286,8 @@ typedef enum ufbx_transform_flags UFBX_FLAG_REPR {
} ufbx_transform_flags;
// Evaluate the animated transform of a node given a time.
+// The returned transform is the local transform of the node (ie. relative to the parent),
+// comparable to `ufbx_node.local_transform`.
ufbx_abi ufbx_transform ufbx_evaluate_transform(const ufbx_anim *anim, const ufbx_node *node, double time);
ufbx_abi ufbx_transform ufbx_evaluate_transform_flags(const ufbx_anim *anim, const ufbx_node *node, double time, uint32_t flags);